/*
 * This module is a special tserver_tcp module to provide SSL (Secure Socket
 * Layer) support.  When linked as a shareable image it substitutes for
 * an actual tserver_tcpshr image to transparently provide the connection.
 *
 * The logical name TSERVER_SSL_TRANSPORT must be defined to point to an actual
 * tserver_tcp shareable image (e.g. www_system:tserver_ucxshr).  That image
 * is dynamically loaded and its entry points provide TCP I/O for the client.
 *
 * If the started port is not an SSL port, we transparently pass the I/O 
 * operations (read, write, etc) onto the tserver_tcpshr2 image.  If the 
 * port number is for an SSL port, internally process the SSL handshakes and
 * encoding and present the data to the calling program in clear form
 * as if it were a normal connection.
 *
 * For testing, odd port numbers are assumed to be SSL (secure) ports and even
 * port numbers (e.g. 80) are non-SSL ports.
 *
 * int ts_declare_tcp_port ( port_num, client_limit, attr, thread, start );
 *	int start ( ctx, port, remote_address, index, remaining );
 * int ts_set_manage_port ( port_num, host_address, callback );
 *	int callback ( ctx, port, shutdown_time );
 * int ts_set_logging ( callback );
 *	int callback ( min_level, faostr, ... );
 * int ts_tcp_write ( ctx, buffer, length );
 * int ts_tcp_read ( ctx, buffer, maxlen, *length );
 * int ts_tcp_close ( ctx );
 * int ts_tcp_info ( local_port, remote_port, remote_address, remote_host );
 * char *ts_tcp_remote_host();
 * int ts_set_local_addr ( address );
 *
 * Builing:
 *    cc tserver_ssl
 *    cc ssl_server.c
 *    @link_share ssl ssl_server,{ssllib/lib},		!note trailing comma
 *
 * Revised: 20-JUL-1996
 * Revised: 26-SEP-1997			Allow time_limit on secure ports.
 * Revised: 29-OCT-2001			Add @SSLPORT=nnn localaddress option.
 *					Add hooks for client cert. support.
 * Revised: 27-NOV-2001			Rework client cert hooks.
 */
#include "pthread_1c_np.h"
#include "tutil.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <descrip.h>

#ifndef EPERM
#include <errno.h>
#endif

#define ERROR_FAIL(s,text) if (s == -1) perror(text);
#define COND_TEST(s,text) if ( (s&1) == 0 ) perror ( text );
#define MAX_IO_SIZE 2048		/* maximum transport read request */

static int (*tcp_declare_port)(), (*tcp_write)(), (*tcp_read)();
static int (*tcp_close)(), (*tcp_stack_used)(), (*tcp_info)();
static char *(*tcp_remote_host)();
static int (*set_manage_port)(), (*set_local_address)(), (*set_logging)();
static int (*tcp_set_time_limit)();
static int (*tcp_transaction_count)(), (*tcp_end_transaction)();

#include "ssl_server.h"
#include "client_cert.h"
#include "cert_map.h"

typedef struct client_context client_ctx_struct, *client_ctx;

struct client_context {
    void *tcp_ctx;		/* context for real TCP transport layer  */
    unsigned long local_ip_addr;
    int secure;			/* True iff SSL protocol */
    
				/* null if no certificate registered */
    ssl_context ssl_ctx;
    int ssl_version;		/* sll protocol in use (2, 3, etc) */
    int app_data_pending;	/* Bytes of application data ready to read */
    unsigned char *app_data;	/* Data decoded and waiting to be read */
    /*
     * Extension for client certificate operations.
     */
    long cc_rec_num, cc_parent;
    int id_count;
    unsigned long identifiers[32];
    struct certmap_db_rec ccrec;	/* Record from client cert. database. */
    struct certmap_db_var subject, issuer;   /* scratch for search operations */
};

