/* This shareable image module provides an ISAPI interface via a
 * dynamically loaded MST-based scriptserver.  Use the following rules
 * in the configuration file to load this module and bind and an exec path
 * to it:
 *
 *    ThreadPool isapi stack=160000 q_flag=1 limit=8
 *    Service isapi pool=isapi dynamic=(isapi,isapi_mst) [info=threadsafe]
 *    Exec /isapi/* %ispai:www_root:[isapi]
 *
 * The ThreadPool rule defines a pool of MSTs (message-based service
 * threads) for servicing requests.  The service rule defines the
 * service 'testsrv', assigning it to pool 'test' and setting it's
 * MST start routine as 'isapi' in image isapi_mst (image is assumed
 * to be in directory WWW_SYSTEM).  The Exec rule makes it so that URL paths 
 * beginning with '/isapi/' will invoke the isapi scriptserver so that
 * is searches www_root:[isapi] for the DLL.
 *
 * Author: David Jones
 * Date:   21-SEP-1997
 * Revised: 6-OCT-1997		Fixed bug in isapi routine's ecb initialization.
 */
#include "pthread_1c_np.h"
#include <stdio.h>
#include <stdlib.h>
#include <ssdef.h>
#include <descrip.h>
#include "mst_share.h"
#include "tutil.h"
#include "ISAPI.h"
/*
 * Define global structures.
 */
static pthread_mutex_t client_lock;
static int threadsafe_dlls;
#define MAX_CLIENT_NDX 512
struct client_context {
    mst_link_t link;			/* I/O handle to server */
    int scriptserver_phase;		/* 0- dialog, 1 - output (CGI mode) */
    pthread_cond_t completion;
    int cond_valid;			/* true iff completion valid */
    int exit_state;			/* if true, delay thread exit */
    LPEXTENSION_CONTROL_BLOCK ecb;	/* argument to extension proc */
    struct mstshr_envbuf *env;		/* holds CGI variables */
} client[MAX_CLIENT_NDX];		/* array index matches MST's ndx */

typedef DWORD (*extproc) ( LPEXTENSION_CONTROL_BLOCK );
struct dll_item {
    struct dll_item *next;
    char name[256];
    char dir[256];
    extproc proc;
    int status;				/* Load status of entry */
} *dll_list;
/***************************************************************************/
/* Every dynamically loaded service must have an INIT routine, which is
 * called once during the processing of the Service rule to initialize
 * static structures needed by the MST.  By default the init routine
 * name is the start routine + the string _init, and 'init=name' clause
 * on the service rule will override the default.
 *
 * Arguments:
 *    mst_linkage vector  Structure containing addresses of essential
 *			items wee need from main program, such as
 *			address of putlog() routine.
 *
 *    char *info	Administrator provided argument (info=).
 *
 *    char *errmsg	Error text for log file when error status returned.
 */
int isapi_init ( mst_linkage vector, char *info, char *errmsg )
{
   int i;
   /*
    * Initialize mst_share module.
    * If mstshr_init fails, we can't proceed so return immediately.
    */
   if ( (1&mstshr_init ( vector, info, errmsg )) == 0 ) return 0;
   /*
    * examine info argument and set flag to indicate whether DLLs for this
    * service are assumed to be threadsafe.
    *
    */
   /*
    * Initialize global lists used to track which DLLs have been loaded and
    * client contexts.
    */
    INITIALIZE_MUTEX ( &client_lock );
    dll_list = (struct dll_item *) 0;
    for ( i = 0; i < MAX_CLIENT_NDX; i++ ) {
	client[i].cond_valid = 0;
	client[i].ecb = (LPEXTENSION_CONTROL_BLOCK) 0;
	client[i].env = (struct mstshr_envbuf *) 0;
    }
    /*
     * Interpret info argument for options.
     */
    threadsafe_dlls = 0;		/* not threadsafe by default */
    if ( 0 == tu_strncmp ( info, "threadsafe", 20 ) ) threadsafe_dlls = 1;
    /*
     * Return success status.
     */
    tu_strcpy ( errmsg, "testcgi scriptserver sucessfully initialized" );
    return 1;
}
/***************************************************************************/
/* Pthreads really discourages establishing your own condition handler,
 * but a TRY block around LIB$FIND_IMAGE_SYMBOL fails mysteriously, so
 * establish my own handler to catch all errors and ignore.
 */
