/*
 * This program is run by the WWWEXEC scriptserver to do pre-processing of
 * html files, dynamically inserting files or other generated data.
 *
 * To specify the preprocessing you must give the file a distinct file type
 * (e.g. htmlx) and add the following to the configuration file:
 *
 *	suffix .htmlx text/x-server-parsed-html
 *	presentation text/x-server-parsed-html html_preproc
 *
 * WWWEXEC will then execute the following command line:
 *
 *	html_preproc method url protocol
 *
 *	argv[1]		Method specified in request (e.g. GET).
 *	argv[2]		Ident portion of requested URL, after translation by
 *			rule file.  See special parsing note below.
 *	argv[3]		Protocol specified in request, "" or "HTTP/1.0".
 *
 * None of the resulting file contents is returned until the entire file
 * has been processed.  This is required for 2 reasons:
 *
 *   1. The HTTP status, which is the first part of the response, is not known
 *       until processing is complete.
 *
 *   2.  Processing of the file may require using additional server functions,
 *       such as <DNETXLATE>, which become unavailable once the HTTP response
 *       <DNETRAW> is started.
 *
 * Argv[2] parsing:
 *    If the path in argv[2] is of the form /dir/filename.partname.type,
 *    then partname is extracted from /dir/filename.type.
 *
 * Author:	David Jones
 * Date:	25-AUG-1994
 * Revised:	3-SEP-1994	Re-coded parsing.
 * Revised:	6-OCT-1994	Support fsize/flastmod.
 * Revised:	11-OCT_1994	Adjust length in translate.
 * Revised:	20-DEC-1994	Fix length in do_include
 * Revised:	22-APR-1995	Added extra echo variables suggested by
 *			        Kent Covert (kacovert@miavx1.acs.muohio.edu):
 *				    DOCUMENT_NAME, LAST_MODIFIED, 
 *				    ACCESSES/ACCESSES_ORDINAL
 * Revised:	23-APR-1995	Add permissions file checks to ACCESSES var.
 * Revised:	25-APR-1995	Overhaul:
 *				  - Add strftime() formatting options to
 *				    LAST_MODIFIED, DATE_LOCAL, ACCESSES*
 *				  - Add version numbering to ACCESSES.
 *				  - Rename indexio.h to access_db.h
 * Revised:	15-MAY-1995	Use <DNETXLATEV> for virtual includes
 *				(does protection check).
 * Revised:	21-JUN-1995	Remove '\r's from net_link_printf formats as
 *				net_link_printf will add them.
 * Revised:	8-AUG-1995	Use CGI mode rather than RAW mode (stsline 
 *				can be sent using status: CGI header)
 * Revised:	31-AUG-1995	Fixup formatting of ACCESSES values for
 *				VAXC compatibility.
 * Revised:	13-NOV-1995	Add tag_verify config option.
 * Revised:	12-MAY-1996	Convert for MST support, (symbol IS_MST defined
 *				when compiled as part of MST image).
 * Revised:	18-MAY-1996	Add part include idea of Richard Levitte.
 * Revised:	19-MAY-1996	Changed to understand multiple instances of
 *				a part.
 * Revised:	27-MAY-1996	Changed to accept multiple part names in
 *				#begin and #end directives.  Separate names
 *				with whitespace, e.g.:
 *				   <!--#begin small big -->
 *				(allows you to condense comments around
 *				command parts such as header tags).
 * Revised:	28-MAY-1996	Bug fix, supply missing argument in open error
 *				messages (MST's only).
 * Revised:	9-SEP-1996	Change member names to avoid recursive
 *				macro substituition on MST variant.
 * Revised:	25-OCT-1996	Place limit on size of file to process,
 *				100,000 for script, 60000 for MST.
 * Revised:	25-DEC-1996	Split main routine into 2 parts to enable
 *				pre-processor to be called from other
 *				scripts (must define CALLABLE_PREPROC).
 * Revised:	3-FEB-1997	Fix bug handling directives that return zero
 *				length segment.
 * Revised:	26-AUG-1997	Add last-modified header if not using counter.
 * Revised:	8-DEC-1997	Add limited support for nested includes.
 *				(lastmod date is always for primary file).
 * Revised:	12-DEC-1997	Consolidate file loads into single routine.
 * Revised:	15-DEC-1997	Save to cache in 4000 byte chunks vs. 16000
 * Revised:	28-JAN-1998	Modify parse_part_name to ignore null part
 *				names in the filename, allowing specification
 *				of files with multiple dots:
 *				    foo.htmlx		file foo.htmlx
 *				    foo.bar.htmlx	part bar in foo.htmlx
 *				    foo.bar.x.htmlx	part x in foo.bar.htmlx
 *				    foo..htmlx		file foo.htmlx
 *				    foo.bar..html	file foo.bar.htmlx
 */

#ifndef IS_MST
/*
 * Include files only needed when not an MST
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "scriptlib.h"
#include "access_db.h"
#define LOCK_C_RTL
#define UNLOCK_C_RTL
#define INPUT_LIMIT 100000
#else
#define INPUT_LIMIT 60000
#endif
#include <time.h>
#include <syidef.h>
#include <descrip.h>
#include <stat.h>
int LIB$GETSYI();

#if defined(DEBUG) && defined(IS_MST)
#error "The combination of DEBUG and IS_MST is not allowed."
#endif

struct segment { int length; char *addr; };
typedef struct segment *segptr;

static int send_http_header ( char *stsline, char *content );
static int parse_directive 
	( char *path, char **targ, int *outlen, char **outbuf );
static int parse_tag ( char *tag, int maxlen, int *taglen, char **targ );
static int parse_part_name ( char *fname, char **pfile, char **pname );
static int extract_part ( char *, char *, int *, char ** );
static segptr alloc_segment ( segptr seg, int sc, int *seg_size, char *start );

static int tag_verify, abort_status;
static char *access_file_fdl = "\
  FILE; ORGANIZATION indexed; PROTECTION (system:RWED,owner:RWED,group,world);\
  RECORD; CARRIAGE_CONTROL carriage_return; FORMAT fixed; SIZE 120;\
  KEY 0; CHANGES no; PROLOG 3; SEG0_LENGTH 100; SEG0_POSITION 0; TYPE string;\
";
static int preprocess_html_source ( char *method, char *ident,
	char *source, int source_length );
static int load_file ( char *fname, char * lname, int *outlen, char **outbuf, int initial );
/*
 * Customized error response structures.
 */
struct custom_ep {
    int state;			/* 0 - unitialized, 1 - defined, -1 - default */
    char *default_stsline;	/* text if state=-1 */
    char *varname;		/* environment variable name */
    char stsline[256];		/* Status line */
};
static struct custom_ep loadfail = { 0, 
	"5001 File load failure", "HTTP_PREPROC_LOADFAIL", { 0 } };
static struct custom_ep includefail = { 0, 
	"500 Failed to open include file", "HTTP_PREPROC_INCLUDEFAIL", { 0 } };
static struct custom_ep allocfail = { 0,
	"500 Allocation failure during load", "HTTP_PREPROC_ALLOCFAIL", {0} };
static struct custom_ep statfail = { 0,
	"500 Failed to access file", "HTTP_PREPROC_STATFAIL", {0} };
static struct custom_ep accessesfail = { 0,
	"500 Failure in accesses.dat processing", "HTTP_PREPROC_ACCESSESFAIL",
	{0} };
static struct custom_ep baddirective = { 0,
	"500 bad directive in file", "HTTP_PREPROC_BADDIRECTIVE", {0} };

static int error_abort ( struct custom_ep *def, char *content_type )
{
    int length, status;
    char *env;
    /*
     * check state.  For MST serialize the access.
     */
#ifdef IS_MST
    pthread_lock_global_np();
#endif
    if ( def->state == 0 ) {
	env = getenv ( def->varname );
	if ( !env ) {			/* take the default */
	    def->state = -1;
	    strcpy ( def->stsline, def->default_stsline );
	} else {
	    def->state = 1;
	    length = strlen ( env );
	    if (length >= sizeof(def->stsline)) length = sizeof(def->stsline)-1;
	    strncpy ( def->stsline, env, length );
	    def->stsline[length] = '\0';
	}
    }
#ifdef IS_MST
    pthread_unlock_global_np();
#endif
    /*
     * Send the response.
     */
    status = send_http_header ( def->stsline, content_type );

    return status;
}
/****************************************************************************/
/* Main program/routine
 */