struct port_context {
    struct port_context *flink, *blink;
    int port_num;
    int status;
    int secure;				/* If true, connection encrypted */
    int (*start)( client_ctx ctx, 
	  short port, unsigned char *rem_addr,
	  int ndx, int length);	/* Application supplied startup routine */
};
typedef struct port_context port_ctx_struct, *port_ctx;
#define ts_client_ctx client_ctx
#include "tserver_tcp.h"		/* validate header file */

/*
 * port_enable struct used to hold list of port number enabled with
 * @SSLPORT=nnn localaddress hack.
 */
struct port_enable {
    struct port_enable *next;
    int port;
};

static port_ctx_struct port_list;
static client_ctx *client_list;
static int client_list_alloc;		/* Slots in client list */
static int ts_startup_client ( );
port_ctx tserver_tcp_port_list;
static port_ctx *transport_port_list;
static int (*ts_manage_callback)(ts_client_ctx ctx, short port, int
		*shutdown_time);  /* upcall for management connects */
static int ts_logging = 0;
static int (*ts_putlog)(int, char *, ...);	/* upcall for logging */
static int report_ssl_error ( int, char * );
static int explicit_secure_ports;
static void *client_certificate_db;
static char client_cert_msg[256];
static struct port_enable *secure_ports;
/*
 * The psuedo-environment list is a list of arbitrary parameters loaded
 * from the configuration file in the 'keyword=value' form via
 * the localaddress rule (hostname starts with @SSL).
 */
#define MAX_PENV 300
static char *pseudo_env[MAX_PENV];
static int pseudo_env_count = 0;
/*
 * The following mutexes and conditions serialize access to resources:
 *    ssl_ctl		This mutex is obtained when a thread needs to modify
 *			the client list or port list data structures.
 */
static pthread_mutex_t ssl_ctl;		/* ssl context database mutex */
static pthread_key_t client_key;	/* Private data for TCP client */
static pthread_once_t ts_mutex_setup = PTHREAD_ONCE_INIT;
static pthread_once_t ts_ssl_setup = PTHREAD_ONCE_INIT;

static void ts_init_mutexes();		/* forward reference */
static void ts_init_ssl ();
/*************************************************************************/
/* Set flag and putlog callback routine address.  Note that this routine
 * must only be called if the ssl_ctl mutex is owned by the current thread.
 */
int ts_set_logging ( int callback(int, char *, ...) )
{
    int previous_logging;
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    /*
     * Get mutex and update shared data.  Return value if previous setting.
     */
    pthread_mutex_lock ( &ssl_ctl );
    previous_logging = ts_logging;
    ts_putlog = callback;
    if ( ts_putlog ) ts_logging = 1; else ts_logging = 0;
    pthread_mutex_unlock ( &ssl_ctl );
    return (*set_logging)( callback );
}
/*
 * Use this macro with care since it leaves an if dangling!
 */
#define PUTLOG if ( ts_logging ) (*ts_putlog)
/*************************************************************************/
/* Set local listen address to bind sockets to, simply pass on to the
 * actual TCP layer.
 */