int LIB$ESTABLISH();
static imgact_handler ( int *signal, int mech )
{
    return SS$_CONTINUE;
}
/**************************************************************************/
static int load_isapi ( char *script, char *bindir, extproc *proc )
{
    static $DESCRIPTOR(image,"");
    static $DESCRIPTOR(defdir,"");
    static $DESCRIPTOR(proc_symbol,"HttpExtensionProc");
    static $DESCRIPTOR(vers_symbol,"GetExtensionVersion");
    struct dll_item *dll;
    int status, LIB$FIND_IMAGE_SYMBOL(), LIB$REVERT(), i, dlen;
    char *dot;
    /*
     * Look in list of previously loaded DLLs
     */
    pthread_mutex_lock ( &client_lock );
    for ( dll = dll_list; dll; dll = dll->next ) {
	if ( 0 == tu_strncmp ( script, dll->name, 255 ) &&
	     0 == tu_strncmp ( bindir, dll->dir, 255 ) ) {
	    /* Found existing entry. */
	    *proc = dll->proc;
	    pthread_mutex_unlock ( &client_lock );
	    return dll->status;
	}
    }
    /*
     * Add new entry.
     */
    LOCK_C_RTL
    dll = (struct dll_item *) malloc ( sizeof(struct dll_item) );
    UNLOCK_C_RTL
    
    if ( dll ) {
	dll->next = dll_list;
	dll_list = dll;
	tu_strnzcpy ( dll->name, script, 255 );
	tu_strnzcpy ( dll->dir, bindir, 255 );
	dlen = tu_strlen ( dll->dir );
	for ( i = 0; dll->name[i]; i++ ) if ( dll->name[i] == '.' ) {
	    tu_strnzcpy ( &dll->dir[dlen], &dll->name[i], 255-dlen-1 );
	    break;
	}
	image.dsc$w_length = i;
	image.dsc$a_pointer = dll->name;
	defdir.dsc$w_length = tu_strlen ( dll->dir );
	defdir.dsc$a_pointer = dll->dir;

	pthread_lock_global_np();
	LIB$ESTABLISH ( imgact_handler );
	dll->status = LIB$FIND_IMAGE_SYMBOL ( &image, &proc_symbol,
		&dll->proc, &defdir );
	LIB$REVERT();
	pthread_unlock_global_np();
	if ( 1&dll->status ) {
	    /*
	     * Validate version.
	     */
	    BOOL (*getver) ( HSE_VERSION_INFO * );
	    HSE_VERSION_INFO version;
	    int major_id, minor_id;

	     *proc = dll->proc;
	    dll->status = LIB$FIND_IMAGE_SYMBOL ( &image, &vers_symbol,
		&getver, &defdir );
	    if ( 1&dll->status ) {
		status = (*getver) ( &version );
		if ( status ) {
		    major_id = version.dwExtensionVersion >> 16;
		    minor_id = version.dwExtensionVersion & 0x0ffff;
		    tlog_putlog(2,"Loaded DLL !AS, ISAPI version !UW.!UW!/",
			&image, major_id, minor_id );
		    if ( major_id != 1 ) dll->status = 8380;
		} else dll->status = 8380;
	    }
	}
	dll->dir[dlen] = '\0';
	status = dll->status;
    } else status = 292;
    pthread_mutex_unlock ( &client_lock );
    return status;
}
/**************************************************************************/
/* Callback routines.
 */
