/*
 * The script_execute routine handles the interaction of the HTTP server with
 * the WWWEXEC decnet object.
 *
 * int http_script_execute ( session_ctx cnx, char *subfunct,
 *		char *ident, char *arg, string *iobuf );
 *
 * Revised:  6-APR-1994		Add support for exec directives and <DNETPATH>
 * Revised:  5-MAY-1994		Support node:: prefix on bin directory
 *				(Sugessted by Robin Garner, torobin@svh.unsw.edu.au)
 * Revised:   28-MAY-1994	Added INVCACHE option and fixed bug in
 *				tag_list processing.
 * reviesd:   28-JUN-1994	Fixed operation of CGI mode not hang when
 *				script sends invalid response (</DNETCGI>
 *				was not being checked for when reading header.
 * Revised:    7-JUL-1994	Add access check.
 * Revised:	1-AUG-1994	Support alternate object for WWWEXEC.
 * Revised:     7-AUG-1994	Fix problem with DNETRAW being treated as
 *				producing no output.
 * Revised:	11-AUG-1994	Fix DNETRAW so first data message goes into
 *				response header buffer for logging.
 * Revised:	1-SEP-1994	Fix bad maxlen in tu_read_line call.
 * Revised:	10-SEP-1994	Include version string.
 * Revised:	15-SEP-1994	Add more error checks to CGI processing.
 * Revised:	10-OCT-1994	Add <DNETRECMODE> tag.
 * Revised:     18-OCT-1994	Add <DNETID2> tag.
 * Revised:     17-NOV-1994	Fixed bug in flushing of linefeed following
 *				CR in CGI mode.
 * Revised:	4-JAN-1995	pass along unrecognized headers to client
 *				when in CGI mode.
 * Revised:     19-JAN-1995	Correct calculation of byte count on
 *				stream buffer flush.
 * Revised:	16-MAR-1995	Do transfer on CGI relocates.
 * Revised:	24-MAR-1995	Add builtin mapimage.
 * Revised:	31-MAR-1995	Play it safe, use "Location:" over "location:"
 * Revised:	26-APR-1995	Support search args in local CGI redirects.
 * Revised:	27-APR-1995	Recursively call script_execute.
 * Revised:	28-APR-1995	Close current link before recursive calls,
 *				initialize dummy acc for re-direct translates.
 * Revised:	14-MAY-1995	Add XLATEV to honor document protection.
 * Revised:	14-JUN-1995	Fix bug in XLATEV, use acc struct from translate
 *				when doing protection check.
 * Revised:	16-JUl-1995	Support multihomed hostnames.
 * Revised:	30-SEP-1995	Fix bug in XLATEV, remove response header added
 *				bye check_protection call.
 * Revised:	10-NOV-1995	Return error code on connect failure.
 * Revised:	20-JUN-1996	Relax ordering restrictions on CGI mode header
 *				lines.  If script-supplied headers specified,
 *				status: line will be truncated or padded to
 *				to 35 characters.
 * Revised:	2-AUG-1996	Add <DNETMANAGE> support.
 * Revised:	4-DEC-1996	Support http_crlf_newline flag in <DNETRECMODE>
 * Revised:	19-MAY-1997	Support http_script_time limits.
 * Revised:	20-AUG-1997	Allow extra asterisks in path.
 * Revised:	6-SEP-1997	Allow virtual paths for <DNETXLATE>.
 * Revised:	29-JAN-1998	Don't call time limit functions when script
 *				is an MST.
 * Revsied:	19-FEB-1998	Initiali port_attr field in acc struct on
 *				redirects.
 * Revised:	22-FEB-1998	Added hack to cgimode relocates, converts
 *				*://*:* to scheme://host[:port]/ of current
 *				connection.
 * Revsed:	8-MAR-1998	RESUE support.
 * Revised:	14-MAR-1998	Split CGI processing to separate module.
 * REvsied:	18-JUN-1998	fix uninitialized link.dptr problem.
 * Revised:	13-AUG-1998	Fix bug in using record mode with DNETRAW.
 * Revised:	27-AUG-1998	Prevent buffer overflow in transfer_output.
 * Revised:	5-DEC-1998	Use dnet_read_streamed unless record mode set.
 * Revised:	28-JUL-1999	Change http_send_response_header format.
 * Revised:	5-MAR-2000	Linux support.
 * Revised:	30-AUG-2001	Support protfail errorpage on scripts.
 * Revised:	9-JAN-2002	Tweak dnetreuse processing, abort on
 *				any error.
 */
#include "pthread_1c_np.h"
#include <stdio.h>
#include <stdlib.h>
#include "session.h"
#include "tserver_tcp.h"
#include "tmemory.h"
#include "errorpage.h"
#include "decnet_access.h"
#include "decnet_searchlist.h"
#include "message_service.h"
#include "script_manage.h"
#define VMS_EOF 2162

int http_log_level;			/* global variable */
int http_script_time_limit1;		/* Timeout for dialog phase */
int http_script_time_limit2;		/* timeout for output phase */
extern char http_server_version[];	/* Global variable, server version */
int http_crlf_newline;			/* 0-LF newline, 1- CRLF newline */
int tlog_putlog();
int http_dns_enable;		/* GLobal variable, name lookup enable */
int http_send_error 		/* prototype for send_error function */
	( session_ctx scb, char *msg, char *buf );