int ts_set_local_addr ( char *addr ) 
{
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    /*
     * Check for special case where addr starts with "@SSL"
     */
    if ( addr[0] == '@' ) {
	char keyword[256], errmsg[256], *value;
	/*
	 * Break argument into keyword=string and upcase keyword.
	 */
	tu_strnzcpy ( keyword, addr, sizeof(keyword)-1 );
	value = tu_strstr ( keyword, "=" );
	if ( value ) *value++ = '\0';
	tu_strupcase ( keyword, keyword );
	/*
	 * Only handle keywords that start with SSL:
	 *      sslport=nnn
	 *	sslcertmap=dbfile
	 */
	if ( tu_strncmp ( keyword, "@SSL", 4 ) == 0 ) {
	    if ( (keyword[4]=='_') && (pseudo_env_count < (MAX_PENV-1)) ) {
		/*
		 * Variables of form @SSL_ are passed through to
		 * threaded module.
		 */
		pseudo_env[pseudo_env_count] = malloc (strlen(keyword) +
		     (value ? strlen(value) : 0) );
		if ( pseudo_env[pseudo_env_count] ) {
		    sprintf ( pseudo_env[pseudo_env_count],
			"%s=%s", &keyword[5], value ? value : "" );
		    pseudo_env_count++;
		} else {
	            PUTLOG ( 0, "Malloc fail while processing SSL option!/" );
		    return 20;
		}
	    } else if ( tu_strncmp ( keyword, "@SSLCERTMAP", 12 ) == 0 ) {
		/*
		 * Attempt to open database that maps client certificates
		 * to VMS identifiers.
		 */
		char errmsg[256];

		if ( !value ) value = certmap_logical_dbname;
		else if ( !*value ) value = certmap_logical_dbname;
		if ( 0 == (1 & certmap_open ( value, "www_system:[].dat", "r",
			&client_certificate_db, errmsg )) ) {
		    PUTLOG ( 0, 
			"Certificate map open error on '!AZ':!/  !AZ!/", 
				value, errmsg );
		    return SS$_ABORT;
		}

	    } else if ( tu_strncmp ( keyword, "@SSLPORT", 10 ) == 0 ) {
		/*
		 * First SSLPORT overrides default, subsequent add to list.
		 */
		int portnum;
		portnum = atoi ( value );
		if ( (portnum <= 0) || (portnum > 65536) ) {
		    PUTLOG ( 0, "SSL port out of range: '!AZ'!/", portnum );
		    return 20;
		}
		if ( explicit_secure_ports ) {
		    struct port_enable *item;
		    LOCK_C_RTL
		    item = malloc ( sizeof(struct port_enable) );
		    UNLOCK_C_RTL
		    item->next = secure_ports;
		    item->port = portnum;
		    secure_ports = item;
		} else {
		    PUTLOG ( 0, "Overriding default SSL port (!SL)!/",
			secure_ports->port );
		    secure_ports->port = portnum;
		    explicit_secure_ports = 1;
		}
	    } else {
		PUTLOG ( 0, "Invalid SSL option request: '!AZ!AZ!AZ'!/",
			keyword, value ? "=" : "", value ? value : "" );
		return 20;
	    }
	    /*
	     * SSL options aren't passed on to TCP layer.
	     */
	    return 1;
	}
    }
    /*
     * Call shareable image to set address.
     */
    return set_local_address ( addr );   /* always return success */
}
/*************************************************************************/
/* Initialize port management parameters, save callback address and
 * supply our own so we can build our own fake context.
 */
static int ssl_manage ( client_ctx tcp_ctx, short port, int *sd_time )
{
    struct client_context ctx;
    /*
     * build fake structure.
     */
    ctx.tcp_ctx = (void *) tcp_ctx;
    ctx.local_ip_addr = 0;
    ctx.secure = 0;	/* Remainder now moot */

    return (*ts_manage_callback) ( &ctx, port, sd_time );   
}
int ts_set_manage_port ( int port_num, unsigned int host_address,
	int (*callback)(client_ctx ctx, short port, int *shutdown_time) )
{
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    ts_manage_callback = callback;
    return (*set_manage_port) ( port_num, host_address, ssl_manage );
}
/*************************************************************************/
/* Create thread that listens on a particular TCP/IP port and starts
 * new thread for each accepted connection.  A caller supplied start routine
 * is called by the created thread to process the session.
 *
 *
 * User start (session) routine:
 *	int start ( void *ctx, int port_num, sockaddrin remote );
 */
