/*
 * This program implements a DECnet persistent object that functions as
 * a proxy gateway for the OSU decthreads server.
 *
 * Required logical names:
 *    WWW_PROXY_OBJECT		Translates to DECnet object name to use (e.g.
 *				WWWPROXY), must	be defined /system/exec.
 *    WWW_PROXY_ACCESSS		Multi-valued logical name containing list
 *				of node::username pairs that are to use the
 *				gateway.
 *    WWW_PROXY_LOCALHOSTS	Multi-valued logical name containing names
 *				of local server.  Include port number if
 *				applicable.
 *    WWW_PROXY_LOG		Translates to log file name, must include
 *				device specification.
 *    WWW_PROXY_LOG_LEVEL	Trace level for logging.
 *    WWW_PROXY_CLIENT_LIMIT	Maximum number of concurrent clients allowed.
 *    WWW_PROXY_TARGET_TIMEOUT	Number of seconds to allow for remote
 *				transaction.
 *
 * Web server configuration rules:
 *    ProxyScript /proxy_gateway/
 *    exec /proxy_gateway/ 0::"0=WWWPROXY"
 *
 * Author:	David Jones
 * Date:	19-FEB-1998
 * Revised:	28-mar-1998		use <DNETCGI> instead of <DNETRAW>
 */
#include "../base_code/pthread_1c_np.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <descrip.h>
#include <lnmdef.h>			/* logical name symbols */
#include "../base_code/session.h"
#include "../base_code/tmemory.h"
#include "../base_code/http_header.h"
#ifdef IS_MST
#define NET_WRITE mst_write
#define NET_READ mst_read
#define NET_CLOSE mst_close
#define NET_CONNECT_INFO_T char
#include "../base_code/mst_share.h"
#else
#define NET_WRITE ts_decnet_write
#define NET_READ ts_decnet_read
#define NET_CLOSE ts_decnet_close
#define NET_CONNECT_INFO_T struct ncbdef
#include <prvdef.h>
#include <jpidef.h>
#include <syidef.h>
#define A(v) (lval=v,&lval)
#include "../base_code/tserver_decnet.h"
#endif
#include "../base_code/file_access.h"

char *proxy_localhosts[256];
char proxy_target_time_limit;		/* seconds, 0 --> no time limit */

int tlog_init ( char * );
int tlog_putlog ( int, char *, ... );
int tlog_initlog ( int, char * );
int (*tlog_putlog_cb) ( int level, char * ctlstr, ... );
int http_log_level;

int sys$setprv();

static int proxy_request ( void *ctx, NET_CONNECT_INFO_T *cnx_info, 
	int ndx, int available );
static long pc_mask;

typedef int proxy_handler ( session_ctx scb, 	/* session control block */
	string * content,			/* request content data */
	char *host,				/* target host */
	char *path );				/* target path */

typedef int host_parse ( char *, char **, char ** );

host_parse http_proxy_host_parse;
proxy_handler http_proxy_handler;

struct {
    char *name;
    host_parse *parse;
    proxy_handler *execute;
} handler_list[] = {
    { "HTTP", &http_proxy_host_parse, &http_proxy_handler },
    { "FTP", (host_parse *) 0, (proxy_handler *) 0 },
    { "GOPHER", (host_parse *) 0, (proxy_handler *) 0 },
    { (char *) 0, (host_parse *) 0, (proxy_handler *) 0 }
};

/******************************************************************************/
/* Initalize proxy_localhosts array by translating WWW_PROXY_LOCALHOSTS
 * logical name.
 */