#ifndef CALLABLE_PREPROC
#ifdef IS_MST
static int preproc_main ( int argc, char **argv )
#else
int main ( int argc, char **argv )
#endif
{
    int status, i, j, k, length, s_size, used, sc, sg_size;
    char *source, *part_file, *part_name;
    char hdrline[64];
#ifdef IS_MST
    struct private_ctx *ctx;
    void *hfile, *link;
    char errmsg[256];
    /*
     * Get pointer to thread's private data (particularly link).
     */
    GET_SPECIFIC ( private_context, ctx )
    if ( http_log_level > 6 ) tlog_putlog ( 7,
	"Context address: !XL, args: !AZ '!AZ' !AZ!/", ctx, 
	argv[1], argv[2], argv[3] );
#else
    FILE *hfile;
    /*
     * Make connection back to server and set protocol version field for
     * the response to server.
     */
    status = net_link_open();
    if ( 0 == (status&1) ) exit ( status );
#endif
    /*
     * Validate method (argv[1]).  We only understand GET and HEAD.
     */
    if ( argc < 4 ) exit ( 20 );
    if ( strncmp("GET",argv[1],4) && strncmp("HEAD",argv[1],5) ) {
	send_http_header ( "501 unsupported method", "text/plain" );
	net_link_printf ("Unsupported method (%s)\n", argv[1] );
	return 1;
    }
    if ( parse_part_name ( argv[2], &part_file, &part_name ) ) {
	/*
	 * argv[2] was of form /path/filename.partname.type.
	 */
	status = extract_part ( part_file, part_name, &used, &source );
	if ( status < 0 ) return status;
	argv[2] = part_file;
    } else {
        /*
         * Open file specified by argv[2] (fixed up and returned in part_file).
         */
#ifdef IS_MST
	ctx->last_mod = 0;
#endif
	status = load_file ( part_file, part_file, &used, &source, 1 );
        if ( status < 0 ) return status;
    }
    /*
     * Parse source.
     */
    status = preprocess_html_source ( argv[1], argv[2], source, used );
    return status;
}
#else
/*
 *  Set scriptlib mode to buffer all output.  Call instead of cgi_begin_output
 *  (called after cgi_init_env).   Do not next CGI response header
 *  (content-type) as this is assumed to be text/html.
 */
#include "scriptlib.h"
static int old_nl_mode;
static char *pp_method, *pp_ident;
int cgi_begin_preprocessed ( char *method, char *ident )
{
    int status, old_mode;
    pp_method = malloc ( strlen(method)+1 );
    strcpy ( pp_method, method );
    pp_ident = malloc ( strlen(ident)+1 );
    if ( !pp_ident ) return 0;
    strcpy ( pp_ident, ident );
    old_nl_mode = net_link_set_mode ( 2 );		/* text mode, save ouput */
    if ( (old_nl_mode&1) == 1 ) net_link_set_mode ( 3 );
    return 1;
}
int cgi_end_preprocessed()
{
    char *buffer;
    int status, length;
    /*
     * reset mode and get address of buffer holding saved data.
     */
    net_link_set_mode ( old_nl_mode );
    status = net_link_saved_output ( &buffer, &length );
    if ( status&1 ) status = preprocess_html_source ( pp_method,
	pp_ident, buffer, length );
    free ( pp_ident );
    free ( pp_method );
    return status;
}
#endif   /* CALLABLE_PREPROC */
/****************************************************************************/
/* Recursive routine to scan source text for tags to pre-process.
 * Return value is number of segments.
 */
static int build_segment ( char *ident,	char *source, int source_length,
	segptr *seglist, int *seglist_size, int seglist_start )
{
    int status, is_directive, i, j, k, length, s_size, sc, sg_size;
    segptr seg;
    char *targ[11], hdrline[128];
#ifdef IS_MST
    struct private_ctx *ctx;
    int reslen, tag_verify;
    /*
     * Get pointer to thread's private data and initialize local tag_verify.
     */
    GET_SPECIFIC ( private_context, ctx )
    tag_verify = ctx->tag_verify;
#endif
    
    /*
     * Parse file, making list of individual blocks of text to send
     * back to server.
     */
    seg = *seglist;
    sg_size = *seglist_size;		/* make local copies of parameters */
    sc = seglist_start;			/* first element to load */
    seg[sc].length = 0;
    seg[sc].addr = source;
    status = 1;
    for ( i = 0; i < source_length; i++ ) {
	char cur;
	cur = source[i];
	if ( cur == '<' ) {
	    /*
	     * At beggining of HTML tag, go to state machine to parse.
	     */
	    is_directive = parse_tag ( &source[i], source_length-i, &j, targ );
#ifdef DEBUG
printf("Tag at %d (seg[%d].addr[%d]), length: %d, directive: %d verify: %d\n", i, 
sc, seg[sc].length, j, is_directive, tag_verify );
#endif
	    if ( j > 0 && (!is_directive || tag_verify) ) {
		/*
		 * Include parsed tag in output, as a comment the client
		 * will ignore it.
		 */
		seg[sc].length += j;
		while ( seg[sc].length >= 1024 ) {
		    /* Make current segment 1024 and put rest in new segment */
		    seg = alloc_segment ( seg, sc, &sg_size, 
				&seg[sc].addr[1024] );
		    seg[sc+1].length = seg[sc].length - 1024;
		    seg[sc++].length = 1024;
		}
	    }
	    i += j - 1;

	    if ( is_directive ) {
		/*
		 * Terminate current segment and make fresh one to hold
		 * included info.  Init start address of new segment to
		 * point after tag.
		 */
		if ( seg[sc].length > 0 ) {
		    seg = alloc_segment ( seg, sc++, &sg_size, &source[i] );
		} else seg[sc].addr = &source[i];
	 	j = 0;	/* remove already added. */
		status = parse_directive 
			( ident, targ, &seg[sc].length, &seg[sc].addr );
		if ( status < 0 ) return status;
#ifdef IS_MST
		tag_verify = ctx->tag_verify;
#endif
		if ( status > 1 ) {		/* recusive scan */
#ifdef IS_MST
		    ctx->recur_level++;
		    if ( ctx->recur_level < 5 ) sc = build_segment ( ident, 
			seg[sc].addr, seg[sc].length, &seg, &sg_size, sc );
		    --ctx->recur_level;
#else
		    sc = build_segment ( ident, seg[sc].addr,
			seg[sc].length, &seg, &sg_size, sc );
#endif
		    if ( sc < 0 ) return sc;
		}
		while ( seg[sc].length > 1024 ) {
		    /* Make current segment 1024 and put rest in new segment */
		    seg = alloc_segment ( seg, sc, &sg_size, 
				&seg[sc].addr[1024] );
		    seg[sc+1].length = seg[sc].length - 1024;
		    seg[sc++].length = 1024;
		}
		if ( seg[sc].length > 0 ) {
		    seg = alloc_segment ( seg, sc++, &sg_size, &source[i+1] );
		} else {
		    /* 
		     * result of directive was zero length, update start 
		     * point
		     */
		    seg[sc].addr = &source[i+1];
		}
	    }
	} else {
	    /*
	     * Extend current segment to include this character.
	     */
	    seg[sc].length++;
	    if ( seg[sc].length >= 1024 ) {
		/* Segment at write limit, advance to next */
		seg = alloc_segment ( seg, sc++, &sg_size, &source[i+1] );
	    }
	}
    }
    /*
     * Check for error status.
     */
    if ( status < 0 ) return status;
    *seglist = seg;
    *seglist_size = sg_size;
    return sc;
}
/****************************************************************************/
/* Main engine for interpreting the HTML source for pre-processor directives.
 * When finished, network link is placed in CGI mode and result is dumped
 * to net_link.
 */