int ts_declare_tcp_port ( 
    int port_num, 		/* Port number to listen on, host order */
    int client_limit, 		/* Limit on concurrent client threads allowed */
    pthread_attr_t *client_attr, /* Thread attributes for client threads */
    pthread_t *control_thread, 	/* Thread that listens for connects */
    int (*start)( client_ctx ctx, short port, unsigned char *rem_addr,
		int ndx, int length) )	/* Start routine for new clients. */
{
    int i, status;
    port_ctx ctx, tmp;
    struct port_enable *sdef;
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    pthread_once ( &ts_ssl_setup, ts_init_ssl );
    /*
     * Allocate port control block that becomes the thread-specific context
     * data for the new thread.
     */
    LOCK_C_RTL
    ctx = (port_ctx) malloc ( sizeof(port_ctx_struct) );
    UNLOCK_C_RTL
    /*
     * Link the control block into the global list and connect pool, use 
     * mutex to synchronize update of pointers.
     */
    pthread_mutex_lock ( &ssl_ctl );
    ctx->port_num = port_num;
    ctx->flink = &port_list;
    ctx->blink = port_list.blink;
    port_list.blink->flink = ctx;
    port_list.blink = ctx;
    pthread_mutex_unlock   ( &ssl_ctl );
    /*
     * Initialize pertinent fields in control block.  Secure flag
     * is true if SSL encoding is set for this port.
     */
    ctx->secure = 0;			/* default to non-SSL */
    for ( sdef = secure_ports; sdef; sdef = sdef->next ) {
	if ( sdef->port == port_num ) ctx->secure = 1;
    }
    ctx->start = start;
    /*
     * If client certificates enabled, prepare it to handle client_limit
     * concurrent requests.
     */
    if ( (client_limit > 0) && client_certificate_db ) {/*
	status = client_cert_allocate ( client_certificate_db, client_limit);*/
    }
    /*
     * Call declare port routine in actual TCP layer supplying our own
     * shell routine to do the SSL initialization.
     */
    TRY {
       status = (*tcp_declare_port)( port_num, client_limit, client_attr,
		control_thread, ts_startup_client );
    }
    CATCH_ALL {
#ifdef PTHREAD_USE_D4
	exc_report ( THIS_CATCH );
#endif
	PUTLOG(0,"SSL layer caught exception!/");
    }
    ENDTRY
    tserver_tcp_port_list = *transport_port_list;
    return status;
}
/**************************************************************************/
/* Write data to thread's TCP/IP connection.
 */
int ts_tcp_write 
    ( client_ctx ctx,		/* Context passed to port start() function */
    char *buffer,		/* Data buffer to write */
    int length )		/* Number of chars to write */
{
    int status;
    if ( ctx->secure ) {
	/*
	 * Add to output stream.
	 */
	status = tssl_put_app_data 
		( ctx->ssl_ctx, (unsigned char *) buffer, length );
    } else {
	/* Pass through to TCP shareable */
	status = (*tcp_write) ( ctx->tcp_ctx, buffer, length );
    }
    return status;
}
/**************************************************************************/
/* Read data from thread's TCP/IP connection.
 */
int ts_tcp_read
    ( client_ctx ctx,		/* Context passed to port start() function */
    char *buffer,		/* Data buffer to write */
    int maxlen,			/* Size of buffer */
    int *length )		/* Number of chars to write */
{
    int status;
    if ( ctx->secure ) {
	/*
	 * Extract bytes from decoded stream.
	 */
	int count;
	count = ctx->app_data_pending;
	if ( count == 0 ) {
	    status = tssl_get_app_data 
		( ctx->ssl_ctx, &ctx->app_data_pending, &ctx->app_data );
	    if ( (status&1) == 0 ) return status;
	    count = ctx->app_data_pending;
	    if ( count <= 0 ) return 312;		/* invalid result */
	}
	/*
	 * Limit count returned to no more than maxlen.
	 */
	if ( count > maxlen ) count = maxlen;
	*length = count;
	ctx->app_data_pending = ctx->app_data_pending - count;
	for ( ; count > 0; --count ) *buffer++ = *ctx->app_data++;
	status = 1;

    } else {
	/* Pass through to TCP shareable */
	status = (*tcp_read) ( ctx->tcp_ctx, buffer, maxlen, length );
    }
    return status;
}
/**************************************************************************/
/* Perform synchronous disconnect of socket.  When in secure mode, pass
 * off connection to new thread via tcp_end_transaction.  We use a new
 * thread to help ensure clean rundown of the current request (files closed,
 * etc).
 */