int http_add_response(), http_send_response_header(), http_translate_ident();
int http_parse_url(char *, char *, char **, char **, char **, char **);
int http_invalidate_doc_cache(), http_check_protection();
int http_keepalive_limit;	/* max keepalives allowed or 0 */

static pthread_once_t wwwexec_setup = PTHREAD_ONCE_INIT;
static char *www_exec_task;
static int www_exec_node;		/* Length of node name portion */
static enum opcodes { DNET_HDR, DNET_ARG, DNET_ARG2, DNET_INPUT,
	DNET_TEXT, DNET_RAW, DNET_RQURL, DNET_CGI, DNET_HOST, DNET_ID,
	DNET_BINDIR, DNET_PATH, DNET_XLATE, DNET_XLATEV, DNET_SENTINEL, 
	DNET_INVCACHE, DNET_RECMODE, DNET_RECMODE2, DNET_ID2, DNET_MANAGE, 
	DNET_REUSE, DNET_FORCEKA, DNET_CERTMAP } dummy;

static struct { enum opcodes opc; 	/* opcode */
		int l; 			/* Length of tag name (s) */
		char *s; 		/* Tag name */
		int terminal; } 	/* If true, ends session */
    tag_list[] = {
	{ DNET_HDR, 9, "<DNETHDR>", 0 }, 	/* Send header */
	{ DNET_ARG, 9, "<DNETARG>", 0 }, 	/* Send search arg */
	{ DNET_ARG2, 10, "<DNETARG2>", 0 },	/* Send trunc. search arg */
	{ DNET_INPUT, 11, "<DNETINPUT>", 0 },	/* Send client data */
	{ DNET_TEXT, 10, "<DNETTEXT>", 1 }, 	/* Read text response */
	{ DNET_RAW, 9, "<DNETRAW>", 1 }, 	/* Read HTTP response */
	{ DNET_RQURL, 11, "<DNETRQURL>", 0 },	/* Send original URL */
	{ DNET_CGI, 9, "<DNETCGI>", 1 }, 	/* Read 'CGI' response */
	{ DNET_HOST, 10,"<DNETHOST>", 0 },	/* Send server host */
	{ DNET_ID, 8,"<DNETID>", 0 },		/* Send connection info */
	{ DNET_BINDIR, 12,"<DNETBINDIR>", 0 },	/* Send htbin/exec directory */
	{ DNET_PATH, 10, "<DNETPATH>", 0 },	/* Send htbin/exec prefix */
        { DNET_XLATE, 11, "<DNETXLATE>", 0 },	/* Translate URL by rules file*/
        { DNET_XLATEV, 12, "<DNETXLATEV>", 0 },	/* Translate URL by rules file*/
	{ DNET_INVCACHE, 14, "<DNETINVCACHE>", 0 },
        { DNET_RECMODE, 13, "<DNETRECMODE>", 0 },
        { DNET_RECMODE2, 14, "<DNETRECMODE2>", 0 },
	{ DNET_ID2, 9, "<DNETID2>", 0 },	/* extended ID2 */
	{ DNET_MANAGE, 12, "<DNETMANAGE>", 0 }, /* Managment command */
	{ DNET_REUSE, 11, "<DNETREUSE>", 0 },   /* keep decnet link open */
	{ DNET_FORCEKA, 13, "<DNETFORCEKA>", 0 }, /* explicit KA */
	{ DNET_CERTMAP, 13, "<DNETCERTMAP>", 0 },
	{ DNET_SENTINEL, -1, "Sentinel", 1 }
    };

struct io_channel_def {		/* also used by http_cgi_execute!! */
    void *dptr;				/* handle for I/O routine */
    int (*read)();			/* read function */
    int (*write)();
    int (*close)();
    int (*set_time_limit)();
    int (*format_err)();
    char *savetask;		/* if not null, save connection as taskname */
};
typedef struct io_channel_def *io_chan;
typedef int ifunc();

int http_cgi_execute 		/* Handler for <DNETCGI> */
	( session_ctx scb, 		/* Session control block */
	  io_chan link,			/* Decnet connection to script task */
	  int text_mode,		/* true iff <DNETRECMODE> seen */
	  string *iobuf, 		/* Scratch data buffer for I/O */
	  char **end_tag );		/* non-null if transfer_output  should
					 * be called. */

/****************************************************************************/
static void www_init()
{
    dnet_initialize();
}
/****************************************************************************/
/*
 * Common routine to relay DECnet task output to client.
 */