static int get_localhosts ( )
{
    int status, SYS$TRNLNM(), flags, length, index, i, j, max_index;
    struct { short length, code; void *buf; void *retlen; } item[4];
    static $DESCRIPTOR(table_name,"LNM$FILE_DEV");
    static $DESCRIPTOR(log_name,"WWW_PROXY_LOCALHOSTS");
    char string[256];
    /*
     * Set up item list to retrieve first equivalence string and max
     * index number.
     */
    flags = LNM$M_CASE_BLIND;
    length = 0;
    item[0].length = sizeof(int); item[0].code = LNM$_INDEX;
    item[0].buf = &index; item[0].retlen = (void *) 0;

    item[1].length = sizeof(string)-1; item[1].code = LNM$_STRING;
    item[1].buf = string; item[1].retlen = &length;

    item[2].length = sizeof(int); item[2].code = LNM$_MAX_INDEX;
    item[2].buf = &max_index; item[2].retlen = (void *) 0;

    item[3].length = item[3].code = 0;		/* terminate list */
    max_index = 0;
    /*
     * Call TRNLNM for every equivalence name.
     */
    for ( index = j = 0; index <= max_index; index++ ) {
	status = SYS$TRNLNM ( &flags, &table_name, &log_name, 0, item );
	if ( (status&1) == 0 ) break;

	if ( length > 0 ) {
	    string[length] = '\0';
	    proxy_localhosts[j] = malloc ( length+1 );
	    tu_strupcase ( proxy_localhosts[j], string );
	    j++;
	}
        item[2].length = item[2].code = 0;	/* new  list termination */
    }
    proxy_localhosts[j] = "";			/* terminate list */
    return status;
}
/******************************************************************************/
#ifdef IS_MST
#else
/**************************************************************************/
/* Return 1 if process uic is less than sysgen parameter. */
static int is_sysgrp()
{
    int uic_grp, maxsysgrp, status, lval, LIB$GETJPI(), LIB$GETSYI();

    status = LIB$GETJPI ( A(JPI$_GRP), 0, 0, &uic_grp );
    if ( (status&1) == 0 ) printf ( "Error in getjpi: %d\n", status );

    status = LIB$GETSYI ( A(SYI$_MAXSYSGROUP), &maxsysgrp );
    if ( (status&1) == 0 ) printf ( "Error in getsyi: %d\n", status );
    return (uic_grp <= maxsysgrp ) ? 1 : 0;
}
/*****************************************************************************/
/* General routine to send final terminating tag to server and perform final
 * handshake.  If server sends <DNETREUSE>, keep connection open, otherwise
 * close network connection and zero cnx field in SCB.
 */
int http_finish_request ( session_ctx scb, char *end_tag )
{
    int status, length;
    char dummy[128];

    if ( !scb->cnx ) return 0;
    status = NET_WRITE ( scb->cnx, end_tag, tu_strlen(end_tag) );
    if ( (status&1) == 1 ) {
	/*
	 * Read last record from server to ensure connection complete.
	 */
	status = NET_READ ( scb->cnx, dummy, sizeof(dummy), &length );
	if ( (status&1) == 1 ) {
	    if ( (length==11) && (tu_strncmp(dummy,"<DNETREUSE>",11)==0) )
		return status;
	}
    }
    /*
     * Not reusing connection, closedown.
     */
    status = NET_CLOSE ( scb->cnx );
    scb->cnx = (void *) 0;
    return status;
}
/*****************************************************************************/
/* General routine to complete request with error message.
 */