int ts_tcp_close
    ( client_ctx ctx )		/* Context passed to port start() function */
{
    int status;
    if ( ctx->secure ) {
	/*
	 * Rundown this transaction, preserving the sll context.  New thread
	 * will be created to which we reassign the sll context.
	 */
	tssl_rundown_context ( ctx->ssl_ctx, 1 );
	ctx->secure = 0;	/* no longer an SSL connection */
    }
    return tcp_close ( ctx->tcp_ctx );
}
/**************************************************************************/
/* Do one-time initialization of static variables for this module, primarily
 * mutexes, condition variables and thread-specific context keys.
 */
static void client_rundown();
static void ts_init_mutexes ( )
{
    int status, i, LIB$FIND_IMAGE_SYMBOL();
    $DESCRIPTOR(image_file,"TSERVER_SSL_TRANSPORT");
    $DESCRIPTOR(image_dir,"WWW_SYSTEM:.EXE");
    $DESCRIPTOR(sym_declare_tcp_port,"TS_DECLARE_TCP_PORT");
    $DESCRIPTOR(sym_write,"TS_TCP_WRITE");
    $DESCRIPTOR(sym_read,"TS_TCP_READ");
    $DESCRIPTOR(sym_close,"TS_TCP_CLOSE");
    $DESCRIPTOR(sym_stack_used,"TS_TCP_STACK_USED");
    $DESCRIPTOR(sym_remote_host,"TS_TCP_REMOTE_HOST");
    $DESCRIPTOR(sym_tcp_info,"TS_TCP_INFO");
    $DESCRIPTOR(sym_set_manage_port,"TS_SET_MANAGE_PORT");
    $DESCRIPTOR(sym_set_local_addr,"TS_SET_LOCAL_ADDR");
    $DESCRIPTOR(sym_set_logging,"TS_SET_LOGGING");
    $DESCRIPTOR(sym_set_time_limit,"TS_TCP_SET_TIME_LIMIT");
    $DESCRIPTOR(sym_transaction_count,"TS_TCP_TRANSACTION_COUNT");
    $DESCRIPTOR(sym_end_transaction,"TS_TCP_END_TRANSACTION");
    $DESCRIPTOR(sym_port_list,"TS_TCP_PORT_LIST");
    /*
     * Create mutexes and condition variables.
     */
    status = INITIALIZE_MUTEX ( &ssl_ctl );
    ERROR_FAIL(status,"Error creating ssl_ctl mutex" )
    /* 
     * Create keys used to locate context blocks 
     */
    status = CREATE_KEY ( &client_key, client_rundown );
    ERROR_FAIL(status,"Error creating tcp client context key" )
    /*
     * Initialize list of port numbers that will use SSL.
     */
    explicit_secure_ports = 0;		/* 0 means list is default list */
    client_certificate_db = (void *) 0;
    client_cert_msg[0] = '\0';
    LOCK_C_RTL
    secure_ports = malloc ( sizeof(struct port_enable) );
    UNLOCK_C_RTL
    secure_ports->next = (struct port_enable *) 0;
    secure_ports->port = 443;
    /*
     * Initialize list head of port contexts created.  The pointers in
     * this structure are protected by the ssl_ctl mutex.
     */
    tserver_tcp_port_list = &port_list;		/* set global pointer */
    client_list_alloc = 0;
    pthread_mutex_lock ( &ssl_ctl );
    port_list.flink = &port_list; port_list.blink = &port_list;
    port_list.port_num = 0;	/* flag list header */
    pthread_mutex_unlock ( &ssl_ctl );
    /*
     * Load real TCP routines from shareable image.
     */
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_declare_tcp_port,
	&tcp_declare_port, &image_dir );
    COND_TEST(status,"Error loading declare_port" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_write,
	&tcp_write, &image_dir );
    COND_TEST (status,"Error loading tcp_write " )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_read,
	&tcp_read, &image_dir );
    COND_TEST(status,"Error loading tcp_read" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_close,
	&tcp_close, &image_dir );
    COND_TEST(status,"Error loading tcp_close" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_stack_used,
	&tcp_stack_used, &image_dir );
    COND_TEST(status,"Error loading stack-used" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_tcp_info,
	&tcp_info, &image_dir );
    COND_TEST(status,"Error loading tcp_info" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_remote_host,
	&tcp_remote_host, &image_dir );
    COND_TEST(status,"Error loading remote_host" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_set_manage_port,
	&set_manage_port, &image_dir );
    COND_TEST (status,"Error loading set_manage_port" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_set_local_addr,
	&set_local_address, &image_dir );
    COND_TEST (status,"Error loading set_local_addr" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_set_logging,
	&set_logging, &image_dir );
    COND_TEST (status,"Error loading set_logging" )

    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_set_time_limit,
	&tcp_set_time_limit, &image_dir );
    COND_TEST (status,"Error loading set_time_limit" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_transaction_count,
	&tcp_transaction_count, &image_dir );
    COND_TEST (status,"Error loading set_loggin" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_end_transaction,
	&tcp_end_transaction, &image_dir );
    COND_TEST (status,"Error loading end_transaction" )
    status = LIB$FIND_IMAGE_SYMBOL ( &image_file, &sym_port_list,
	&transport_port_list, &image_dir );
    COND_TEST (status,"Error loading ts_tcp_port_list" )
    if ( (status&1) == 0 ) transport_port_list = &tserver_tcp_port_list;
}
static void ts_init_ssl ()
{
   int status;
    /*
     * Initialize SSL layer.
     */
    pseudo_env[pseudo_env_count] = (char *) 0;	/* terminate list */
    status = tssl_initialize ( report_ssl_error, pseudo_env );
}
/***************************************************************************/
/* Fill in the client_certificate data portion of the client context.
 * 
 */