static int preprocess_html_source ( char *method, char *ident,
	char *source, int source_length )
{
    int status, is_directive, i, j, k, length, s_size, sc, sg_size;
    segptr seg;
    char *targ[11], hdrline[128];
#ifdef IS_MST
    struct private_ctx *ctx;
    int reslen, tag_verify;
    /*
     * Get pointer to thread's private data and initialize local tag_verify.
     */
    GET_SPECIFIC ( private_context, ctx )
    tag_verify = ctx->tag_verify;
#else
    /*
     * Initialize global tag_verify.
     */
    tag_verify = 0;
#endif
    /*
     * Parse file, making list of individual blocks of text to send
     * back to server.
     */
    sg_size = 1000;
    seg = (struct segment *) malloc ( sg_size * sizeof(struct segment) );
    sc = build_segment ( ident, source, source_length, &seg, &sg_size, 0 );
    if ( sc < 0 ) return -1;
    /*
     * Flush accumulated segments, compute total length and include in header.
     */
    for (k = i = 0; i <= sc; i++) if (seg[i].length > 0) k += seg[i].length;
#ifdef IS_MST	       /* 123456789012345678901234567 89012345678901234 */
    tu_strcpy ( hdrline, "200 Sending Processed HTMLX\nContent-length: " );
    tu_strint ( k, &hdrline[44] );
    if ( !ctx->accessesKnown && ctx->last_mod ) {
	int hlen, cstatus, i, j;
	struct mstshr_envbuf env;
	char *if_modified_since;
	/*
	 * Check for if-modified-since header.
	 */
	env.used = 1;
	env.prolog[0] = ctx->prolog[0];
	env.prolog[1] = ctx->prolog[1];
	env.prolog[2] = ctx->prolog[2];
	env.prolog[3] = ctx->prolog[3];
	cstatus = mstshr_cgi_symbols2 ( ctx->link, "", &env );
	if_modified_since = mstshr_getenv ( "HTTP_IF_MODIFIED_SINCE", &env );

	if ( http_log_level > 6 ) tlog_putlog ( 7, 
	    "if-modified header: !XL '!AZ'!/",
	   if_modified_since, if_modified_since ? if_modified_since : "" );
	if ( if_modified_since ) {
	    unsigned int cache_time;
	    for ( i = j = 0; if_modified_since[i]; i++ ) {
		if ( if_modified_since[i] != ' ' ) {
		    if_modified_since[j++] = if_modified_since[i];
		}
	    }
	    if_modified_since[j] = '\0';
	    cache_time = tf_decode_time ( if_modified_since );

	    if ( ctx->last_mod <= cache_time ) {
	        tu_strcpy ( hdrline, "304 Cached copy good\nContent-length: " );
	        tu_strint ( k, &hdrline[37] );
		method = "HEAD";		/* eliminate body */
	    }
	}
	
	/* Append last-modified header since counter not used */
	hlen = tu_strlen ( hdrline );
	tu_strcpy ( &hdrline[hlen], "\nLast-modified: " ); hlen += 16;
	tf_format_time ( ctx->last_mod, &hdrline[hlen] );
    }
#else
    sprintf ( hdrline, "200 Sending Processed HTMLX\nContent-length: %d", k );
#endif
    send_http_header ( hdrline, "text/html" );
    if ( 0 == strncmp(method,"HEAD",5) ) return 1;

    for ( i = 0; i <= sc; i++ ) if ( seg[i].length > 0 ) {
#ifdef IS_MST
	status = mst_write ( ctx->link, seg[i].addr, seg[i].length, &reslen );
#else
	status = net_link_write ( seg[i].addr, seg[i].length );
#endif
	if ( (status&1) == 0 ) break;
    }
    return 1;
}
/**************************************************************************/
/* Convert binary time to ascii string.  If fmt_str does not start with '=',
 * use ctime format, otherwise use string after '=' as format for strftime().
 */
static char *format_time (char *buffer, int bufsize, char *fmt, void *timebuf)
{
    int i;
#ifdef __DECC
    if ( fmt ) if ( *fmt == '=' ) {
	size_t size;
	LOCK_C_RTL
	size = strftime ( buffer, bufsize, &fmt[1], 
		localtime((time_t *)timebuf) );
	UNLOCK_C_RTL
	if ( (size > 0) && (size < bufsize) ) buffer[size] = '\0';
	return buffer;
    }
#endif
    /*
     * fallback to using ctime() routine.
     */
#ifdef IS_MST
    pthread_lock_global_np();
    strncpy ( buffer, ctime((unsigned long *) timebuf), bufsize-1 );
    buffer[bufsize-1] = '\0';
    pthread_unlock_global_np();
#else
    strncpy ( buffer, ctime((unsigned long *) timebuf), bufsize-1 );
    buffer[bufsize-1] = '\0';
#endif
    for ( i = 0; buffer[i]; i++ ) if ( buffer[i] == '\n' ) {
	buffer[i] = '\0'; break;
    }
    return buffer;
}
/**************************************************************************/
static segptr alloc_segment ( segptr seg, int sc, int *seg_size, char *start ) {
    sc = sc + 1;
    if ( sc >= *seg_size ) {
	*seg_size += 1000;
	seg = realloc ( seg, sizeof(struct segment)*(*seg_size) );
    }
    seg[sc].length = 0;
    seg[sc].addr = start;
    return seg;
}
/**************************************************************************/
/* Prepare to send back response.  Build standard response header.
 */