static BOOL get_server_variable ( HCONN connid, LPSTR varname,
	LPVOID buffer, LPDWORD size )
{
    struct client_context *ctx;
    char *value;
    int length, size_in;

    ctx = (struct client_context *) connid;
    value = mstshr_getenv ( varname, ctx->env );
    if ( http_log_level > 4 ) tlog_putlog(5,"ISAPI getvar: '!AZ' -> '!AZ'!/",
	varname, value ? value : "(NULL)" );
    size_in = *size;
    *size = 0;
    if ( !value ) {
	/*
	 * Do special fixups.
	 */
	if ( size_in > 0 ) tu_strcpy ( buffer, "" );
	if ( tu_strncmp ( varname, "COOKIE", 9 ) == 0 ) {
	    tu_strcpy ( buffer, "12345" );
	    *size = 5;
tlog_putlog(5,"Synthetic cookie returned!/");
	    return 1;
	} else if ( tu_strncmp ( varname, "ALL_HTTP", 9 ) == 0 ) {
	    /*
	     * Stupid hack added by microsoft, return all HTTP_* CGI variables
	     * as header lines separated by linefeeds.
	     */
	    int i, j, k, maxsize;
	    char *out, *hdr;
	    out = (char *) buffer;
	    maxsize = size_in-1;
	    for ( j = i = 0; i < ctx->env->count; i++ ) {
		if ( tu_strncmp ( ctx->env->ptr[i], "HTTP_", 5 ) ) continue;
		/*
		 * Copy variable name after the HTTP_, convert = to ": ".
		 */
		if ( j != 0 ) out[j++] = '\n';
		hdr = (ctx->env->ptr[i])+5;
		for ( k = 0; hdr[k] && hdr[k] != '='; k++ ) {
		    if ( j+1 >= maxsize ) return 0;	/* buffer overflow */
		    out[j++] = hdr[k];
		}
		if ( j + 2 >= maxsize ) return 0;
		out[j++] = ':'; out[j++] = ' ';
		if ( hdr[k] == '=' ) {
		   for ( k++; hdr[k]; k++ ) {
		        if ( j+1 >= maxsize ) return 0;	/* buffer overflow */
		        out[j++] = hdr[k];
		   }
		} 
		out[j] = '\0';
	    }
	    *size = j;
	    return 1;
	} else if ( tu_strncmp ( varname, "User-agent", 20 ) == 0 ) {
	    value = mstshr_getenv ( "HTTP_USER_AGENT", ctx->env );
	    if ( !value ) return 0;
	} else {
	    return 0;
	}
    }
    /*
     * Make special check for content-length.
     */
    if ( varname[0] == 'C' ) if ( 0 == tu_strncmp ( "CONTENT_LENGTH",
	varname, 20 ) ) {
	*size = sizeof ( DWORD );
	length = atoi ( value );
	*((DWORD *) buffer) = length;
	return 1;
    }
    length = tu_strlen ( value );
    if ( length+1 > size_in ) {
	return 0;
    }
    *size = length;
    tu_strcpy ( buffer, value );
    return 1;
}

static BOOL write_client ( HCONN connid, LPVOID buffer, LPDWORD bytes,
	DWORD reserved )
{
    struct client_context *ctx;
    int status, length;

    if ( http_log_level > 4 ) tlog_putlog(5,"ISAPI write, size: !SL!/", *bytes);

    ctx = (struct client_context *) connid;
    if ( ctx->scriptserver_phase == 0 ) {
	/* Error, header wasn't sent. */
	return 0;
    }
    /*
     * Segment writes.
     */
    status = mst_write ( ctx->link, buffer, *bytes, &length );
    *bytes = length;
    return 0;
}
static BOOL read_client ( HCONN connid, LPVOID buffer, LPDWORD bytes )
{
    struct client_context *ctx;
    int status, length;

    if ( http_log_level > 4 ) tlog_putlog(5,"ISAPI read, size: !SL!/", *bytes);

    ctx = (struct client_context *) connid;
    if ( ctx->scriptserver_phase == 1 ) {
	/* Error, header wasn't sent. */
	return 0;
    }
    status = mst_write ( ctx->link, "<DNETINPUT>", 11, &length );
    status = mst_read ( ctx->link, buffer, *bytes, &length );
    *bytes = length;
    return 0;
}
static BOOL server_support ( HCONN connid, DWORD request, LPVOID buffer,
	LPDWORD size, LPDWORD dtype )
{
    struct client_context *ctx;
    int i, length, status;
    char sts_line[80], *dbuf;

    ctx = (struct client_context *) connid;
    if ( http_log_level > 4 ) 
	tlog_putlog(5,"ISAPI support, code: !SL, bufadr: !XL, size: !SL !XL!/", 
		request, buffer, *size, dtype );
    switch ( request ) {
	case HSE_REQ_SEND_URL_REDIRECT_RESP:
	case HSE_REQ_SEND_URL:
	    if ( ctx->scriptserver_phase == 1 ) { return 0; }
	    mst_write ( ctx->link, "<DNETCGI>", 9, &length );
	    ctx->scriptserver_phase = 1;
	    mst_write ( ctx->link, "Location: ", 10, &length );
	    mst_write ( ctx->link, buffer, *size, &length );
	    mst_write ( ctx->link, "\r\n\r\n", 4, &length );
	    break;

	case HSE_REQ_SEND_RESPONSE_HEADER:
	    if ( ctx->scriptserver_phase == 1 ) { return 0; }
	    mst_write ( ctx->link, "<DNETCGI>", 9, &length );
	    ctx->scriptserver_phase = 1;
	    if ( buffer ) {
		/* Caller is supplying status line. */
		status = mst_write ( ctx->link, "Status: ", 8, &length );
		status = mst_write ( ctx->link, buffer, tu_strlen(buffer),
			&length );
		status = mst_write ( ctx->link, "\r\n", 2, &length );
	    } else {
	        tu_strcpy ( sts_line, "Status: " );
	        tu_strint ( ctx->ecb->dwHttpStatusCode, &sts_line[8] );
	        i = tu_strlen ( sts_line );
	        tu_strcpy ( &sts_line[i], " ISAPI output follows\r\n" ); 
		i += 23;
		if ( *size > 0 && dtype ) {
		    /* Fixup broken buffers by appending to status line. */
		    dbuf = (char *) dtype;
		    if ( (*dbuf >= '0') && (*dbuf <= '9') ) i = i - 2;
		}
	        status = mst_write ( ctx->link, sts_line, i, &length );
	    }
	    if ( *size > 0 && dtype ) {
	        status = mst_write ( ctx->link, (char *) dtype, *size, 
			&length );
    if ( http_log_level > 4 ) {
	tlog_putlog(5,"ISAPI support write status: !SL", status );
	tlog_putlog(5," '!AF'!/", *size, dtype );
    }
	    } else {
		status = mst_write ( ctx->link, "\r\n", 2, &length );
	    }
	    return (status&1);

	case HSE_REQ_DONE_WITH_SESSION:
	    /*
	     * Signal.
	     */
	    pthread_mutex_lock ( &client_lock );
	    ctx->exit_state = 1;		/* final exit */
	    if ( ctx->cond_valid ) pthread_cond_signal ( &ctx->completion );
	    pthread_mutex_unlock ( &client_lock );
	    return 1;
	default:
	    /*
	     * Unsupported request
	     */
	     return 0;
    }
    return 0;
}
/***************************************************************************/
/*
 * Place link in text mode and and send error message.
 */