static int get_certificate_info ( client_ctx ctx )
{
    int dlen, status;
    client_cert_info *info;
    unsigned char *work; void *sctx;
    /*
     * Call into SSL layer to get the certicates sent by the client, returned
     * In a special data structure as a list of DNs.  Note work address returned
     * points to temporary storage and becomes invalid at next tssl_* call.
     */
    status = tssl_get_client_cert ( ctx->ssl_ctx, 
		&dlen, (unsigned char **) &work );
    if ( (status&1) == 0 ) return status;

    info = client_cert_info_validate ( work, dlen );
    if ( !info ) {
	PUTLOG ( 0, 
	    "Invalid cert_info buffer (len=!SL) returned by get_client_cert!/",
		dlen );
	return SS$_ABORT;
    }
    if ( info->error != 0 ) {
	PUTLOG ( 0, "SSL engine had error processing certificate!/" );
	return SS$_ABORT;
    }
    certmap_set_var ( &ctx->subject, info->data, info->length[1],
		&info->data[info->length[1]], info->length[2] );
    certmap_set_var ( &ctx->issuer, &info->data[info->length[1]+info->length[2]],
	    info->length[3], (unsigned char *) "", 0 );
    /*
     * Convert the client's certificate to a list of VMS identifiers and
     * save in client context.  A special data file is used to hold the
     * mapping information.
     */
    sctx = (void *) 0;
    status = certmap_search ( client_certificate_db, &ctx->subject, &ctx->issuer,
			&sctx, &ctx->ccrec );
    ctx->id_count = 0;
    if ( (status&1) == 1 ) {
	/*
	 * Search successful, read remaining records with same key and
	 * make copy of identifiers found.
	 */
	ctx->cc_rec_num = ctx->ccrec.rec_num;
	ctx->cc_parent = ctx->ccrec.parent;
	do {
	    /* PUTLOG ( 1, "cert. maps to VMS identifier !XL!/",
		ctx->ccrec.identifier); */
	    if ( ctx->ccrec.identifier ) 
		ctx->identifiers[ctx->id_count++] = ctx->ccrec.identifier;
	    status = certmap_search ( client_certificate_db, &ctx->subject, 
		&ctx->issuer, &sctx, &ctx->ccrec );
	} while ( (status&1) && (ctx->id_count < 32) );
	/*
	 * Release resources used for search.
	 */
	certmap_search_end ( client_certificate_db, &sctx );
	status = 1;
     }
     return status;
}
/***************************************************************************/
/* Our internal routine for new client client connection.
 * Check type of connection, if non-secure, pass through to user's
 * start function, otherwise create a session.
 */