static int transfer_output ( int text_mode, io_chan link, void *ctx, 
	string *iobuf,
	char * end_tag, int *data_bytes )
{
    char *buffer;
    int length, status, tag_length, iosize;
    tag_length = tu_strlen(end_tag);
    buffer = iobuf->s;
    iosize = iobuf->l > 4096 ? 4096 : iobuf->l;
    while ( ((status=(*link->read)( link->dptr, buffer,
			iosize,&length))&1) == 1 ) {
	if (length == tag_length) 
		if ( 0 == tu_strncmp(buffer,end_tag, tag_length) ) break;
	if ( text_mode ) {
	    /* Add CRLF if in text mode */
	    if ( (length+2) > iosize ) {		/* flush buffer */
		status = ts_tcp_write ( ctx, buffer, length );
		if ( (status&1) == 0 ) break;
		length = 0;
	    }
	    if ( http_crlf_newline ) buffer[length++] = '\r'; 
	    buffer[length++] = '\n';
	}
	status = ts_tcp_write ( ctx, buffer, length );
	if ( (status&1) == 0 ) break;
	*data_bytes += length;
    }
    /*
     * If we exitted loop abnormally (end_tag not matched), script it
     * in an invalid state so kill any reuse.
     */
    if ( (status&1) == 0 ) link->savetask = (char *) 0;
    return status;
}
/**************************************************************************/
/* The following routine fixes up a relative URL to translation.  Return
 * value is final length of buffer.
 */