void abort_connection ( mst_link_t link, char *sts_line, char *err_text )
{
    int status, length;
    status = mst_write ( link, "<DNETTEXT>", 10, &length );
    if ( (status &1) == 0 ) return;
    status = mst_write ( link, sts_line, tu_strlen(sts_line), &length );
    if ( (status &1) == 0 ) return;
    status = mst_write ( link, err_text, tu_strlen(err_text), &length );
    if ( (status &1) == 0 ) return;
    status = mst_write ( link, "</DNETTEXT>", 11, &length );
}
/***************************************************************************/
/* Main routine to handle client requests.  To the server, this routine
 * must behave like a DECnet scriptserver task (e.g. WWWEXEC) only messages 
 * are transferred via mst_read() and mst_write() rather than $QIO's to a 
 * logical link.
 *
 * Arguments:
 *    mst_link_t link	Connection structure used by mst_read(), mst_write().
 *
 *    char *service	Service name (for logging purposes).
 *
 *    char *info	Script-directory argument from 'exec' rule that
 *			triggered the MST (exec /path/* %service:info).
 *
 *    int ndx		Service thread index, all services sharing same pool
 *			share the same thread index numbers.
 *
 *    int avail		Number of remaining threads in pool assigned to service.
 */
int isapi ( mst_link_t link, char *service, char *info, int ndx, int avail )
{
    int i, status, length;
    char line[512], *script_name, *clstr;
    struct mstshr_envbuf *env;
    LPEXTENSION_CONTROL_BLOCK ecb;
    DWORD (*extensionproc) ( LPEXTENSION_CONTROL_BLOCK );
    /*
     * Log that we began execution
     */
    if ( http_log_level > 0 ) tlog_putlog ( 1, 
	"Service!AZ/!SL connected, info: '!AZ', pool remaining: !SL!/", 
	service, ndx, info, avail );
    /*
     * Allocate global context for callbacks.
     */
    if ( ndx >= MAX_CLIENT_NDX || ndx < 0 ) {
	return 0;			/* ndx out of range */
    }
    client[ndx].link = link;
    client[ndx].scriptserver_phase = 0;
    if ( !client[ndx].ecb ) {
	LOCK_C_RTL
	client[ndx].ecb = (LPEXTENSION_CONTROL_BLOCK) malloc ( 
		sizeof(EXTENSION_CONTROL_BLOCK) + (48 * 1024) );
	UNLOCK_C_RTL
	if ( !client[ndx].ecb ) {
	    abort_connection ( link, "500 Insufficient resources",
		"Allocation failure on server" );
	    return 0;
	}
    }
    ecb = client[ndx].ecb;
    if ( !client[ndx].env ) {
	LOCK_C_RTL
	client[ndx].env = (struct mstshr_envbuf *) malloc ( 
		sizeof (struct mstshr_envbuf) );
	UNLOCK_C_RTL
	if ( !client[ndx].env ) {
	    abort_connection ( link, "500 Insufficient resources",
		"Allocation failure on server" );
	    return 0;
	}
    }
    env = client[ndx].env;
    /*
     * Setup cgi environment (reads prologue as a consequence).
     */
    status = mstshr_cgi_symbols ( link, info, env );
    if ( (status &1) == 0 ) return 0;
    /*
     * Get the BINDIR directory and dynamically load the ISAPI shareable
     */
    status = mst_write ( link, "<DNETBINDIR>", 12, &length );
    if ( (status &1) == 0 ) return 0;
    else {
	char isapi_dir[256], *iname;
        status = mst_read ( link, isapi_dir, sizeof(isapi_dir), &length );
        if ( (status &1) == 0 ) return 0;
        isapi_dir[length] = '\0';
	script_name = mstshr_getenv ( "SCRIPT_NAME", env );
	for ( iname = script_name; *script_name; script_name++ )
	    if ( *script_name == '/' ) iname = &script_name[1];
        status = load_isapi ( iname, isapi_dir, &extensionproc );
	if ( (status&1) == 0 ) {
	    /* Error loading DLL */
	    abort_connection ( link, "400 Error loading DLL",
		"Invalid DLL specified" );
	    return 0;
	}
    }
    /*
     * Build extension control block
     */
    ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
    ecb->dwVersion = HSE_VERSION_MAJOR;
    ecb->ConnID = &client[ndx];
    ecb->dwHttpStatusCode = 200;
    ecb->lpszLogData[0] = '\0';		/* log data */
    ecb->lpszMethod = mstshr_getenv ( "METHOD", env );
    ecb->lpszQueryString = mstshr_getenv ( "QUERY_STRING", env );
    ecb->lpszPathInfo = mstshr_getenv ( "PATH_INFO", env );
    ecb->lpszPathTranslated = mstshr_getenv ( "PATH_TRANSLATED", env );
    ecb->cbTotalBytes = 0;
    clstr = mstshr_getenv ( "CONTENT_LENGTH", env );
    if ( clstr ) ecb->cbTotalBytes = atoi ( clstr );
    ecb->lpbData = (LPBYTE) &ecb[1];	/* Initial buffer for request content */
    ecb->cbAvailable = 0;
    ecb->GetServerVariable = get_server_variable;
    ecb->WriteClient = write_client;
    ecb->ReadClient = read_client;
    ecb->ServerSupportFunction = server_support;
    /*
     * read up to first 48 of content.
     */
    if ( ecb->cbTotalBytes ) {
	for ( i = 0; (i < ecb->cbTotalBytes) && i < (48*1024); i += length ) {
	    status = mst_write ( link, "<DNETINPUT>", 11, &length );
	    if ( (status&1) == 0 ) return 0;
	    status = mst_read ( link, &ecb->lpbData[i],
		(i < (48*1024-256)) ? 256 : (48*1024) - i, &length );
	    if ( (status&1) == 0 ) return 0;
	}
    }
    /*
     * Call the extension.
     */
    pthread_mutex_lock ( &client_lock );
    client[ndx].exit_state = 0;
    if ( threadsafe_dlls ) pthread_mutex_unlock ( &client_lock );
    status = (*extensionproc) ( ecb );
    switch ( status ) {
	case HSE_STATUS_SUCCESS:
	case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
	    break;
	case HSE_STATUS_PENDING:
	    if ( threadsafe_dlls ) pthread_mutex_lock ( &client_lock );
	    if ( !client[ndx].cond_valid ) {
		/* Note the we have mutex, so there is no race with callback */
		INITIALIZE_CONDITION ( &client[ndx].completion );
		client[ndx].cond_valid = 1;
	    }
	    if ( client[ndx].exit_state == 0 ) client[ndx].exit_state = -1;
	    while ( client[ndx].exit_state <= 0 ) {
		pthread_cond_wait ( &client[ndx].completion, &client_lock );
	    }
	    break;
	case HSE_STATUS_ERROR:
	default:
	    break;
    }
    if ( !threadsafe_dlls ) pthread_mutex_unlock ( &client_lock );
    /*
     * Place connection in CGI mode.
     */
    if ( client[ndx].scriptserver_phase == 0 ) {
	/* extension called produced no output, produce diagnositic mess ate */
	abort_connection ( link, "500 DLL produced no output", 
		"No output produced by ISAPI DLL" );
    } else {
        status = mst_write ( link, "</DNETCGI>", 10, &length );
    }
    mst_close ( link );
    
    return 1;
}