static int ts_startup_client ( void *tcp_ctx, int port_num,
	unsigned char *remote_address, int index, int remaining )
{
    int status, i, new_size, t_count, cc_status;
    port_ctx port;
    client_ctx ctx, *new_list;
    /*
     * Locate port in port list with matching port number.  Port structure
     * will tell us attributes such as secure/non-secure protocol in use.
     */
    pthread_mutex_lock ( &ssl_ctl );
    for (port=port_list.flink; port->port_num != port_num; port=port->flink) {
	if ( port->port_num == 0 ) {
    	    pthread_mutex_unlock ( &ssl_ctl );
	    PUTLOG ( 0, "Unknown port number: !SL!/", port_num );
	    return 0;
	}
    }
    /*
     * Keep per-connection context in structures addressed by connection
     * index number.
     */
    if ( index >= client_list_alloc ) {
	/* Expand array to include port number */
	new_size = index + 31;
	LOCK_C_RTL
	if ( client_list_alloc ) 
		new_list = realloc ( client_list, 
			new_size*sizeof(client_ctx *) );
	else
		new_list = malloc ( new_size*sizeof(client_ctx *) );
	UNLOCK_C_RTL
	for ( i = client_list_alloc; i < new_size; i++ )
	    new_list[i] = (client_ctx) 0;
	client_list_alloc = new_size;
	client_list = new_list;
    }
    ctx = client_list[index];
    if ( !ctx ) {
	/*
	 * Allocate new client block for slot.
	 */
	client_list[index] = malloc ( sizeof(client_ctx_struct) );
	ctx = client_list[index];
	PUTLOG(4,"Allocated new SSL context for index!/" );
    }
    pthread_mutex_unlock ( &ssl_ctl );
    /*
     * initialize the context structure.
     */
    ctx->secure = 0;
    ctx->cc_rec_num = 0;
    ctx->tcp_ctx = tcp_ctx;
    pthread_setspecific ( client_key, ctx );
    if ( port->secure ) {
	/* 
	 * Initialize the SSL data structures, including handshake with client
	 */
	t_count = (*tcp_transaction_count)(ctx->tcp_ctx);
	if ( t_count > 0 ) {
	    /*
	     * Look for existing session and inherit its attributes.
	     */
	    status = tssl_init_server_context ( &ctx->ssl_ctx,
		tcp_read, tcp_write, report_ssl_error, ctx->tcp_ctx,
		MAX_IO_SIZE );
	    if ( (status&1) == 0 ) return status;
	} else {
	    /* New session */
	    status = tssl_init_server_context ( &ctx->ssl_ctx,
		tcp_read, tcp_write, report_ssl_error, ctx->tcp_ctx,
		MAX_IO_SIZE );
	    if ( (status&1) == 0 ) return status;
	}
	ctx->app_data_pending = 0;
    	ctx->secure = port->secure;	/* mark connection as secure */
	/*
	 * Register certificate if enabled.  We have to call tcp_info
	 * function to get the remote port number.
	 */
	if ( client_certificate_db ) {
	    status = get_certificate_info ( ctx );
	}
    }
    /*
     * Call the actual start routine specified for this port.
     */
    status = (*port->start)(ctx, port_num, remote_address, 
		index, remaining );
    /*
     * Cleanup client certificate data.
     */
    ctx->cc_rec_num = 0;

    return status;
}
/***************************************************************************/
/* Return stack used, pass through to actual TCP layer.
 */
int ts_tcp_stack_used ()
{
    return (*tcp_stack_used)();
}
/***************************************************************************/
/*
 * Return information about connection, pass through to actual TCP layer
 * except for special case where remote address matches local_port.  Fo
 * the special case, treat the arguments as:
 *
 *    ts_tcp_info ( unsigned int *buffer, int *bufsize, unsigned int *alias );
 * 
 */