int http_proxy_abort ( session_ctx scb, char *sts_line, char *message )
{
    int status, length;
    char dummy[64];

    status = NET_WRITE ( scb->cnx, "<DNETTEXT>", 10 );
    if ( (status&1) == 0 ) return status;
    status = NET_WRITE ( scb->cnx, sts_line, tu_strlen ( sts_line ) );
    if ( (status&1) == 0 ) return status;
    status = NET_WRITE(scb->cnx, "Error encountered by proxy server\r\n", 35);
    if ( (status&1) == 0 ) return status;
    status = NET_WRITE ( scb->cnx, message, tu_strlen ( message ) );
    if ( (status&1) == 0 ) return status;
    status = http_finish_request ( scb, "</DNETTEXT>" );
    return status;
}
/*****************************************************************************/
int main ( int argc, char **argv )
{
    int server_mode, status, client_limit, exit_status;
    char *level, taskname[100];
    long priv_mask[2], prev_priv[2];
    pthread_t listener_thread;
    pthread_attr_t client_attr;
    /*
     * Disable sysnam privilege and initialize low-level components
     */
    priv_mask[0] = PRV$M_SYSNAM; priv_mask[1] = 0;
    status = sys$setprv ( 0, priv_mask, 0, prev_priv );
    printf("disable SYSNAM status: %d, prev set: %d\n", status,
	(prev_priv[0]&PRV$M_SYSNAM) ? 1 : 0 );
    if ( is_sysgrp() ) printf 
	( "Warning, system group id gives implicit SYSPRV\n" );

    status = tlog_init ( "WWW_PROXY_LOG" );
    tlog_putlog_cb = &tlog_putlog;
    tf_initialize("");			/* file access setup */
    tm_initialize();			/* per-thread heaps */
    status = http_init_standard_atoms();	/* header parsing support */
    status = http_create_atom ( "proxy-connection", &pc_mask );

    ts_set_logging ( tlog_putlog );
    /*
     * set other parameters by logical names.
     */
    server_mode = ts_get_taskname 
	( "WWW_PROXY_OBJECT", taskname,	sizeof(taskname) );

    status = ts_set_access ( "WWW_PROXY_ACCESS" );

    status = get_localhosts ( );
    if ( (status&1) == 0 ) return status;
    
    level = getenv ( "WWW_PROXY_LOG_LEVEL" );
    if ( level ) http_log_level = atoi ( level ); else http_log_level = 0;
    if ( http_log_level > 0 ) {
	status = tlog_initlog ( http_log_level, "" );
    }
    client_limit = 32;
    level = getenv ( "WWW_PROXY_CLIENT_LIMIT" );
    if ( level ) client_limit = atoi ( level );
    proxy_target_time_limit = 600;		/* 10 minutes */
    level = getenv ( "WWW_PROXY_TARGET_TIMEOUT" );
    if ( level ) proxy_target_time_limit = atoi ( level );

    tlog_putlog(0,
	"OSU/Decthreads proxy server prototype, V0.1, client limit: !SL!/",
	client_limit );
    /*
     * Create listener thread that listens for decnet object, must enable
     * privileges to declare object.
     */
    INITIALIZE_THREAD_ATTR ( &client_attr );
    pthread_attr_setinheritsched ( &client_attr, PTHREAD_EXPLICIT_SCHED );
    pthread_attr_setstacksize ( &client_attr, 62000 );
    status = sys$setprv ( 1, priv_mask, 0, 0 );

    status = ts_declare_decnet_object ( taskname, client_limit, 
		&client_attr, &listener_thread, proxy_request );
    (void) sys$setprv ( 0, priv_mask, 0, 0 );

    tlog_putlog(0,"Status of declare object (!AZ) = !SL!/", taskname, status);
    /*
     * Wait for DECnet communication thread to rundown.
     */
    if ( (status&1) == 1 ) {
	status = pthread_join ( listener_thread, (void *) &exit_status );
    }
    else exit_status = status;
    if ( server_mode ) tlog_putlog 
		( 0, "!/listener thread exit status: !SL!/", exit_status );
    if ( status == 0 ) return exit_status;
    else return status;
    
}
#endif
/*****************************************************************************/
static int get_http_request ( session_ctx scb, char *hdrbuf, int hb_size,
	string *content )
{
    string *hdr;
    char *content_ptr, cl_str[64];
    int used, status, i, length, content_length;
    /*
     * read request headers, storing in caller-supplied buffer.
     */
    status = NET_WRITE ( scb->cnx, "<DNETHDR>", 9 );
    used = 0;
    hdr = scb->request;
    for ( i = 3; (status&1); i++ ) {
	status = NET_READ ( scb->cnx, &hdrbuf[used], hb_size-used-1, &length );
	if ( (status&1) == 0 ) break;
	if ( http_log_level > 4 ) tlog_putlog(5,
		"!AZ - header line read: '!AF'!/", scb->log_prefix, length,
		&hdrbuf[used] );
	if ( length == 0 ) break;
	if ( i < 127 ) {
	    hdr[i].l = length;
	    hdr[i].s = &hdrbuf[used];
	    hdr[i].s[length] = '\0';
	    used += length + 1;
	} else {
	    hdr[127].l = 0;
	    hdr[127].s = "";
	}
    }
    if ( (status&1) == 0 ) return status;
    if ( i < 128 ) {
	hdr[i].l = 0; hdr[i].s = "";
    }

    status = http_summarize_headers ( scb );
    /*
     * Read content, allocating storage from a per-thread heap (automatically
     * deallocated at thread exit.
     */
    i = http_extract_header ( http_std_atoms.content_length, scb,
	cl_str, sizeof(cl_str)-1, &length );
    if ( (i == 1) && (length > 0) ) {
	/*
	 * Decode string and check if within bounds.
	 */
	cl_str[length] = '\0';
	content_length = atoi ( cl_str );
	if ( http_log_level > 4 ) tlog_putlog ( 5, 
		"!AZ - Decoded content-length '!AZ' as !SL!/", scb->log_prefix,
		cl_str, content_length );
	if ( content_length < 0 || content_length > 32000 ) {
	    /* Out of range, disallow */
	   status = http_proxy_abort ( scb, "413 too much content",
		"Too much content included with request" );
	}
	/*
	 * Allocate string to store content, include 256 chars slop to
	 * accommodate clients that send more data than content-length says.
	 */
	content_ptr = tm_malloc ( content_length + 256 );
	if ( !content_ptr ) {
	    status = http_proxy_abort ( scb, "500 allocation failure",
		"Could not allocate memory for request content" );
	    return status;
	}
	/*
	 * Query server for content, server writes to us in no more than
	 * 255 byte chunks (to allow DCL scripts to read posted data).
	 */
	content->l = content_length;
	content->s = content_ptr;
	for ( i = 0; i < content_length; i += length ) {
	    status = NET_WRITE ( scb->cnx, "<DNETINPUT>", 11 );
	    if ( (status&1) == 0 ) return status;
	    status = NET_READ ( scb->cnx, &content_ptr[i], 256, &length );
	    if ( (status&1) == 0 ) return status;
	}
	if ( i > content_length && (http_log_level > 4) ) tlog_putlog ( 5,
		"!AZ - Excess content data: !SL byte!%S!/", scb->log_prefix,
		i - content_length );
    } else {
	content->l = -1;		/* no content */
	content->s = "";
    }
    return 1;
}
/*****************************************************************************/
/* Top level routine for handling a connection from the server.
 */