static int send_http_header ( char *stsline, char *content )
{
    int status;
#ifdef IS_MST
    struct private_ctx *ctx;
    int reslen;
    /*
     * Get pointer to thread's private data (particularly link).
     */
    GET_SPECIFIC ( private_context, ctx );
#endif
    /*
     * Enter CGI mode, set rundown to terminate mode on exit.
     */
#ifdef IS_MST
    status = mst_write ( ctx->link, "<DNETCGI>", 9, &reslen );
    if ( (status&1) == 0 ) { 
	abort_status = status; pthread_exit (&abort_status);
    }
    ctx->cgimode_active = 1;
#else
    status = net_link_write ( "<DNETCGI>", 9 );
    if ( 0 == (status&1) ) exit ( status );
    status = net_link_set_rundown ( "</DNETCGI>" );
#endif
    /*
     * Send back standard header.
     */
    if ( status&1 ) status = net_link_printf ( 
	"Content-type:%s\nstatus: %s\n\n", content, stsline );

    return status;
}
/*****************************************************************************/
static int extract_part (
	char *fname,	/* pathname of file being parsed */
	char *partname,	/* name of part to extract */
	int *outlen, 
	char **outbuf )
{
    char *ibuf, *tag, *name;
    int length, buf_used, buf_size;
    int state, tagstate, tagstart, isquoted, ibuf_i;
    char *partname_p;
    struct part_chunk { int start, end; } *parts;
    int current_part;
    int parts_size;
#ifdef IS_MST
    void *ifile;
    char errmsg[256];
    /*
     * Get pointer to private data.
     */
#else
    FILE *ifile;
#endif
    *outlen = 0;
    /*
     * Open the file.
     */
    length = strlen ( fname );
#ifdef IS_MST
    ifile = (length > 0) ? tf_open ( fname, "r", errmsg ) : (void *) 0;
    if ( ifile ) {
	/* Update last mod date in ctx if later than existing */
	int size;  unsigned uic, cdate, mdate;
	struct private_ctx *ctx;
	GET_SPECIFIC ( private_context, ctx )
	tf_header_info ( ifile, &size, &uic, &cdate, &mdate );
	if ( mdate > ctx->last_mod ) ctx->last_mod = mdate;
    }
#else
    ifile = (length > 0) ? fopen ( fname, "r", "mbc=32" ) : (FILE *) 0;
#endif
    if ( !ifile ) {
	error_abort ( &includefail, "text/plain" );
#ifdef IS_MST
   	net_link_printf 
	    ( "Could not open include part file (%s)\n%s", fname, errmsg );
#else
   	net_link_printf ( "Could not open include part file (%s)\n%s", fname,
			     strerror ( errno, vaxc$errno ) );
#endif
	return -1;
    }
    buf_size = 10000;
    buf_used = 0;
    ibuf = malloc ( buf_size );

    /* I make sure to allocate 8192 bytes, because that's the minimum memory
       page on an Alpha, and malloc() allocates full memory pages anyway... */
    parts_size = 8192 / sizeof (struct part_chunk);
    parts = malloc ( parts_size * sizeof ( struct part_chunk ) );
    current_part = 0;
    parts[current_part].start = -1;
    parts[current_part].end = 0;

    state = 0;
    tagstate = 10;		/* 10 when looking for <!--#begin word-->,
				   20 when looking for <!--#end word--> */
    isquoted = 0;
    ibuf_i = 0;
#ifdef IS_MST
    while ( (length=tf_read(ifile,&ibuf[buf_used], buf_size-buf_used)) > 0) {
#else
    while ( (length=fread(&ibuf[buf_used], 1, buf_size-buf_used, ifile)) > 0) {
#endif
   	if (parts[current_part].end == buf_used)
	    parts[current_part].end += length;
	buf_used += length;
	if ( buf_used >= buf_size ) {
	    buf_size += 10000;
	    ibuf = realloc ( ibuf, buf_size );
	}
   	for (; state >= 0 && ibuf_i < buf_used; ibuf_i++) {
	    char cur = ibuf[ibuf_i];
#ifdef DEBUG
	    printf ("ibuf_i{1,2,3} = {%d, %d, %d}, ",
		    parts[current_part].start, ibuf_i,
		    parts[current_part].end);
	    printf ("state = %d, ", state);
	    printf ("tagstate = %d, tagstart = %d, ", tagstate, tagstart);
	    printf ("cur = '%c' (0%%x%X), *partname_p = '%c' (0%%x%X)\n",
		    (cur & 127) >= 32 ? cur : '.', cur,
		    (*partname_p & 127 ) >= 32 ? *partname_p : '.',
		    *partname_p);
#endif
	    switch (state) {
   		case 0:
   			if (cur == '<') { state = 1; tagstart = ibuf_i; }
   			break;
   		case 1:
   			if (cur == '!') { state = 3; break; }
   			state = 2;
   		case 2:
   			if (cur == '>') state = 0;
   			break;
   
   		case 3:
   			if (cur != '-') { state = 2; ibuf_i--; break; }
   			state = 4;
   			break;
   		case 4:
   			if (cur != '-') { state = 2; ibuf_i--; break; }
   			state = 5;
   			break;
   		case 5:
   			if (cur != '#') { state = 2; ibuf_i--; break; }
   			state = tagstate;
   			break;
   
   		case 6:		/* Find terminator */
   			if (cur == '-') state = 7;
   			else if (tagstate > 0 && !isspace(cur) && cur != '\n')
   			    state = 2;
   			break;
   		case 7:
   			if (cur == '-') state = 8;
   			else if (tagstate > 0) state = 2;
   			else state = 6;
   			break;
   		case 8:
   			if (isspace(cur) || cur == '\n') break;
   			if (cur != '>') {
   			    tagstate = -tagstate; state = 6; break;
   			}
   			state = 0;
#ifdef DEBUG
		        { int saved_part = current_part;
			  printf ("before: tagstate = %d, part = %d, start = %d, end = %d\n",
				  tagstate, saved_part,
				  parts[current_part].start,
				  parts[current_part].end);
#endif
			  if (tagstate < 0) {
			      /* restart from the beginning */
			      tagstate = -tagstate;
			      if (tagstate == 20)
				  parts[current_part].end = buf_used;
			  } else if (tagstate == 10) {
			      parts[current_part].start = tagstart;
			      parts[current_part].end = buf_used;
			      tagstate = 20;
			  } else if (tagstate == 20) {
			      parts[current_part++].end = ibuf_i + 1;

			      if ( current_part >= parts_size ) {
				  parts_size += 8192 / sizeof ( struct part_chunk );
				  parts = realloc ( parts, parts_size * sizeof ( struct part_chunk ) );
			      }

			      /* -1 indicates it's not been set yet */
			      parts[current_part].start = -1;
			      parts[current_part].end = buf_used;
			      tagstate = 10;
			  }
#ifdef DEBUG
			  printf ("after: tagstate = %d, part = %d, start = %d, end = %d\n",
				  tagstate, saved_part,
				  parts[current_part].start,
				  parts[current_part].end);
		        }
#endif
   			break;

   		case 10:
   			if (isspace(cur) || cur == '\n') break;
   			if (cur != 'b' && cur != 'B') { state = 2; break; }
   			state = 11;
   			break;
   		case 11:
   			if (cur != 'e' && cur != 'E') { state = 2; break; }
   			state = 12;
   			break;
   		case 12:
   			if (cur != 'g' && cur != 'G') { state = 2; break; }
   			state = 13;
   			break;
   		case 13:
   			if (cur != 'i' && cur != 'I') { state = 2; break; }
   			state = 14;
   			break;
   		case 14:
   			if (cur != 'n' && cur != 'N') { state = 2; break; }
   			state = 30;
   			partname_p = partname;
   			break;
   
   		case 20:
   			if (isspace(cur) || cur == '\n') break;
   			if (cur != 'e' && cur != 'E') { state = 2; break; }
   			state = 21;
   			break;
   		case 21:
   			if (cur != 'n' && cur != 'N') { state = 2; break; }
   			state = 22;
   			break;
   		case 22:
   			if (cur != 'd' && cur != 'D') { state = 2; break; }
   			state = 30;
   			partname_p = partname;
   			break;
   
   		case 30:
   			if (isspace(cur) || cur == '\n') break;
   			partname_p = partname;
			state = 31;
			isquoted = 0;
   			if (cur == '"') { isquoted = 1; break; }
   		case 31:
   			if (*partname_p == cur) { 
			    partname_p++; 
			    break;
			}
			if ( (*partname_p == '\0') ) {
			    if ( isquoted ) {
				if ( cur == '"' ) { state = 33; break; }
			    } else {
				if ( (cur == '-') || isspace(cur) || 
					(cur == '\n') ) {
				    if ( cur == '-' ) ibuf_i--;
				     state = 33; break;
				}
			    }
			}
			if ( cur == '-' ) {
			    tagstate = -tagstate; ibuf_i--; state = 6; break;
			}
			partname_p = partname;	/* restart search */
			if ( isspace(cur) || (cur == '\n') ) state = 30;
			else state = 32;
		    break;
		case 32:	/* remainder of rejected label label */
		    if ( isspace(cur) || (cur == '\n') ) state = 30;
		    if ( cur == '-' ) { 
			ibuf_i--; tagstate = (-tagstate); state = 6; }
   		    break;
		case 33:
		    if ( cur == '-' || cur == '>' ) { state = 6; ibuf_i--; }
		    break;
   	    }
   	}
	if (tagstate == 20 && state == -1) break;
    }
    /*
     * rundown file.
     */
#ifdef IS_MST
    tf_close ( ifile );
#else
    fclose(ifile);
#endif
    {
      int i, tmp;
      for (i = 0, ibuf_i = 0; i <= current_part; i++)
	  if (parts[i].start >= 0) {
	      /*
	       * Copy contents.
	       */
	      strncpy(ibuf + ibuf_i,
		      &ibuf[parts[i].start],
		      tmp = parts[i].end - parts[i].start);
	      ibuf_i += tmp;
	  }
      if (ibuf_i) buf_used = ibuf_i;
    }
    ibuf[buf_used] = '\0';
    *outbuf = ibuf;
    *outlen = buf_used;
    return *outlen;
}
#ifdef IS_MST
/*****************************************************************************/
/* Read named file into memory.  Return -1 on error, 2 on success.
 */
static int load_file ( char *fname, char *lname, int *outlen, char **outbuf, 
	int initial )
{
    char *ibuf;
    int length, buf_used, buf_size, cache_allowed, cache_status;
    struct private_ctx *ctx;
    void *ifile;
    struct tfc_context tfc;
    char errmsg[256];
    /*
     * Get pointer to private data.
     */
    GET_SPECIFIC ( private_context, ctx )
    /*
     * cache status: 0-nocache, 1-reading from cache, 2 writing cache.
     */
    cache_status = tfc.mode = 0;
    if ( *fname && ctx->cache_allowed ) {
	cache_status = tfc_cached_open ( 1, fname, &ifile, errmsg, &tfc );
#ifdef DEBUG
	printf("Cache lookup on %s, status: %d\n", fname, cache_status );
#endif
    } else ifile = *fname ? tf_open ( fname, "r", errmsg ) : (void *) 0;
    
    if ( ifile ) {
	/* Update last mod date in ctx */
	int size;  unsigned uic, cdate, mdate;
	tf_header_info ( ifile, &size, &uic, &cdate, &mdate );
	if ( mdate > ctx->last_mod ) ctx->last_mod = mdate;
    } else if ( cache_status == 1 ) {
	/* Update last mod date from tfc header */
	if ( tfc.hdr.mdate > ctx->last_mod ) ctx->last_mod = tfc.hdr.mdate;
    }
    if ( !ifile && (cache_status != 1) ) {
	error_abort ( initial ? &loadfail : &includefail, "text/plain" );
   	net_link_printf ( initial ? 
		"Could not open file for '%s' HTML pre-processing\n%s" :
		"Could not open include file (%s)\n%s",  lname,	errmsg );
	return -1;
    }
    buf_used = 0;
    if ( ifile ) {
	/*
	 * Read file contents.
	 */
       buf_size = initial ? 20000 : 10000;
       ibuf = malloc ( buf_size );
        while ( ibuf &&
	    (length=tf_read(ifile,&ibuf[buf_used], buf_size-buf_used)) > 0) {
	    buf_used += length;
	        if ( buf_used >= buf_size ) {
	        buf_size += (initial ? 20000 : 10000);
	        ibuf = realloc ( ibuf, buf_size );
   	    }
        }
	tf_close ( ifile );
	if ( cache_status == 2 && ibuf ) {
	    /*
	     * Save contents in cache in 4000 byte chunks.
	     */
	    int i, rec_size;;
	    for ( i = 0; i < buf_used; i += rec_size ) {
		rec_size = buf_used-i;
		if ( rec_size > 4000 ) rec_size = 4000;
		if ( 1 != tfc_put ( &tfc, &ibuf[i], rec_size ) ) break;
	    }
	}
    } else if ( cache_status == 1 ) {
	/*
	 * copy cache contents.
	 */
	buf_size = tfc.hdr.size;
	ibuf = malloc ( buf_size );
	for ( buf_used = 0; ibuf && (buf_used < buf_size); buf_used+=length) {
	    void *rec;
	    if ( 1 != tfc_get ( &tfc, &rec, &length ) ) break;
	    memcpy ( &ibuf[buf_used], rec, length );
	}
    }
    /*
     * Delete cache entry on error (null ibuf)
     */
    if ( tfc.mode ) tfc_rundown_context ( &tfc, ibuf ? 0 : 1 );

    *outbuf = ibuf;
    *outlen = buf_used;
    if ( !ibuf ) {
        error_abort ( &allocfail, "text/plain" );
   	net_link_printf ( "Memory allocation failure while loading %s", lname);
	return -1;
    }
    return 2;
}
#else /* no MST */
/*****************************************************************************/
/* Read named file into memory.  Return -1 on error, 2 on success.
 */
static int load_file ( char *fname, char *lname, int *outlen, char **outbuf, 
	int initial )
{
    char *ibuf;
    int length, buf_used, buf_size;
    FILE *ifile;

    ifile = *fname ? fopen ( fname, "r", "mbc=32" ) : (FILE *) 0;

    if ( !ifile ) {
	error_abort ( initial ? &loadfail : &includefail, "text/plain" );
   	net_link_printf ( initial ?
		"Could not open file for '%s' HTML pre-processing\n%s" :
			"Could not open include file (%s)\n%s", lname,
			     strerror ( errno, vaxc$errno ) );
	return -1;
    }
    buf_used = 0;
    buf_size = initial ? 20000 : 10000;
    ibuf = malloc ( buf_size );
    while ( ibuf &&
	    (length=fread(&ibuf[buf_used], 1, buf_size-buf_used, ifile)) > 0) {
	buf_used += length;
	if ( buf_used >= buf_size ) {
	    buf_size += (initial ? 20000 : 10000);
	    ibuf = realloc ( ibuf, buf_size );
   	}
    }
    fclose(ifile);

    *outbuf = ibuf;
    *outlen = buf_used;
    if ( !ibuf ) {
	error_abort ( &allocfail, "text/plain" );
   	net_link_printf ( "Memory allocation failure while loading %s", lname);
	return -1;
    }
    return 2;
}
#endif /* MST */
/*****************************************************************************/
/* Handle the file-related server directives (include, fsize, flastmod).
 */
static int do_include ( int opcode,	/* 1 - include, 2-fsize, 3-flastmod */
	char *path,	/* pathname of file being parsed */
	char **drctv,		/* Parsed directive tokens 1=tag, 2=fname */
	int *outlen, 
	char **outbuf )
{
    char *ibuf, *tag, *name, fname[600];
    int length, buf_used, buf_size;
    int have_part;
#ifdef IS_MST
    struct private_ctx *ctx;
    void *ifile;
    /*
     * Get pointer to private data.
     */
    GET_SPECIFIC ( private_context, ctx )
#else
    FILE *ifile;
#endif
    /*
     * Examine field1 argument to determine include type.
     */
    tag = drctv[1];
    name = drctv[2];
    *outlen = 0;

    have_part = 0;
    if ( opcode == 1 && 0 == strncmp ( drctv[3], "PART", 5 ) ) {
	have_part = 1;

#ifdef DEBUG
   	printf("Part name is %s\n", fname);
#endif
    }

    if ( 0 == strncmp ( tag, "FILE", 5 ) ) {
	/*
	 * Construct filename relative to current path.
	 */
	strncpy ( fname, path, 255 ); fname[255] = '\0'; 
	for ( length = strlen(fname); 
		(length > 0) && (fname[length] != '/'); --length );
	length++;
	strcpy ( &fname[length], name );
	length = strlen ( fname );
	if ( length > 0 ) if ( fname[length-1] == '"' ) fname[length-1] = '\0';

    } else if ( 0 == strncmp ( tag, "VIRTUAL", 8 ) ) {
	/*
	 * Have server translate name.
	 */
	int status;
#ifdef IS_MST
	mst_write ( ctx->link, "<DNETXLATEV>", 12, &length );
#else
	net_link_write ( "<DNETXLATEV>",  12 );
#endif
	status = net_link_query ( name, fname, sizeof(fname)-1, &length);
	if ( 0 == (status&1) ) length = 0;
	fname[length] = '\0';
    }
    /*
     * Take action based upon opcode.
     */
    if ( opcode == 1 && !have_part ) {
	/*
	 * Include the file.
	 */
	int status;
	status = load_file ( fname, name, outlen, outbuf, 0 );
	if ( status < 0 ) return status;
	return status;
    } else if ( opcode == 1 && have_part ) {
	/*
	 * Scan file and return named part.
	 */
	length = extract_part ( fname, drctv[4], outlen, outbuf );
	return 2;

    } else {
	/*
	 * fsize(2) and flastmod(3).  Load stat structure.
	 */
	stat_t info;
	int status;
	LOCK_C_RTL
	status = (length > 0) ? stat ( fname, &info ) : -1;
	UNLOCK_C_RTL
	if ( status != 0 ) {
	    error_abort ( &statfail, "text/plain" );
	    net_link_printf ( "Could not read file attributes- (%s)\n", name );
	    return -1;
	}
	/*
	 * Format information.
	 */
	*outbuf = malloc ( 64 );
	if ( opcode == 2 ) {
	    sprintf ( *outbuf, "%d", info.st_size );
	} else {
	    /*
	     * Check if user included FMT tag.
	     */
	    char fmt[100];
	    fmt[0] = '\0';
	    if ( strncmp(drctv[3],"FMT",4) == 0 ) {
		fmt[0] = '=';
		strncpy ( &fmt[1], drctv[4], sizeof(fmt)-2 );
		fmt[99] = '\0';
	    }
	    format_time ( *outbuf, 64, fmt, &info.st_mtime );
	}
	*outlen = strlen ( *outbuf );
    }
    return 1;
}
/*****************************************************************************/
/* Return accesses count for indicated path, updating count by 1 on first call
 * A version number less than or equal to 0 means ignore the version number.
 */
static int get_access_count ( char *path, int version ) {
#ifdef IS_MST
    IFILE *fp;
    struct private_ctx *ctx;
#define accesses_known ctx->accessesKnown
#define accesses ctx->Accesses
#define rec_version ctx->version
#else
    IFILE *fp;
    static int accesses_known = 0, accesses, rec_version;
#endif
    char accessesRecord[128];
    char accessesPath[104];
    char accessesStr[20];
    int status, i, new_rec;
    size_t length;
    stat_t info;
#ifdef IS_MST
    /*
     * Get private data.
     */
   GET_SPECIFIC ( private_context, ctx )
#endif
    /*
     * Reset accesses_known if version changes.
     */
    if ( accesses_known ) if ( (version > 0) && (version != rec_version) ) {
	accesses_known = 0;
    }

    if ( !accesses_known ) {
	/*
	 * Get/update access count/version in file.  Try to open file.
	 */
#ifdef IS_MST
	iacquire_db();		/* serialize access */
	ctx->have_lock = 1;
	fp = count_db ? count_db :
	    ifopen ( "www_root:[000000]accesses.dat","r+" );
#else
	fp = ifopen ( "www_root:[000000]accesses.dat","r+" );
#endif
	if ( !fp ) {
	    /*
	     * See if we have permission to write new records.
	     */
	    LOCK_C_RTL
	    status = stat ( path, &info );
	    UNLOCK_C_RTL
	    if ( status == 0 ) 	status = icheck_access 
		( "www_root:[000000]accesses.permissions", info.st_uid );
	    if ( (status&1) == 0 ) {
		error_abort ( &accessesfail, "text/plain" );
	        net_link_printf ( 
		    "No permission to create accesses.dat file, code %d\n",
			status );
	        return -1;
	    }
	    if ( (status&1) == 0 ) {
	    }
	    /*
	     * Attempt to create indexed file and retry open.
	     */
	    status = ifdlcreate 
		( access_file_fdl, "accesses.dat", "www_root:[000000]" );
	    if ( status & 1 ) 
		fp = ifopen ( "www_root:[000000]accesses.dat","r+" );
	}
	if ( !fp ) {
	    error_abort ( &accessesfail, "text/plain" );
	    net_link_printf ( "Could not open accesses.dat file.");
	    return -1;
	}
	/*
	 * File is now open, do indexed read to get record for specified path.
	 */
#ifdef IS_MST
	count_db = fp;		/* share amongst several threads */
	tu_strnzcpy ( accessesPath, path, 100 );
	tu_strupcase ( accessesPath, accessesPath );
	i = tu_strlen ( accessesPath );
#else
	for ( i = 0; (i < 100) && path[i]; i++ ) 
		accessesPath[i] = _toupper ( path[i] );
#endif
	while ( i < 100 ) accessesPath[i++] = ' ';	/* pad to 100 bytes */
	accessesPath[i] = '\0';

	status = ifread_rec(accessesRecord,120,&length,fp,0,accessesPath,100);
	if ( status & 1 ) {
	    accessesRecord[120] = '\0';
	    sscanf(accessesRecord+100,"%10d%10d",&accesses, &rec_version);
	    new_rec = 0;
	} else {
	    /*
	     * Read error on record, assume non-existent and make initial.
	     */
	    accesses = 0;
	    rec_version = 0;
	    new_rec = 1;
	    /*
	     * Check if file owner has permission to add to accesses.dat file.
	     */
	    LOCK_C_RTL
	    status = stat ( path, &info );
	    UNLOCK_C_RTL
	    if ( status == 0 ) 	status = icheck_access 
		( "www_root:[000000]accesses.permissions", info.st_uid );
	    if ( (status&1) == 0 ) {
		error_abort ( &accessesfail, "text/plain" );
	        net_link_printf ( 
		    "No permission to add records to accesses.dat, code %d\n",
			status );
	        return -1;
	    }
	}
        /*
	 * Reset or update access count by 1 and update record in file.
	 */
	if ( version <= 0 ) version = rec_version;
	else if ( version != rec_version ) accesses = 0;
	accesses++;

	sprintf(accessesRecord,"%-100.100s%10.10d%10.10d",accessesPath,accesses,
		version);
	if ( new_rec ) {
#ifndef IS_MST
	    iferror(fp);
#endif
	    status = ifwrite_rec(accessesRecord,120,fp);
	} else {
	    status = ifupdate_rec(accessesRecord,120,fp);
	}
#ifdef IS_MST
	ctx->have_lock = 0;
	irelease_db();
#else
	if ( (status&1) == 0 ) iferror(fp);
	ifclose(fp);
#endif
	accesses_known = 1;
    }
    return accesses;
}
#ifdef IS_MST
#undef accesses
#undef accesses_known
#undef rec_version
#endif
/*****************************************************************************/
/* Lookup indicate variables and return their values.
 */
static int do_echo ( char *path,	/* pathname of file being parsed */
	char **drctv,			/* Elements of directive */
	int *outlen, 
	char **outbuf )
{
    int SYS$ASCTIM(), timlen, i, j, length;
    char *timestr, *tag, *name;
    static enum vcodes { C_DATE_LOCAL, C_DOCUMENT_NAME, C_LAST_MODIFIED,
	C_ACCESSES, C_ACCESSES_ORDINAL, C_VMS_VERSION, C_HW_NAME,
	C_SERVER_ACCOUNT, C_SERVER_NAME, C_SERVER_VERSION, C_GETENV, C_FINIS };
    enum vcodes vcode;
    static struct {
	enum vcodes code;
	char delimiter; 
	int min_len;
	char *keyword;
    } vtbl[] = {
	{ C_DATE_LOCAL, '=', 10,  "DATE_LOCAL" },
	{ C_DOCUMENT_NAME, '\0', 13, "DOCUMENT_NAME" },
	{ C_LAST_MODIFIED, '=', 13,  "LAST_MODIFIED" },
	{ C_ACCESSES, ';', 8, "ACCESSES" },
	{ C_ACCESSES_ORDINAL, ';', 16,"ACCESSES_ORDINAL" },
	{ C_SERVER_NAME, '\0', 11, "SERVER_NAME" },
	{ C_SERVER_VERSION, '\0', 14, "SERVER_VERSION" },
	{ C_GETENV, '=', 6, "GETENV" },
	{ C_VMS_VERSION, '\0', 11, "VMS_VERSION" },
	{ C_HW_NAME, '\0', 7, "HW_NAME" },
	{ C_FINIS, '\0', 0, "" }
    };
    static char hardware[31], os_version[12];
    static $DESCRIPTOR(vms_version_dx, os_version);
    static $DESCRIPTOR(hardware_dx,hardware);
    tag = drctv[1];
    name = drctv[2];
    *outlen = 0;
    if ( strncmp ( tag, "VAR",4 ) ) return 0;	/* syntax error */

    /*
     * Lookup var keyword in table, putting it's code number in vcode.
     */
    length = strlen ( name );
    for ( vcode = C_FINIS, i = 0; vtbl[i].code != C_FINIS; i++ ) {
	if ( length >= vtbl[i].min_len ) {
	    j = vtbl[i].min_len;
	    if ( 0 == strncmp ( name,vtbl[i].keyword,j) ) {
		if ( name[j] == '\0' || name[j] == vtbl[i].delimiter ) {
		    vcode = vtbl[i].code;
		    break;
		}
	    }
	}
    }
    /*
     * Process based upon vcode.
     */
    switch ( vcode ) {
	stat_t info;
	int status, code, accesses, version, last, last2;
	char *env_val;
      case C_DATE_LOCAL:
	/*
	 * See if format info was supplied.
	 */
	if ( name[10] == '=' ) {
	    time_t now;
	    *outbuf = malloc ( 64 );
	    now = time(NULL);
	    format_time ( *outbuf, 64, &name[10], &now );
	    *outlen = strlen ( *outbuf );
	} else {
	    struct { long l; char * a; } timebuf;
	    *outbuf = malloc ( 24 );
	    timebuf.l = 24;
	    timebuf.a = *outbuf;
	    SYS$ASCTIM ( outlen, &timebuf, 0, 0 );
	}
      break;

      case C_DOCUMENT_NAME:
	/* Echo filename of document (part to right of last /) */
	*outbuf = strrchr(path,'/');
	if ( *outbuf ) *outbuf = *outbuf+1; else *outbuf = path;
	*outlen = strlen ( *outbuf );
      break;

      case C_LAST_MODIFIED:
	/*
	 * Echo last modified date of current file path.
	 */
	LOCK_C_RTL
	status = stat ( path, &info );
	UNLOCK_C_RTL
	if ( status != 0 ) {
	    error_abort ( &statfail, "text/plain" );
	    net_link_printf ( "Could not read file attributes-- (%s)\n", path );
	    return -1;
	}
	/*
	 * Format information.
	 */
	*outbuf = malloc ( 64 );
	format_time ( *outbuf, 64, &name[13], &info.st_mtime );
	*outlen = strlen ( *outbuf );
	break;

      case C_ACCESSES:
      case C_ACCESSES_ORDINAL:
	/*
	 * Echo access count.
	 */
	/*
	 * see if name includes version number.
	 */
	version = 0;
	if ( (vcode == C_ACCESSES) && (name[8] == ';') ) 
		version = atoi(&name[9]);
	else if ( (vcode == C_ACCESSES_ORDINAL) && (name[16] == ';') )
		version = atoi(&name[17]);

	accesses = get_access_count ( path, version );
	if ( accesses < 0 ) return accesses;
	*outbuf = malloc ( 24 );
	if ( accesses < 1000 ) sprintf(*outbuf, "%d",accesses );
	else if ( accesses < 1000000 ) sprintf(*outbuf, "%d,%03.3d",
		accesses/1000, accesses%1000 );
	else sprintf ( *outbuf, "%d,%03.3d,%03.3d", accesses/1000000,
	    accesses%1000000/1000, accesses%1000 );
	if ( vcode == C_ACCESSES_ORDINAL ) {
	    /* tack on suffix */
	    static char *ordination[10] = { "th", "st", "nd", "rd",
		"th", "th", "th", "th", "th", "th" };
	    char *suffix;
	    last2 = accesses %100;
	    suffix = &(*outbuf)[strlen(*outbuf)];
	    last = accesses % 10;
	    if ( (last2 >= 11) && (last2 <= 13) ) last = 0;
	    strcpy ( suffix, ordination[last] );
	}
	*outlen = strlen ( *outbuf );
	break;

      case C_SERVER_NAME:
	*outbuf = malloc ( 256 );
	status = net_link_query ( "<DNETHOST>", *outbuf, 255, outlen );
	if ( (status&1) == 0 ) *outlen = 0;
	break;

      case C_SERVER_VERSION:
	*outbuf = malloc ( 256 );
	status = net_link_query ( "<DNETID2>", *outbuf, 255, outlen );
	if ( (status&1) == 0 ) *outlen = 0;
	else *outlen = strchr(*outbuf, ' ') - *outbuf;
	break;

      case C_GETENV:
	/*
	 * Return value of arbitrary logical or DCL symbol.
	 */
	if ( length < 8 ) break;	/* invalid syntax */
#ifdef IS_MST
	pthread_lock_global_np();
#endif
	env_val = getenv ( &name[7] );
	if ( env_val ) {
	    *outlen = strlen ( env_val );
	    *outbuf = malloc ( *outlen + 1 );
	    strcpy ( *outbuf, env_val );
	}
	else { *outbuf = "unknown"; *outlen = 7; }
#ifdef IS_MST
	pthread_unlock_global_np();
#endif
	break;

      case C_VMS_VERSION:
	code = SYI$_VERSION;
#ifdef IS_MST
	*outbuf = malloc ( sizeof(os_version)+1 );
	pthread_lock_global_np();
	LIB$GETSYI ( &code, 0, &vms_version_dx, outlen, 0, 0 );
	tu_strnzcpy ( *outbuf, vms_version_dx.dsc$a_pointer, *outlen );
	pthread_unlock_global_np();
#else
	*outbuf = os_version;
	LIB$GETSYI ( &code, 0, &vms_version_dx, outlen, 0, 0 );
	if ( *outlen < sizeof(os_version) ) os_version[*outlen] = '\0';
#endif
	break;

      case C_HW_NAME:
	code = SYI$_HW_NAME;
#ifdef IS_MST
	*outbuf = malloc ( sizeof(hardware)+1 );
	pthread_lock_global_np();
	LIB$GETSYI ( &code, 0, &hardware_dx, outlen, 0, 0 );
	tu_strnzcpy ( *outbuf, hardware_dx.dsc$a_pointer, *outlen );
	pthread_unlock_global_np();
#else
	*outbuf = hardware;
	LIB$GETSYI ( &code, 0, &hardware_dx, outlen, 0, 0 );
	if ( *outlen < sizeof(hardware) ) hardware[*outlen] = '\0';
#endif
	break;

      default:
	/* 
	 * Unknown var.
	 */
	*outbuf = malloc ( length + 3 );
	sprintf ( *outbuf, "?%s?", name );
	*outlen = length + 2;
	break;
    }
    return 1;
}
/*****************************************************************************/
/* Handle config directive.
 * Tags:
 *       verify=[0|1]
 */
static int do_config ( char *path,
	char *field1, char *field2,
	int *outlen, 
	char **outbuf )
{ 
    if ( strncmp ( field1, "VERIFY", 7) == 0 ) {
	/*
	 * Set tag verify global that governs whether tag will be included.
	 */
#ifdef IS_MST
	struct private_ctx *ctx;
	GET_SPECIFIC ( private_context, ctx )
	if ( *field2 == '1' || *field2 == 'Y' ) ctx->tag_verify = 1;
	else ctx->tag_verify = 0;
#else
	if ( *field2 == '1' || *field2 == 'Y' ) tag_verify = 1;
	else tag_verify = 0;
#endif
    }
    *outlen = 0; *outbuf=" "; return 0; 
}
/*****************************************************************************/
/* Top routine to handle interpreting an server-side html directive,
 * optionally returning to the caller a pointer to buffer containing
 * additional data to send to the client (inserted at the point of
 * the directive).  A zero outlen indicates no optional data is returned.
 *
 * The directive takes the form <!--#command [tag1=value [tag2=value]]-->
 *
 * Note that tag array is modified by this routine.
 * Note also that output buffers must be statically allocated, they are not
 * read by the caller until final processing.
 *
 * If this routine generates an error response, it must return -1 as the
 * function value.
 */
static int parse_directive ( 
	char *path,			/* Pathname of file being parsed */
	char **drctv,			/* 5 elements of parsed directive */
	int *outlen, 			/* Size of result output buffer */
	char **outbuf )			/* Address of result buffer */
{
    int i, taglen;
    char *command;
    /*
     * Scan the directive tag and parse into command and tags.
     */
#ifdef DEBUG
printf("Directive: '%s' '%s'='%s' '%s'='%s'\n\n", drctv[0], drctv[1],
drctv[2],drctv[3],drctv[4] );
#endif
    command = drctv[0];
    /*
     * Now interpret command.
     */
    if ( 0 == strncmp(command,"INCLUDE",8) ) {
	/*
	 * Include file.
	 */
	return do_include ( 1, path, drctv, outlen, outbuf );

    } else if ( 0 == strncmp ( command, "ECHO",5 ) ) {
	/*
	 * Echo will insert values of special variables into stream.
	 */
	return do_echo ( path, drctv, outlen, outbuf );
    } else if ( 0 == strncmp(command,"FSIZE",6) ) {
	/*
	 * File attributes.
	 */
	return do_include ( 2, path, drctv, outlen, outbuf );
    } else if ( 0 == strncmp(command,"FLASTMOD",9) ) {
	return do_include ( 3, path, drctv, outlen, outbuf );

    } else if ( 0 == strncmp ( command, "CONFIG",7 ) ) {
	/*
	 * Set configuration parameters.
	 */
	return do_config ( path, drctv[1], drctv[2], outlen, outbuf );
    } else if ( 0 == strncmp ( command, "BEGIN", 6 )
	       || 0 == strncmp ( command, "END", 4 ) ) {
	/* For some reason, I HAVE to put one space in the output
	   buffer.  If I just do '*outlen = 0;', the ending ">" will
	   be preserved in the .htmlx file.  If I do '*outbuf = "";
	   *outlen = 0;', everything after on of these directives
	   will be ignored...  --  Richard Levitte  */
	*outbuf = " ";
	*outlen = 0;		/* fixed bug, so can use zero length, DLJ */
	return 1;
    }
    /*
     * Invalid directive in file.
     */
    error_abort ( &baddirective, "text/plain" );
    net_link_printf("invalid directive: (%s %s %s)\n", command, drctv[1],
		drctv[2] );
    *outlen = 0;
    return -1;
}
/*****************************************************************************/
/* Parse HTML tag.  If HTML tag looks like a pre-processor directive,
 * fill in targ array with parsed elments. (command, (tag,value) pairs).
 *
 * Return value:
 *	0	Tag is NOT a pro
 */
static int parse_tag ( char *tag, 	/* start of tag */
    int maxlen, 			/* remaining bytes in input file */
    int *taglen, 			/* Size of tag, including closing '>'*/
    char **targ )			/* Elements, cmd and up to 5 tag/value
					   pairs */
{
    int state, i, j, eot_ok;
    char *directive, cur;
    /*
     * First, determine if tag if directive or not, Size of tag is
     * also determined.
     */
    if ( *tag != '<' ) { *taglen = 0; return 0; }

    for ( state = i = 0; (state >= 0) && (i < maxlen); i++ ) {
	cur = tag[i];
	switch (state)  {
	  case 0:
	    if ( cur != '<' ) { *taglen = i+1; return 0; }
	    state = 1;
	    break;
	   case 1:
	     if ( cur == '!' ) { state = 3; break; }
	     state = 2;
	   case 2:	/* Not a directive, search for end. */
	     if ( cur == '>' ) { *taglen = i+1; return 0; }
	     break;

	   case 3:
	     if ( cur != '-' ) { state = 2; --i; break; }
	     state = 4;
	     break;
	   case 4:
	     if ( cur != '-' ) { state = 2; --i; break; }
	     state = 5;
	     break;
	   case 5:
	     if ( cur != '#' ) { state = 2; --i; break; }
	     state = 6;
	     break;

	   case 6:		/* Find terminator */
	     if ( cur == '-' ) state = 7;
	     break;
	   case 7:
	     if ( cur == '-' ) state = 8; else state = 6;
	     break;
	   case 8:
	    if ( cur == '>' ) state = -1; else state = (cur=='-') ? 8 : 6;
	    break;
	}
    }
    /*
     * Getting to this point means we parsed a directive (state = -1).
     * Make copy of tag and parse the copy (portions of copy will be upcased).
     */
    directive = malloc ( i + 1 );
    strncpy ( directive, tag, i );
    directive[i] = '\0';
    tag = directive;
#ifdef DEBUG
printf("comment: '%s' l = %d\n", directive, i );
#endif
    /*
     * Initialize return array to all null strings.
     */
    *taglen = i;
    targ[0] = &tag[5];
    for ( i = 1; i < 11; i++ ) targ[i] = "";
    eot_ok = 1;
    /*
     * Parse tag into directive components using state machine.
     */
    for ( state = j = 0, i = 5; (state >=0) && (i < *taglen-3); i++ ) {
#ifdef DEBUG
printf("state = %d, tag[%d] = '%c'\n", state, i, tag[i] );
#endif
	cur = tag[i];
	switch ( state ) {
	   case 0:
		if ( isspace(cur) || (cur=='\n') )  {
		    /* found end of targ[0] */
		    tag[i] = '\0'; 
#ifdef IS_MST
		    tu_strupcase ( tag, tag );
#endif
		    state = 1;
		    j = 1;
		    targ[j] = &tag[i]; 
		}
#ifndef IS_MST
		else tag[i] = _toupper(cur);
#endif
	        break;

	   case 1:			/* look for start of targ[j] */
		if ( !isspace(cur) /* || (cur == '\n') */ ) {
		    targ[j] = &tag[i];
		    state = 2;
		    eot_ok = 0;
		}
		else break;
	   case 2:			/* look for end of targ[1] */
		if ( cur == '=' ) { 
		    tag[i] = '\0'; 
		    state = 3;
#ifdef IS_MST
		    tu_strupcase ( targ[j], targ[j] );
#endif
		}
		else if ( isspace(cur) ) state = -1; /* syntax error */
#ifndef IS_MST
		else tag[i] = _toupper(cur);
#endif
		break;

	   case 3:			/* Check for proper syntax */
		if ( cur == '"' ) {
		    j++;		/* Advance to value part */
		    targ[j] = &tag[i+1];
		    state = 4;
	 	} else state = -1;
		break;
	    case 4:
		if ( cur == '"' ) {	/* end of targ[2] */
		    tag[i] = '\0';
		    j++;
		    targ[j] = &tag[i];
		    state = 1;		/* look for next value */
		    eot_ok = 1;
		}
		break;
	}
    }
    if ( state == -1 ) {
	free ( directive );
	return 0;	/* improper syntax */
    }
    return 1;
}
/*****************************************************************************/
/*
 * Scan file specification to see if the filename contains an embedded
 * part name.  If a partname is present, copy the partname to a separate
 * string and reconstruct filename without the embedded partname.
 *
 * Function value returned is 1 if partname present and 0 if not.
 */
static int parse_part_name ( char *fname, char **pfile, char **pname )
{
    int length, i, dot1, dot2;
    char cur, *tail;
    /*
     * Parse from back, looking for postiions in fname of 1st 2 dots after
     * the last slash.
     */
    dot1 = dot2 = -1;
    length = strlen ( fname );
    for ( i = length - 1; i >= 0; --i ) {
	cur = fname[i];
	if ( (cur == '/') || (cur == '>') || (cur == ']') ) break;
	if ( cur == '.' ) {
	    dot2 = dot1;
	    dot1 = i;
	}
    }
    *pfile = fname;
    if ( dot2 < 0 ) return 0;		/* normal filename */
    if ( fname[dot2+1] >= '0' && fname[dot2+1] <= '9' ) return 0;
    /*
     * Reconstruct filename without part and put part in separate.
     */
    *pname = malloc ( dot2 - dot1 );
    strncpy ( *pname, &fname[dot1+1], (dot2-dot1-1) );
    (*pname)[dot2-dot1-1] = '\0';

    *pfile = malloc ( length +1 );
    strncpy ( *pfile, fname, dot1 );		/* all before 1st dot */
    strcpy ( (*pfile)+dot1, &fname[dot2] );	/* rest */
    if ( (dot1+1) == dot2 ) return 0;		/* part name null */
    return 1;
}