int ts_tcp_info ( int *local_port, int *remote_port, 
	unsigned int *remote_address )
{
    int status;
    if ( (local_port == (int *) remote_address) && remote_port ) {
	int bufsize, i;
	client_ctx ctx;
        /*
         * Return special structure:
	 *   buffer[0]		certificate map db record number.
	 *   buffer[1]		count of identifier (must be initialized to
	 *			-1 by caller).
	 *   buffer [2..n+2]	VMS identifiers.
	 *   
         */
	bufsize = (*remote_port)/sizeof(unsigned int);
	if ( (bufsize < 3) || (local_port[1] != -1) ) {
	    /* Sanity check on caller's arguments failed. */
	    return SS$_BADPARAM;
	}
	if ( !client_certificate_db ) { *local_port = 0; return 1; };
	
	GET_SPECIFIC ( client_key, ctx )
	if ( !ctx ) { *local_port = 0; return SS$_ABORT; }
	if ( !ctx->secure ) {
	    /*
	     * Non-secure port has no certificate data.
	     */
	    local_port[0] = 0;		/* No cert. map record. */
	    local_port[1] = 0;		/* no identifiers */
	    return 1;
	}
	local_port[0] = ctx->cc_rec_num;
	local_port[1] = ctx->id_count;
	/*
	 * Copy identifier list.
	 */
        for ( i = 0; i < ctx->id_count; i++ ) {
	    if ( i+2 >= bufsize ) break;	/* user's buffer full */
	    remote_address[i+2] = ctx->identifiers[i];
	}
	return 1;
	
    }
    status = (*tcp_info)( local_port, remote_port, remote_address );
    if ( status < 0 ) {
	if ( local_port ) *local_port = 0;
	if ( remote_port ) *remote_port = 0;
	if ( remote_address ) *remote_address = 0;
    }
    return status;
}
/***************************************************************************/
/* Don't support keepalive in  SSL.
 */
ts_tcp_transaction_count ( client_ctx ctx )
{
    return ctx->secure ? 0 : (*tcp_transaction_count) ( ctx->tcp_ctx );
}
ts_tcp_end_transaction ( client_ctx ctx ) 
{
    PUTLOG(4,"SSL end transaction called, ctx address: !XL!/", ctx );
    return ctx->secure ? -1 : (*tcp_end_transaction) ( ctx->tcp_ctx );
}
int ts_tcp_set_time_limit ( client_ctx ctx, int limit )
{
    /* if ( ctx->secure ) return 0; */	/* timeouts not supported */
    return (*tcp_set_time_limit) ( ctx->tcp_ctx, limit );
}
/***************************************************************************/
/* Define routine to return host name for current connection.  The return
 * value is a pointer to a statically allocated area that holds the host name
 * or a formatted representation of the host address.
 */
char *ts_tcp_remote_host ( )
{
    return (*tcp_remote_host)();
}
/***************************************************************************/

static void client_rundown ( client_ctx ctx )
{
    int status;
    client_ctx t;
    port_ctx parent;
    struct client_pool *pool;
    if ( !ctx ) return;
    /* printf("running down tcp client, index=%d\n", ctx->index ); */

    if ( ctx->secure ) {
	/*
	 * Do any teardown of SSL structures.
	 */
	tssl_rundown_context ( ctx->ssl_ctx, 0 );
	ctx->secure = 0;
    }
}
/***************************************************************************/
/* Define callback routine the SSL_SERVER layer will use to add error messages
 * to log file.  Error codes are logged at level 0, success codes at level 
 * code*3.
 */
static int report_ssl_error ( int code, char *text )
{
    int local_port, remote_port;
    unsigned char rem_a[32];
    (*tcp_info) ( &local_port, &remote_port, (unsigned int *) rem_a );

   PUTLOG ( (code&1) ? code&3 : 0,
	 "SSL-!UW/!UB.!UB.!UB.!UB.!UW !AZ: !SL - '!AZ'!/", 
	local_port, rem_a[0], rem_a[1], rem_a[2], rem_a[3], remote_port,
	(code&1) ? "status" : "error", code, text ? text : "" );
   return 1;
}