static int proxy_request
	( void *ctx, NET_CONNECT_INFO_T *cnx_info, int ndx, int available )
{
    int length, status, used, i, content_length, tf_len, j, p_ndx;
    int header_only;
    string prologue[4];
    struct session_control_block scb;
    struct acc_info_struct acc;
    struct tu_textbuf rsphdr;
    struct tu_streambuf inbound;
    string content, hdr[128];
    int (*handler) ( session_ctx, string *, char *, char * );

    char  *scheme, *url, *full_name, *tail, prolog_buf[1104], hdrbuf[8192];
    char *host;
    char *bp, *opcode, *method, *protocol, *module, log_prefix[32];
    /*
     * Make prefix string for error messaages to identify thread.
     */
    tu_strcpy ( log_prefix, "PROXY/" );
    tu_strint ( ndx, &log_prefix[6] );
    if ( http_log_level > 1 ) tlog_putlog ( 1, "!AZ - Connected at !%D!/", 
		log_prefix, 0 );
    /*
     * outermost loop, keep processing until scb closed down.
     */
    for ( scb.cnx = ctx; scb.cnx; ) {
    /*                   0      1        2        3
     * Read prologue (module, method, protocol, ident) sent by HTTP server.
     */
    for ( i = 0, bp = prolog_buf; i < 4; i++ ) {
	status = ts_decnet_read ( ctx, bp, 255, &length );
	if ( (status&1) == 1 ) {
	    if ( (length == 11) && (tu_strncmp(bp,"<DNETREUSE>",11)==0) ) {
		i = i - 1;
		continue; 		/* Ignore extra <DNETREUSE> tags */
	    }
	    prologue[i].l = length;
	    prologue[i].s = bp;
	    bp[length++] = '\0';	/* safety first, terminate string */
	    bp = &bp[length];
	} else {
	    tlog_putlog ( 0, "!AZ, Error reading prologue: !SL, at !%D!/", 
			log_prefix, status, 0 );
	    return status;
	}
    }
    /*
     * Build session control block. and parse header lines.
     */
    hdr[0] = prologue[1];
    hdr[1] = prologue[3];
    hdr[2] = prologue[2];
    hdr[3].l = -1;		/* mark end of headers */
    scb.request = hdr;
    scb.inbound = &inbound;
    tu_init_stream ( ctx, NET_READ, scb.inbound );
    scb.rsphdr = &rsphdr;
    scb.data_bytes = 0;
    scb.acc = &acc;
    scb.log_prefix = log_prefix;
    scb.completed = 0;
    scb.recursion_level = 0;
    scb.accesslog = -1;
    scb.keepalive_limit = 0;
    scb.keepalive_count = 0;
    scb.keepalive_pending = 0;
    scb.req_summary = 0;
    acc.uic = 0;
    acc.cache_allowed = 1;
    acc.prot_file = "";
    acc.rem_user[0] = acc.user[0] = '\0';
    acc.local_address = "???";
    acc.port_attr = "+http";
    /*
     * Get original URL and parse out scheme and host.  Store result in hdrbuf
     */
    status = NET_WRITE ( ctx, "<DNETREUSE>", 11 );
    if ( (status&1) == 0 ) return status;
    status = NET_WRITE ( ctx, "<DNETRQURL>", 11 );
    if ( (status&1) == 0 ) return status;
    status = NET_READ ( ctx, hdrbuf, sizeof(hdrbuf), &length );
    if ( (status&1) == 0 ) return status;
    hdrbuf[length] = '\0';
    used = length + 1;
    url = scheme = (char *) 0;
    for ( i = 0; i < length; i++ ) if ( hdrbuf[i] == ':' ) {
	hdrbuf[i] = '\0';
	scheme = hdrbuf;
	url = &hdrbuf[i+1];
	tu_strupcase ( scheme, scheme );
	break;
    }
    if ( !scheme ) {		/* syntax error */
	status = http_proxy_abort ( &scb, "400 Invalid URL syntax",
		"Invalid URL syntax" );
	return status;
    }
    /*
     * do scheme-specific parsing on URL.
     */
    for ( p_ndx = 0; handler_list[p_ndx].name; p_ndx++ ) {
	if ( tu_strncmp ( scheme, handler_list[p_ndx].name, 100 ) == 0 ) break;
    }
    if ( !handler_list[p_ndx].name ) {
	/*
	 * Unrecogized protocol in URL.
	 */
	status = http_proxy_abort ( &scb, "501 Unrecogized protocol",
		"Protocol in request URL is unknown" );
	return status;
    } else if ( !handler_list[p_ndx].execute ) {
	/*
	 * No handler available.
	 */
	status = http_proxy_abort ( &scb, "501 missing handler",
		"No handler loaded for protocol request in URL" );
	return status;
    }
    status = (*handler_list[p_ndx].parse) ( url, &host, &url );
    if ( status == 0 ) {
	/* Error parsing out host name */
	status = http_proxy_abort ( &scb, "400 syntax error in URL",
		"URL syntax is invalid" );
	return status;
    } else if ( status == -1 ) {
	status = http_proxy_abort ( &scb, "500 memory allocation failure",
		"Memory allocation failure." );
	return status;
    }
    /*
     * download request headers and content from web server.
     */
    status = get_http_request ( &scb, &hdrbuf[used], sizeof(hdrbuf)-used,
		&content );
    if ( http_log_level > 1 ) tlog_putlog ( 2,
		"!AZ - request status: !SL!/", scb.log_prefix, status );
    if ( (status&1) == 0 ) return status;
    /*
     * Hand off to scheme-specific handler.
     */
    status = (*handler_list[p_ndx].execute) ( &scb, &content, host, url );
    }  /* Outer loop */
    return status;
}