static int apply_relative ( char *base, int baselen, char *buffer, int bufsize )
{
    /*
     * Find last slash in base
     */
    char *pdir, *t;
    int last_slash, i, buflen;

    if ( base[0] != '/' ) return tu_strlen ( buffer );
    for ( last_slash = i = 0; i < baselen && base[i]; i++ ) 
	if ( base[i] == '/' ) last_slash = i;
    buflen = tu_strlen(buffer);
    if ( buflen + last_slash + 1 >= bufsize ) return buflen;
    /*
     * copy existing buffer to tail of buffer and insert base at front.
     */
    for ( i=buflen; i >= 0; --i ) buffer[i+last_slash+1] = buffer[i];
    tu_strncpy ( buffer, base, last_slash+1 );
    /*
     * Fixup pseudo-directories.
     */
    while ( pdir = tu_strstr ( buffer, "/./" ) ) {
	while ( *pdir ) { pdir++; pdir[0] = pdir[2]; }
    }
    while ( pdir = tu_strstr ( buffer, "/../" ) ) {
	if ( pdir == buffer ) t = buffer;
	else for ( t = pdir-1; t > buffer; --t ) {
	    if ( *t == '/' ) break;
	}
	while ( *t ) { t++; pdir++; *t = pdir[3]; }
    }
    return tu_strlen ( buffer );
}
/**************************************************************************/
int http_script_link_close ( io_chan link ) 
{
    int status, written;
    if ( link->savetask ) {
	/*
	 * Script told us to save link for reuse, confirm by sending 2
	 * <DNETREUSE> tags back to scriptserver.
	 */
#ifdef VMS
	status = dnet_write ( link->dptr, "<DNETREUSE>", 11, &written );
	if ( status&1 ) status = dnet_write ( link->dptr, 
		"<DNETREUSE>", 11, &written );
#else
	status = 0;
#endif
	if ( (status&1) == 1 ) {
	    /*
	     * Hand off connection to pool managed by decnet_searchlist module.
	     */
	    if ( 1 == dnetx_save_connection ( link->dptr, link->savetask ) ) {
		/*
		 * Sucessful handoff, return sucess code.
		 */
		link->close = (ifunc *) 0;
		return status;
	    }
	}
    }
    /*
     * Rundown link and clear function pointer to inhibit calling more than
     * once.
     */
    status = (*link->close) ( link->dptr );
    link->close = (ifunc *) 0;
    return status;
}
/**************************************************************************/
int http_script_execute (
	session_ctx scb,		/* Session control block */
	char *subfunc, 			/* Subfunction to execute */
	char *ident,			/* Ident parsed from request. */
	char *arg,			/* Search argument portion of URL */
	string *iobuf )			/* I/O buffer */
{
    int status, i, j, written, length, opcode, bufsize, id_len, prefix_len;
    int rr_pos, first, text_mode, manage_req_pending, *cert_data, is_reused;
    int  dir_len, local_port, remote_port, remote_addr, siglen, terminal;
    struct acc_info_struct acc, *save_acc;
    char *buffer, *errmsg, *tmp, *bindir, **exec_list, *mst_exec[2], *etag;
    char *ss_name;			/* scriptserver task specification */
    void *dptr;				/* Handle for DECnet connection */
    int (*x_write)(); int (*x_read)(), ts_tcp_stack_used();
    struct io_channel_def link;
    /*
     * check recursion limits.
     */
    scb->recursion_level++;
    if ( http_log_level >= 12 ) {
	tlog_putlog(0,"!AZ script_exec level !SL, stack used: !SL!/", 
		scb->log_prefix, scb->recursion_level, ts_tcp_stack_used() );
    }
    if ( scb->recursion_level >= SCRIPT_RECURSION_LIMIT ) {
	    http_add_response ( scb->rsphdr, "500 Stack overflow", 1 );
	    status = http_send_response_header ( scb );
	    if ( (status&1) == 1 ) status = ts_tcp_write ( scb->cnx,
		"Recursion limit exceeded\r\n", 26 );
	    return status;
    }
    /*
     * Check access protection.
     */
    pthread_once ( &wwwexec_setup, www_init );
    if ( scb->acc->prot_file[0] ) {
	i = scb->rsphdr->l;		/* original response header length */
        status = http_check_protection  ( scb, ident, iobuf );
	if ( http_log_level > 2 ) {
	    tlog_putlog ( 3, 
		"Protection check status (script): !SL, protfail: '!AZ'!/", 
		status, http_error_page.protfail.type ?
		   http_error_page.protfail.ident : "*" ); 
	}
	if ( (status == 0) && (http_error_page.protfail.type != 0) ) {
	    /*
	     * Protection failed and we have a protfail error page defined.
	     */
	    if ( tu_strncmp ( &scb->rsphdr->s[i], "401 ", 4 ) == 0 ) {
		/* Protection check wants client to send authorization info */
		status = http_send_response_header ( scb );
		if ( (status&1) == 1 ) status = ts_tcp_write ( scb->cnx,
			 "Additional authorization required\r\n", 35 );
	    } else {
		struct errblk {
		    char ecode[8];
		    char *orig_prot_file;
		    char *service, *node, *ident, *arg;
		    char fixup[1024];
		    char url_buf[1];
		} *eblk;
		/*
		 * Allocate memory for error processing from heap so we
		 * don't worry about stack consumption of recursive calls.
		 */
		eblk = tm_malloc (sizeof(struct errblk)+scb->request[1].l+32);
		/*
		 * Setup call for error processing routine, recover
		 * the original ident by re-parsing the recieved request.
		 * Temporarily reset the protection check for the recursive
		 * call.
		 */
		scb->rsphdr->l = i;	/* restore length */
		tu_strnzcpy ( eblk->ecode, &scb->rsphdr->s[i], 4 );
		eblk->orig_prot_file = scb->acc->prot_file;
		scb->acc->prot_file = "";
		status = http_parse_url ( scb->request[1].s,
			eblk->url_buf, &eblk->service, &eblk->node,
			&eblk->ident, &eblk->arg );
		/*
		 * Hand off to common error routine.
		 */
		status = http_process_error ( scb, &http_error_page.protfail,
			eblk->ecode, eblk->ident, eblk->fixup,
			sizeof(eblk->fixup), eblk->arg, iobuf );
		/*
		 * Restore original session state and cleanup.
		 */
		scb->acc->prot_file = eblk->orig_prot_file;
		tm_free ( eblk );
	    }
	    return status;

        } else if ( status == 0 ) {
	    /*
	     * Send error response to client, since this type of failure
	     * is 'normal', don't  echo request headers in response.
	     */
	    status = http_send_response_header ( scb );
	    if ( (status&1) == 1 ) status = ts_tcp_write ( scb->cnx,
		"File _protected_ against access\r\n", 33 );
	    return status;
	}
    }
    /*
     * Parse ident portion into ident, dir-template, and bindir.
     * The rule file database is kludged up to store all 3 separated by
     * asterisks.
     */
    dir_len = id_len = -1;
    bindir = "";
    for ( i = 0; ident[i]; i++ ) if ( ident[i] == '*' ) {
	if ( id_len == -1 ) id_len = i;	/* first asterisk */
	else if ( dir_len == -1 ) {		/* Second asterisk */
	    dir_len = i - id_len - 1;
	    bindir = &ident[i+1];		/* Script directory */
	} else {
	    /*
	     * Path had additional asterisks, update id_len and bindir
	     * to always use the last 2 *-delimited strings.
	     */
	    id_len = id_len + dir_len + 1;
	    dir_len = i - id_len - 1;
	    bindir = &ident[i+1];
	    /* Too many asterisks, abort
	    status = http_send_error ( scb,
		"400 Invalid script specification",
		"Invalid script specified (illegal character in path)" );
	    return status; */
	}
    }
    if ( id_len < 0 ) id_len = i;	/* fallback length */
    if ( dir_len < 0 ) dir_len = 0;
    if ( http_log_level > 9 ) tlog_putlog( 9,
	"!AZ dnet exec Ident: !AZ parse lengths: !SL !SL, dir: !AZ!/", 
	scb->log_prefix, ident, id_len, dir_len, bindir );
    /*
     * Generate DECnet task specification to  use.
     */
    if ( *bindir == '%' ) {
	rr_pos = 0;
	for ( prefix_len = 0; bindir[prefix_len] &&
	    bindir[prefix_len] != ':'; prefix_len++ );
	if ( bindir[prefix_len] ) bindir[prefix_len++] = '\0';
	mst_exec[0] = bindir;
	mst_exec[1] = (char *) 0;
	exec_list = mst_exec;
	link.write = x_write = mst_write;
	link.read = x_read = mst_read;
	link.close = mst_close;
	link.set_time_limit = (void *) 0;
	link.format_err = mst_format_error;
	status = 1;
    } else {
        status = dnetx_parse_task ( bindir, &prefix_len, &exec_list, &rr_pos );
	link.write = x_write = dnet_write;
	x_read = dnet_read;
	link.read = dnet_read_streamed;
	link.close = dnet_disconnect;
	link.set_time_limit = dnet_set_time_limit;
	link.format_err = dnet_format_error;
    }
    link.savetask = (char *) 0;
    if ( (status&1) == 0 ) {
	status = http_send_error ( scb,
		"500 Error connecting to DECnet object",
		"Unable to parse script configuration." );
	return status;
    }
    bindir = &bindir[prefix_len];	/* trim prefix */
    /*
     * Make connection to wwwexec object.  Make repeated attemtps to
     * Get successful connection, starting with task_specification at
     * Round Robin pointer returned by parse function.
     */
    bufsize = iobuf->l;
    buffer = iobuf->s;
    ss_name = errmsg = (char *) 0;

#ifdef VMS
    for ( first = -1; (status&1) && dnetx_find_connection ( 
	    exec_list, rr_pos, &first, &dptr, &ss_name ); ) {
#else
    for ( j = rr_pos, first = -1; (first == -1) || (j != rr_pos); ) {
	first = 0;			/* Fake round-robin processing */
	ss_name = exec_list[j];		/* done by dnetx_find_connection */
	dptr = (void *) 0;
#endif
	is_reused = 0;
	if ( dptr != (void *) 0 ) {
	    /* Reusing existing connection */
	    is_reused = 1;
	    status = 3;
	} else if ( exec_list == mst_exec ) {
	    status = mst_connect ( ss_name, bindir,
		(mst_link_t *) &dptr );
	} else {
            status = dnet_connect ( ss_name, &dptr );
	}
        if ( http_log_level > 5 ) tlog_putlog ( 5,
		"!AZ Connect status=!SL!AZ for task_spec[!SL] = '!AZ'!/", 
			scb->log_prefix, status, 
			is_reused ? " (reused)" : "", first, ss_name );
        if ( (status&1) == 0 ) {
	    (*link.format_err)( status, buffer, bufsize > 256 ? 256 : bufsize );
	    errmsg = "500 Error connecting to DECnet/service object";
	    continue;
        }
 	if ( http_script_time_limit1 > 0 && link.set_time_limit ) {
	    /*  Limit time we will wait for dialog phase to complete. */
	    if ( http_log_level > 4 ) tlog_putlog ( 4,
		"!AZ Setting dialog timeout to !SL second!%S",
		scb->log_prefix, http_script_time_limit1 );
	    link.set_time_limit ( dptr, http_script_time_limit1 );
	}
        /*
         * Send header information, 4 messages.
         */

        status = (*x_write)( dptr, subfunc, tu_strlen(subfunc), &written );
        for ( i = 0; i < 3; i++ ) if ( (status&1) == 1 ) switch ( i ) {
          case 0:	/* Method */
            status = (*x_write)( dptr, scb->request[0].s, 
		scb->request[0].l > 255 ? 255 : scb->request[0].l, &written );
	    break;
          case 1:	/* protocol */
            status = (*x_write)( dptr, scb->request[2].s, 
		scb->request[2].l > 255 ? 255 : scb->request[2].l, &written );
	    break;
          case 2:	/* ident portion of URL */
	    status = (*x_write)
		( dptr, ident, id_len > 255 ? 255 : id_len, &written );
	    break;
        }
        if ( (status&1) == 0 ) {
	    (*link.format_err)( status, buffer, bufsize > 256 ? 256 : bufsize );
	    errmsg = "500 Error writing to DECnet object";
	    (*link.close)( dptr );
#ifdef VMS
	    if ( is_reused ) {
		if ( http_log_level > 0 ) tlog_putlog ( 1, 
		    "!AZ reused connection failure (!SL), resetting!/", 
			scb->log_prefix, status);
		first = -1;
		status = 1;	/* force past loop test */
	    }
#endif
	    continue;
        }
	/*
	 * Read first response.
	 */
	status = (*x_read) ( dptr, buffer, bufsize, &length );
	if ( (status&1) == 0 ) {
	    (*link.close)( dptr );
	    errmsg = "500 Error reading command from script";
	    (*link.format_err)( status, buffer, bufsize > 256 ? 256 : bufsize );
	} else if ( (length == 10) && 
		(0 == tu_strncmp(buffer,"<DNETBUSY>",10) ) ) {
	    /*
	     * Server is refusing connection.
	     */
	    (*link.close)( dptr );
	    errmsg = "500 Error connecting to script process";
	    tu_strcpy ( buffer,	"All scriptservers busy or inaccesbile" );
	    status = 20;
	} else {
	    break;	/* We got connection */
	}
#ifndef VMS
	j++;		/* Go to next entry in list */
	if ( !exec_list[j] ) j = 0;
#endif
    }
    if ( (status&1) == 0 ) {
	/* Format last status code */
	status = http_send_error ( scb, errmsg, buffer );
	return status;
    }
    link.dptr = dptr;
    /*
     * Main loop, get commands from remote task.
     */
    text_mode = manage_req_pending = 0;
    for ( ; (status&1);
	     status = (*x_read)(dptr, buffer, bufsize, &length) ) {
	if ( manage_req_pending ) {
	    manage_req_pending = 0;
	    http_clear_script_manage();
	}
	/*
	 * Lookup message in tag definition table.  Since all tags start
	 * with "<DNET", start compare at 6th position.
	 */
	siglen = length - 5;
	if ( 0 != tu_strncmp(buffer,"<DNET",5) ) siglen =  bufsize-1;
	for ( opcode = 0; tag_list[opcode].l > 0; opcode++ ) 
	    if ( (length ==tag_list[opcode].l) && (0==tu_strncmp(&buffer[5], 
			&tag_list[opcode].s[5], siglen) ) ) break;
	buffer[length] = '\0';
	if ( http_log_level > 4 ) tlog_putlog ( 4,
	    "!AZ Control message from decnet task: '!AZ'(opc: !SL) len: !SL!/", 
		    scb->log_prefix, buffer, opcode, length );
	/*
	 * Execute requested command.
	 */
	terminal = tag_list[opcode].terminal;
	opcode = tag_list[opcode].opc;
	switch ( opcode ) {
	  case DNET_HDR:	/* <DNETHDR> */
	    /*
	     * Write remaining request header lines.  Include blank one.
	     */
	    i = 3;
	    do { status = (*x_write)( dptr, scb->request[i].s, 
			scb->request[i].l > 0 ? scb->request[i].l : 0, &written );
		if ( (status &1) == 0 ) break;
	    } while ( scb->request[i++].l > 0 );

	    break;

	  case DNET_ARG:	/* <DNETARG> */
	    /*
	     * Write URL arg.
	     */
	    i = tu_strlen ( arg );
	    status = (*x_write)( dptr, arg, i, &written );
	    break;

	  case DNET_ARG2:	/* <DNETARG2> */
	    /*
	     * Write URL arg. limited to 255 chars.
	     */
	    i = tu_strlen ( arg );
	    status = (*x_write)( dptr, arg, i > 255  ? 255 : i, &written );
	    break;

	  case DNET_TEXT:	/* <DNETTEXT> */
	    /*
	     * Object will send us status line followed by simple text to be 
	     * formatted, one line per decnet message.
	     */
 	    if ( http_script_time_limit1 > 0 || http_script_time_limit2 ) {
	        /*  Limit time we will wait for output phase to complete. */
		if ( link.set_time_limit ) link.set_time_limit ( dptr, 
			http_script_time_limit2 );
	    }
	    status = (*x_read)(dptr, buffer, bufsize, &length);
	    if ( (status&1) == 0 ) {
		(*link.format_err) ( status,
			buffer, bufsize > 256 ? 256 : bufsize );
	        status = http_send_error ( scb,
			"500 I/O error in DECnet task", buffer );
		return status;
	    }
	    buffer[length] = '\0';
    	    status = http_add_response ( scb->rsphdr, buffer, 1 );
    	    status = http_send_response_header ( scb );
	    if ( (status&1) == 0 ) break;
	    link.dptr = dptr;
            status = transfer_output ( 1, &link, scb->cnx, iobuf, 
			"</DNETTEXT>", &scb->data_bytes );
	    break;

	  case DNET_RAW:	/* <DNETRAW> */
	    /*
	     * Remote task will handle all formatting, relay raw data.
	     * Put first line in rsphdr so log file writer can examine it.
	     */
 	    if ( http_script_time_limit1 > 0 || http_script_time_limit2 ) {
	        /*  Limit time we will wait for output phase to complete. */
		if ( link.set_time_limit ) link.set_time_limit ( dptr, 
			http_script_time_limit2 );
	    }
	    i = (text_mode) ? 2 : 0;	/* reserved for CRLF */
	    status = (*x_read)( dptr, iobuf->s,
			(iobuf->l+i) > 4096 ? (4096-i) : (iobuf->l-i), &length );
    	    if ( (status&1) == 0 ) break;
	    if ( (length == 10) && 
		 ( 0 == tu_strncmp ( iobuf->s, "</DNETRAW>", 10 ) ) ) {
		scb->rsphdr->l = 0;		/* end of file */
		status = VMS_EOF;
	    } else {
		if ( text_mode ) {		/* append record terminator */
	    	    if ( http_crlf_newline ) buffer[length++] = '\r'; 
		    iobuf->s[length++] = '\n';
		}
		tu_strnzcpy ( scb->rsphdr->s, iobuf->s, 
			length < 40 ? length : 40 );
		scb->rsphdr->l = tu_strlen ( scb->rsphdr->s );
		/*
		 * Copy buffer to client and continue transfer till done.
		 */
		if ( length > 0 ) status = ts_tcp_write 
			( scb->cnx, iobuf->s, length );
		if ( (status&1) == 0 ) break;
		scb->data_bytes += length;

		link.dptr = dptr;
		status = transfer_output ( text_mode, &link, scb->cnx, iobuf, 
			"</DNETRAW>", &scb->data_bytes );
	    }
	    break;

	  case DNET_RQURL:	/* <DNETRQURL> */
	    /*
	     * Write actual requested URL arg. unlimited length.
	     */
	    i = tu_strlen ( arg );
	    status = (*x_write)( dptr, scb->request[1].s, 
			scb->request[1].l, &written );
	    break;

	  case DNET_CGI:	/* <DNETCGI> */
	    /*
	     * Special 'CGI' mode, similar to RAW except redirect may take
	     * action.
	     */
 	    if ( http_script_time_limit1 > 0 || http_script_time_limit2 ) {
	        /*  Limit time we will wait for output phase to complete. */
		if ( link.set_time_limit ) link.set_time_limit ( dptr, 
			http_script_time_limit2 );
	    }
	    link.dptr = dptr;
	    status = http_cgi_execute ( scb, &link, text_mode, iobuf, &etag );
	    if ( etag ) {
		/*
		 * Transfer bytes and ensure number matches.
		 */
		int actual_dbytes;
		actual_dbytes = 0;
		status = transfer_output ( text_mode, &link, 
			scb->cnx, iobuf, etag, &actual_dbytes );
		if ( scb->keepalive_pending && 
			(actual_dbytes != scb->data_bytes) ) {
		    /* Mismatch, kill keepalive (but not reuse) */
		    if ( http_log_level > 4 ) tlog_putlog ( 5,
			"!AZ !AZ of !SL when expecting !SL!/", scb->log_prefix,
			"Data transfer from script",
			actual_dbytes, scb->data_bytes );
		    scb->keepalive_pending = 0;
		}
		scb->data_bytes = actual_dbytes;
	    }

	    break;

	  case DNET_HOST:	/* <DNETHOST> */
	    /*
	     * Write HTTP_DEFAULT_HOST to server task.
	     */
	    tmp = scb->acc->local_address;
	    if ( !tmp ) tmp =  "???";
	    status = (*x_write)( dptr, tmp, tu_strlen(tmp), &written );
	    break;

	  case DNET_ID:		/* <DNETID> */
	  case DNET_ID2:	/* <DNETID2> */
	    /*
	     * Write software version, server name, server port, remote_port, 
	     * remote address, remote user, remote host to server task.
	     */
	    status = ts_tcp_info ( &local_port, &remote_port, 
		(unsigned int *) &remote_addr );
	    tu_strcpy ( buffer, "OSU/" ); length = tu_strlen ( buffer );
	    tu_strcpy ( &buffer[length], http_server_version );
	    length = tu_strlen ( buffer );
	    buffer[length++] = ' ';
	    tmp = scb->acc->local_address;
	    tu_strcpy ( &buffer[length], tmp ? tmp : "???" ); 
	    length += tu_strlen ( &buffer[length] );
	    buffer[length++] = ' ';
	    tu_strint ( local_port, &buffer[length] );
	    length += tu_strlen ( &buffer[length] );
	    buffer[length++] = ' ';
	    tu_strint ( remote_port, &buffer[length] );
	    length += tu_strlen ( &buffer[length] );
	    buffer[length++] = ' ';
	    tu_strint ( remote_addr, &buffer[length] );
	    length += tu_strlen ( &buffer[length] );
	    if ( scb->acc->user[0] ) {
		buffer[length++] = ' ';
	        tu_strcpy ( &buffer[length], scb->acc->user );
	        length += tu_strlen ( &buffer[length] );
	    } else if ( opcode == DNET_ID2 ) {
		/* make placeholder for username */
		buffer[length++] = ' ';
	    }
	    if ( (opcode == DNET_ID2) && http_dns_enable ) {
		/* Append remote hostname to return string */
		buffer[length++] = ' ';
		tu_strcpy (&buffer[length], ts_tcp_remote_host() );
	        length += tu_strlen ( &buffer[length] );
	    }
	    status = (*x_write)( dptr, buffer, length, &written );
	    break;

	  case DNET_BINDIR:	/* <DNETBINDIR> */
	    /*
	     * Write HTTP_BINDIR to server task.  This is directory to
	     * search for scripts.
	     */
	    tmp = bindir ? bindir : "???";
	    i = tu_strlen ( tmp );
	    status = (*x_write)( dptr, tmp, i > 255 ? 255 : i, &written );
	    break;

	  case DNET_PATH:	/* <DNETPATH> */
	    /*
	     * Write path to server task.  This is ident prefix that
	     * caused script invocation.
	     */
	    tmp = (dir_len > 0 ) ? &ident[id_len+1] : "???";
	    i = tu_strlen ( tmp );
	    if ( i > dir_len ) i = dir_len;
	    status = (*x_write)( dptr, tmp, i > 255 ? 255 : i, &written );
	    break;

	  case DNET_INPUT:
	    /*
	     * Read characters from client and relay to server task.
	     */
	    status = tu_read_raw ( scb->inbound, buffer, 254, &length );
	    if ( (status&1) == 1 ) {
	        if ( http_log_level > 9 ) tlog_putlog ( 9,
		   "!AZ Data bytes read from client: !SL!/", scb->log_prefix,
		    length );
	        status = (*x_write)( dptr, buffer, length,  &written );
	    }
	    break;

	  case DNET_XLATE:
	  case DNET_XLATEV:
	    /*
	     * Read test URL from task and translate.
	     */
	    status = (*x_read) (dptr, buffer, bufsize, &length);
	    if ( (status&1) == 0 ) break;
	    if ( length > 256 ) length = 256;
	    buffer[length] = '\0';
	    tmp = &buffer[length+1];		/* Allocate temp from buffer*/
	    acc.uic = 0;
	    acc.cache_allowed = 1;
	    acc.prot_file = "";
	    acc.rem_user[0] = '\0';
	    acc.local_address = scb->acc->local_address;

	    if ( (opcode == DNET_XLATEV) && (buffer[0] == '(') ) {
		/* skip method */
		for ( i=0; i < length; i++ ) if ( buffer[i] == ')' ) {
		    i++; break;
		}
	    } else i = 0;

	    if ( buffer[i] != '/' ) {
		/* Buffer is 'relative' URL, fixup */
		length = apply_relative ( scb->request[1].s, scb->request[1].l,
			&buffer[i], bufsize-i );
		length += i;
		tmp = &buffer[length+1];
	    }

	    status = http_translate_ident
		( &buffer[i], tmp, bufsize - length-1, &acc);
	    if ( http_log_level > 4 )
		tlog_putlog ( 4, 
			"!AZ URL translate status: !SL maxlen: !SL prot: !AZ!/", 
			scb->log_prefix, status, bufsize - length, acc.prot_file );
	    if ( status == 0 ) {
		/* Translation failed, return null string */
		buffer[length+1] = '\0';
	    } else if ( (opcode == DNET_XLATEV) && acc.prot_file[0] ) {
		/*
		 * Check protection and null out answer if it fails.
		 * If alternate method specified, replace it.
		 */
		string iobuf2;
		int tmp_len, sav_rsplen;
		if ( i > 2 ) {
		}
		tmp_len = tu_strlen ( tmp );
		iobuf2.l = bufsize - length - tmp_len - 1;
		iobuf2.s = &tmp[tmp_len+1];
		save_acc = scb->acc;
		scb->acc = &acc;		/* Use this doc's prot file */
		sav_rsplen = scb->rsphdr->l;
        	status = http_check_protection  ( scb, tmp, &iobuf2 );
		scb->acc = save_acc;
		scb->rsphdr->l = sav_rsplen;	/* undo anything added */
        	if ( status == 0 ) tmp_len = 0;
	        if ( http_log_level > 4 ) tlog_putlog ( 4, 
			"!AZ URL translate prot check status: !SL!/", 
			scb->log_prefix, status );
		tmp[tmp_len] = '\0';
		if ( i > 2 ) {
		}
	    }
	    length = tu_strlen ( tmp );
	    status = (*x_write)( dptr, tmp, length, &written );
	    break;

	  case DNET_INVCACHE:
	    /* mark invalid */
	    http_invalidate_doc_cache();	/* clear cache */
	    status = 1;
	    break;

	  case DNET_RECMODE:
	    /* Force record mode, turn off streamed reads */
	    text_mode = 1;
	    status = 1;
	    if ( link.read == dnet_read_streamed ) link.read = dnet_read;
	    break;
	  case DNET_RECMODE2:
	    /* Force alternate record mode (longer records) */
	    text_mode = 2;
	    status = 1;
	    break;

	  case DNET_MANAGE:
	    /*
	     * Handshake for <DNETMANAGE>:
	     *   script creates socket and binds port number
	     *   script writes <DNETMANAGE>, port+host
	     *   server confirms script, changes managment port, writes
	     *		status to script.
	     *   if status good script connects to server and performs
	     *          command.
	     *    script writes next command to server or closes port.
	     *    server resets manage port.
	     */
	    status = (*x_read) (dptr, buffer, bufsize, &length);
	    if ( (status&1) == 0 ) break;
	    if ( length > 256 ) length = 256;
	    buffer[length] = '\0';
	    manage_req_pending = http_script_manage_request (ident, buffer, 
		buffer, 255, &length);
	    status = (*x_write) (dptr, buffer, length, &length);
	    break;

	  case DNET_REUSE:
	    /*
	     * Try to save connection for reuse, 
	     */
	    link.savetask = ss_name;
	    break;

	  case DNET_FORCEKA:
	    /*
	     * Script takes responsibility for sending headers so client
	     * reuses connection.
	     */
	    if ( scb->keepalive_count < http_keepalive_limit ) {
		scb->keepalive_pending = 1;
	    }
	    break;

	  case DNET_CERTMAP:
	    /*
	     * Get certificate info use special call.  Return raw response
	     * of call.  If length written is 4, driver did not recognize
	     * special call (1st and 3rd argument identical).
	     */
	    cert_data = (int *) buffer;
	    cert_data[1] = -1;
	    length = bufsize;		/* size of receiving buffer */
	    ts_tcp_info ( cert_data, &length, (unsigned int *) cert_data );
	    if ( cert_data[1] < 0 ) {	/* no capability */
		length = -1;
	    } else if ( length > 32 ) {
		length = 32;
	    }
	    status = (*x_write)( dptr, buffer, (length+2)*sizeof(int), 
			&written );
	    break;

	  default:
	    /*
	     * Protocol error.
	     */
	    buffer[length] = '\0';
	    status = http_send_error ( scb,
		"500 Protocol error in DECnet task", buffer );
	    scb->data_bytes += scb->rsphdr->l;
	    break;
	}
	if ( (status&1) == 0 ) break;	/* abort */
	if ( terminal ) break;
    }
    /*
     * Kill link reuse if any abnormal exit.
     */
    if ( link.savetask && ((status&1) == 0) ) {
	link.savetask = (char *) 0;
	if ( http_log_level > 3 ) tlog_putlog ( 5,
		"!AZ dropped script reuse due to loop exit status of !SL\n",
		scb->log_prefix, status );
    }
    /*
     * Ensure management port reset.
     */
    if ( manage_req_pending ) http_clear_script_manage();
    /*
     * Send last-chance error message if nothing sent so far.
     */
    if ( scb->rsphdr->l <= 0 ) {
	int fallback_status;
	if ( (status&1) == 0 ) {
	    (*link.format_err)(status, buffer, 256);
	} else tu_strcpy ( buffer, 
		"Script produced no output, check NETSERVER.LOG" );
	fallback_status = http_send_error ( scb,
		"500 protocol error in DECnet object", buffer );
    }
    if ( link.close ) http_script_link_close ( &link );
    link.close = (ifunc *) 0;
    return status;
}
