/***********************************************************************/
/* Main routine for handling http server connection.  This function is called 
 * as the thread init routine for tcp-ip server threads.
 *
 * Author:  David Jones
 * Date:    ??-JUL-1997
 * Revised: 21-OCT-1997		Modify read_request_header to deal with timing
 *			 	problem in tu_readline.
 * Revised: 14-feb-1998		Added port_attributes support.
 * Revised: 16-FEB-1998		Support proxyscript.
 * Revsied: 25-FEB-1998		Add http_set_port_attributes function.
 * Revised: 9-OCT-1998		Increased max response header line size to 2048.
 * Revised: 22-APR-1999		Don't include unixlib.h for OSF build.
 * Revised:20-JAN-2000		Fix buffer overflow
 * Revsied: 27-APR-2000		Support response_code option in error page.
 * Revised:  8-AUG-2000		Preserve spaces in user-agent field in logs.
 * Revised: 8-DEC-2001		Use tu_addrtoasc function to format address.
 * Revised: 11-JAN-2002		Redo set_port_attributes function, ndx is
 *				now is_nocache flag and ports specified are
 *				appended to http_ports[] list.
 */
#include <stdio.h>
#ifdef VMS
#include <unixlib.h>
#endif
#include <stdlib.h>
#include "fast_utc.h"		/* Timezone fixup */
#include "session.h"		/* Session control block definition */
#include "tserver_tcp.h"	/* TCP/IP I/O routines */
#include "http_header.h"	/* request header parse support */
#include "counters.h"
#include "multihome.h"
#include "file_access.h"	/* included for time formating functions */
#include "errorpage.h"

int http_log_level;		/* Global variable, logging control */
char *http_default_host;
int tlog_putlog();			/* Logging output routine */
#define MAX_LOG_LINE 512
int http_dns_enable;			/* Global variable, name lookup enable */
int *http_log_extension;		/* Additional flags for extended log */
int http_keepalive_limit;		/* Max keepalives allowed or 0 */
int http_keepalive_time;		/* time limit for keepalive */
int http_request_time_limit;		/* Max time to wait for request */
int http_response_time_limit;		/* timeout for sending data */
int http_min_available;			/* Threshold for rejecting connects */
#define PORT_LIMIT 9
int http_ports[PORT_LIMIT];		/* List of ports to listen on */
int http_nocache_ports[PORT_LIMIT];
char *http_port_attributes[PORT_LIMIT];
char *http_gateway_script;		/* script path to invoke proxy script */
int http_gateway_log;

extern char http_server_version[];	/* Current server version */
int http_add_response(), http_send_response_header(), http_get_method();
int http_parse_elements();

#define MAX_RSP_HDR_LINE 2048
#define METHOD 0
#define URL 1
#define PROTOCOL 2
static int read_request_header ( string *request, tu_stream inbound,
	char *buffer, int bufsize, int *lines );

static int process_request ( void *, char *, char *, int, string *, tu_stream );
static int keepalive_scan ( session_ctx scb, char *iobuffer, int bufsize );

int http_session ( void *ctx, 		/* TCP connection context */
	short port, 			/* Port connection received on */
	unsigned char *rem_address, 	/* Address/port of client */
	int ndx,			/* Thread index */
	int available )			/* # of contexts left on  free list */
{
    int length, status, seg_len, trans_count, tlog_flush();
    int i, lines, ts_tcp_write(), ts_tcp_read(), rem_port;
    long start_time[2], end_time[2];
    string request[128];
    char reqbuf[8192];
    char *opcode, *method, *url, *protocol, *port_attributes, log_prefix[32];
    char *ts_tcp_remote_host();
    struct tu_streambuf inbound;
    struct tu_textbuf response;
    /*
     * Make prefix string for log entries so we can follow interleaved
     * entries in log file.  Log the connection and time.
     */
    tu_strcpy ( log_prefix, "TCP-" );
    tu_strint( (int)((unsigned) port), &log_prefix[4] );
    i = 4 + tu_strlen(&log_prefix[4]); log_prefix[i++] = '/';
    tu_strint(ndx, &log_prefix[i] );

    start_time[1] = -1;
    if (http_log_level > 0 ) {
#ifdef VMS
	int SYS$GETTIM();
	SYS$GETTIM ( start_time );
#endif
	tu_addrtoasc ( rem_address, 3, reqbuf, sizeof(reqbuf) );
	tlog_putlog ( 1, "!AZ connect from !AZ, !%D (!SL)!/", 
		log_prefix, reqbuf, start_time, available );
    }
    if ( http_dns_enable && (http_log_level > 0) ) tlog_putlog ( 1,
	"!AZ host address translates to name !AZ!/", log_prefix,
		ts_tcp_remote_host() );
    /*
     * Update statistics counters.
     */
    if ( http_counters ) {		/* Update server statistics */
	int lock_state = 0;
	if ( http_counters->active_size ) {
	    http_open_active_counter ( ndx, port, rem_address, &lock_state );
	}
	if ( http_counters->host )
		http_increment_host_counters ( rem_address, &lock_state );

	if ( lock_state ) http_unlock_counters();
    }
    /*
     * Initialize the input stream and determine port attributes.
     * Port attributes for SSL (443) inhibit keepalive.
     */
    status = tu_init_stream ( ctx, ts_tcp_read, &inbound );
    for ( i = 0; port != http_ports[i]; i++ ) {
	if ( http_ports[i] == 0 ) {
	    tlog_putlog ( 0, 
		"!AZ, bugcheck, local port !SL not on port list.!/",
		log_prefix, port );
	    return 20;
	}
    }
    port_attributes = http_port_attributes[i];
    /*
     * Loop through multiple transactions (HTTP requests) until an error
     * or zero status occurs.
     */
    for ( trans_count = 0; (status&1) == 1; trans_count++ ) {
	/*
	 * Update counters.  keep-alive zero counter is number of keep-alive
	 * transactions initiated.
	 */
	if ( http_counters ) {
	    int lock_state = 0;
	    if ( trans_count ) 
		http_increment_keepalive_counter ( 0, &lock_state );
	    if ( http_counters->active_size ) http_set_active_counter ( 
		ndx, "", (trans_count==0) ? "(reading request)":
		"(keep-alive wait)", &lock_state );
	    if ( lock_state ) http_unlock_counters();
	}
        /*
         * Read first line of request from network client and parse into
         * separate strings  Keep-alives allow nulls.
         */
	ts_tcp_set_time_limit ( ctx, 
		trans_count ? http_keepalive_time : http_request_time_limit );
        do {
            status = tu_read_line ( &inbound, reqbuf, (sizeof(reqbuf)/2)-21, &length );
	    if ( (status&1) == 0 ) {
	        /* Read error */
		length = 0;
	        i = http_parse_elements ( 3, "{EOF}", request );
	        if ( trans_count ) request[3].l = -9;
	        break;
	    }
        } while ( (trans_count > 0) && (length == 0) );

	if ( trans_count > 0 && length == 0 ) break;	/* read error */

        reqbuf[length++] = '\0';
        i = http_parse_elements ( 3, reqbuf, request );
        request[3].l = 0;
        /*
         * Reset timeout to normal request timeout if keepalive (disables
         * 'scavenge' mode if present.
         */
        if ( trans_count > 0 ) ts_tcp_set_time_limit ( ctx, 
		http_request_time_limit );
        /*
         * Validate command and fetch extended request info.
         */
	if ( available < http_min_available ) {
	    request[3].l = -2;
	    request[3].s = "502 Server overload, request aborted";
        } else if ( !read_request_header ( request, &inbound, &reqbuf[length],
		sizeof(reqbuf)-length, &lines ) ) break;

	/*
	 * Update counters.
	 */
        if ( http_counters ) {
            int lock_state = 0;
	    if ( http_counters->active_size ) {
	        /* record what the active thread is fetching */
	        http_set_active_counter ( ndx, request[METHOD].s, 
			request[URL].s,	&lock_state );
	    }
	    if ( trans_count ) http_increment_keepalive_counter ( trans_count,
		&lock_state );
	    if ( lock_state ) http_unlock_counters();
        }
        if ( http_log_level > 0 ) {
	    tlog_putlog ( 1,"!AZ http request: '!AZ' '!AZ', (!AZ)!/",
	    	log_prefix, request[0].s, request[1].s, request[2].s );
            if ( http_log_level > 2 ) tlog_putlog ( 1, 
		"!AZ      request stats: strings=!SL, bytes=!SL!/",
	    	log_prefix, lines, length );
	}
	if ( request[3].l < 0 )  {
	    /*
	     * Problem with request, return request[3].s as error message 
	     */
	    status = ts_tcp_write(ctx, request[3].s, tu_strlen (request[3].s));
	    break;
	}
	/*
	 * Process the request.
	 */
        if ( http_request_time_limit || http_keepalive_time ) 
            ts_tcp_set_time_limit ( ctx, http_response_time_limit );

	status = process_request ( 
		ctx, log_prefix, port_attributes, trans_count, 
			request, &inbound );

    }
    /*
     * Processing done, cleanup.
     */
    ts_tcp_close ( ctx );

    if ( start_time[1] != -1 ) {
#ifdef VMS
	long delta_time[2], divisor, offset, msec, LIB$EDIV(), LIB$SUBX();
	int SYS$GETTIM();
	SYS$GETTIM ( end_time );
	LIB$SUBX ( end_time,  start_time, delta_time );
	divisor = 10000;	/* millisecond */
	LIB$EDIV ( &divisor, delta_time, &msec, &offset );
	if ( offset >= 5000 ) msec;	/* round remainder */
#else
	long msec = 0;
#endif
	tlog_putlog ( 1,
	"!AZ connection closed with status: !SL at !%T (!SL)!/",
	log_prefix, status, end_time,  msec );
    }
    if ( http_counters ) if ( http_counters->active_size )
	http_close_active_counter ( ndx );
    tlog_flush();
    return status;
}

/****************************************************************************/
/* Read lines from client and build string descriptor for each line, storing
 * text in supplied reqbuf, until a zero-length line is read.  The record 
 * delimiter (lf or crlf) is not part of the string.  Terminate the request
 * array with a zero-length line.
 *
 * Lines are loaded starting at position 3.
 */
static int read_request_header ( string *request, tu_stream inbound,
	char *reqbuf, int reqbuf_size, int *line_count ) {
    char *protocol;
    int lines;

    protocol = request[PROTOCOL].s;
    if ( request[PROTOCOL].l == 0 ) {
	/*
	 * Assume simple request.
         */
	if ( request[3].l == -9 ) {
        } else if ( tu_strncmp ( request[METHOD].s, "GET", 4 ) ) {
	    request[3].l = -1;		/* indicate error */
	    request[3].s = "599 Invalid request";	/* detail */
	} else {
	    request[3].l = 0;
	    request[3].s = "";	/* Make null header line */
	}
	lines = 4;

    } else if ( (tu_strncmp ( protocol, "HTTP/", 5 ) == 0) ||
		(tu_strncmp ( protocol, "HTRQ/", 5 ) == 0) ) {
	/*
         * Protocols other than 1 are followed by header lines.
	 * Read lines until null line read.
         */
	int length, seg_len, status;
	length = 0;
	for ( lines = seg_len = 3; seg_len > 0; lines++ ) {
	    /*
	     * make sure we have enough room to read more.
	     */
	    if ( (lines >= 126) || (length >= reqbuf_size-2) ) {
		request[lines].l = 0; 
		request[lines].s = &reqbuf[length];
		break;
	    }
	    status = tu_read_line ( inbound, &reqbuf[length], 
			reqbuf_size-length-1, &seg_len );
    	    if ( (status&1) == 0 ) {
		seg_len = 0;
		request[3].l == -9;	/* flag read error */
		*line_count = lines;
		return 0;
	    }

	    request[lines].l = seg_len;
	    request[lines].s = &reqbuf[length];
	    length += seg_len+1;
	}
	if ( (inbound->state != 0) && (seg_len == 0) ) {
	    /*
	     * Special hack to get around case where client sends CR without
	     * accompanying LF in a timely fashion for a password-protected
	     * page (client sees FIN on connection prior to sending LF, so
	     * connection is 'broken'.  If internal state of inbound stream
	     * is expecting an LF, reset state and attempt to read last line
	     * (zero length) again to force the LF to be read.
	     */
	    inbound->state = 0;		/* UGLY, members should be private */
	    status = tu_read_line ( inbound, &reqbuf[length],
		reqbuf_size-length-1, &seg_len );
	    /*
	     * All bets off if we don't get a null line.
	     */
	    if ( (status&1) == 0 ) return 0;	/* read error */
	    if ( seg_len != 0 ) return 0;	/* non-null line */
	}
	if ( request[lines].l > 0 ) request[++lines].l = 0;
    } else {
	/*
	 * Unknown protocol in 3rd element.
	 */
	request[3].l = -1;
	request[3].s = "599 protocol error in request";
    }
    *line_count = lines;
    return 1;
}
/****************************************************************************/
/*
 * This module is the main code for processing a request received by
 * the http server.
 *
 * The request is specifed by an array of structures that point to strings.
 * The first 3 elements point to the 3 parts of the initial request line
 * (method, URL, protocol-version).  The remaining elements point to
 * header request lines.  This list is always terminated by a zero-length
 * line.  If the caller detected an error while generating the request,
 * the final line will have a negative length.
 *
 * Author: 	David Jones
 * Revised:	14-FEB-1998		Added port attributes parameter.
 */
static int process_request ( void *ctx,	/* opaque, context for tcp connection */
	char *log_prefix,		/* Tag for log file output */
	char *port_attributes,		/* Options for port processing */
	int trans_num,			/* transaction number (req #) */
	string *request,		/* Request to process. */
	tu_stream inbound )		/* Input character stream */
{
    int status, http_send_error(), http_send_document(), log_access();
    int http_parse_url(), http_translate_ident(), http_script_execute();
    int lock_state;
    char *service, *node, *ident, *suffix, *arg, *user_method;
    struct session_control_block scb;
    struct acc_info_struct acc;
    char url_buf[4096], rsphdrtxt[4096], munged_ident[300], iobuffer[4096];
    string iobuf;
    struct tu_textbuf rsphdr;
    /*
     * Initialize structure for holding response header lines.
     */
    rsphdr.l = 0;			/* current length */
    rsphdr.s = rsphdrtxt;		/* data storage */
    rsphdr.size = sizeof(rsphdrtxt);	/* Storage capacity */
    iobuf.l = sizeof(iobuffer);		/* Work area */
    iobuf.s = iobuffer;			/* Work area */
    if ( request[2].l > 0 ) {
	/*
	 * Set initial response header to HTTP protocol in use.
	 * For full compliance, version number should allow leading zeros!
	 */
	if ( (request[2].l>7) && (0==tu_strncmp(request[2].s,"HTTP/1.", 7)) ) {
	    tu_add_text ( &rsphdr, request[2].s[7] >= '1' ?
		"HTTP/1.1 " : "HTTP/1.0 ", 9 );
	} else tu_add_text ( &rsphdr, "HTTP/1.0 ", 9 );
    }
    /*
     * Build session control block, init access control structure.
     */
    scb.cnx = ctx;
    scb.request = request;
    scb.inbound = 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';
    if ( http_multihomed ) {
	/*
	 * Use special hack in ts_tcp_info to get local interface address,
	 * then call rule_file routine to translate back to host string.
	 */
	int local_port;
	unsigned int remote_address;
	if ( http_cname_count > 0 ) {
	    /*
	     * look for host: header record and translate into configured
	     * address.  Fallback to real address no match.
	     */
	    int length = 0;
	    status = http_extract_header ( http_std_atoms.host, &scb,
		iobuffer, sizeof(iobuffer)-1, &length );
            iobuffer[length] = '\0';
	    if ( 0 == http_cname_address ( iobuffer, &remote_address ) ) {
		if ( http_log_level > 10 ) tlog_putlog(11,
		    "Failed to locate cname '!AZ' in host list!/", iobuffer );
		ts_tcp_info ( &local_port, (int *) 0, &remote_address );
	    }
	}
	else ts_tcp_info ( &local_port, (int *) 0, &remote_address );

	http_multihomed_hostname ( remote_address, scb.acc, &scb.accesslog );
	if ( http_log_level > 2 ) tlog_putlog (3,
		"!AZ, IP interface used: !XL -> '!AZ' (log=!SL)!/", log_prefix, 
		remote_address, acc.local_address, scb.accesslog );
    } else {
	acc.local_address = http_default_host;
    }
    acc.port_attr = port_attributes;
    acc.retention = 0;			/* zero means use default value */
    acc.fopt.etype = 0;
    /*
     * Handle error case.
     */
    if ( request[3].l < 0 ) {
	if ( http_log_level > 1 ) tlog_putlog 
		( 1, "!AZ generating error response!/", log_prefix );
	if ( request[3].l == -2 ) {
	    status = http_send_error ( &scb, "503 Server overload",
		"Request rejected due to server congestion" );
	} else {
	    status = http_send_error ( &scb, "400 Bad request",
		"Syntax error or malformed request" );
	}
	return 0;
    }
#ifdef SHOW_HEADERS
    if ( http_log_level > 2 ) {
	int i;
	tlog_putlog ( 2, "!AZ Headers: !/", log_prefix );
	for ( i = 3; request[i].l > 0; i++ ) tlog_putlog ( 2,
		"!AZ    !AF!/", log_prefix, request[i].l, request[i].s );
	tlog_putlog( 2, "!/" );
    }
#endif
    /*
     * First attempt to translate URL.
     */
    if (request[1].l >= sizeof(url_buf)) request[1].s[sizeof(url_buf)-1] = '\0';
    status = http_parse_url ( request[1].s, url_buf, &service, &node, &ident, 
	&arg );
    if ( status ) {
	/*
	 * Check for gateway request (node field is non-blank).
	 */
	if ( *service || *node ) {
	    if ( http_gateway_script ) {
		ident = http_gateway_script;
		scb.accesslog = http_gateway_log;
	    } else {
	        status = http_send_error ( &scb, "501 Not implemented",
		    "Gateway function not available." );
	        return status;
	    }
	}
	/*
	 * Local ident, attempt to re-map according to local rules.
	 */
        status = http_translate_ident ( ident, munged_ident, 
			sizeof(munged_ident), scb.acc );
	if ( http_counters ) if ( http_counters->icb ) {
	     lock_state = 0;
	     http_increment_icb_requests ( status, &lock_state );
	     if ( lock_state ) http_unlock_counters();
	}
	if ( !status ) {
	    /*
	     * Error status means ident matched a 'fail' record in the
	     * the rule database.
	     */
	    http_ep_def *rulefail;
	    rulefail = &http_error_page.rulefail;
	    if ( rulefail->type != 0 ) {
		if ( rulefail->response_code[0] == '\0' ) {
		    tu_add_text ( scb.rsphdr, "403 ", 4 );
		} else if ( rulefail->response_code[0] != '*' ) {
		    tu_add_text ( scb.rsphdr, 
			rulefail->response_code,
			tu_strlen(rulefail->response_code) );
		}
		status = rulefail->type;
		tu_strnzcpy ( munged_ident,
			rulefail->munged_ident,
			sizeof(munged_ident)-1 );
		if ( status == 3 ) {
		    /*
		     * For scripts, append ident to munged_path.
		     */
		    int length, i;
		    char *tail;
		    tail = tu_strstr ( munged_ident, "*" );
		    *tail = '\0';
		    length = tu_strlen ( munged_ident );
		    tu_strnzcpy ( &munged_ident[length], ident,
			sizeof(munged_ident)-1-length );
		    tail = &rulefail->munged_ident[length];
		    length = tu_strlen ( munged_ident );
		    tu_strnzcpy ( &munged_ident[length], tail,
			sizeof(munged_ident)-1-length );
		}
		arg = "";
	    } else {
	        http_send_error ( &scb, "403 Forbidden",
		    "Access to object is _ruled_ out." );
	        return status;
	    }
	}
	
    } else {
	/*
	 * Parse failure on URL.
	 */
	if ( http_counters ) if ( http_counters->icb ) {
	    lock_state = 0;
	    http_increment_icb_requests ( 0, &lock_state );
	    if ( lock_state ) http_unlock_counters();
	}
	http_send_error 
		( &scb, "400 Bad URL","invalid URL specified" );
	return status;
    }
    /*
     * Check for "Connection: keep-alive" keeplive header if enabled.
     * If keepalive requested get our current generation.
     */
    if ( http_keepalive_limit  && acc.port_attr[0] == '+' ) {
	scb.keepalive_limit = keepalive_scan ( &scb, iobuffer,
		sizeof(iobuffer) );
	if ( scb.keepalive_limit ) scb.keepalive_count = trans_num;
    }
    /*
     * Take action depending upon method specified in request or punt
     * to HTBIN escape mechanism.
     */
    if ( status == 2 ) {
	/* Rule file redirect */
	tu_strcpy ( iobuffer, "Location: " );
	tu_strcpy ( &iobuffer[10], munged_ident );
	status = http_add_response ( &rsphdr, "302 rule file redirect", 0 );
#ifdef full_redirect
	if ( scb.keepalive_count < scb.keepalive_limit ) {
	    http_add_response ( scb.rsphdr, "Connection: Keep-Alive", 0 );
	    scb.keepalive_pending = 1;
	}
#endif
	status = http_add_response ( &rsphdr, iobuffer, 1 );
	status = http_send_response_header ( &scb );

    } else if ( status == 3 ) {
	/* Exec command. */
	status = http_script_execute 
		( &scb, "HTBIN", munged_ident, arg, &iobuf );
    } else if ( 0 == tu_strncmp ( request[0].s, "GET", 4 ) ) {
        status = http_send_document (&scb, ident, munged_ident, arg, &iobuf);

    } else if ( 0 == tu_strncmp ( request[0].s, "HEAD", 5 )  ) {
        status = http_send_document (&scb, ident, munged_ident, arg, &iobuf);

    } else if ( http_get_method ( request[0].s, &user_method ) ) {
	/*
	 * User-defined methods use the POST subfunction.  Create munged ident
	 * to look like search invocation (url**script-name).
	 */
	int pos;
	if (tu_strstr(munged_ident,"*")) *(tu_strstr(munged_ident,"*")) = '\0';
	pos = tu_strlen ( munged_ident );
	if ( pos + 3 + tu_strlen(user_method) < sizeof ( munged_ident ) )  {
	    munged_ident[pos++] = '*';
	    munged_ident[pos++] = '*';
	    tu_strcpy ( &munged_ident[pos], user_method );

            status = http_script_execute 
		( &scb, "POST", munged_ident, arg, &iobuf );
	} else {
	    /* Couldn't fit */
	    status = http_send_error ( &scb, "500 URL too large",
		"Request cannot be processed, URL path too large" );
	}
    } else {
	status = http_send_error ( &scb, "501 Not implemented",
		"Method not supported by this server" );
    }
    /*
     * If response included keep-alive: header, pass off connection to
     * a new thread.
     */
    if ( http_keepalive_limit ) {
	/*
         * Increment counters.  scb limit non-zero implies client offered
	 * to do keep-alive.  scb.keepalive_pending is true if granted.
	 */
	if ( http_counters && scb.keepalive_limit ) {
	    http_lock_counters();
	    http_counters->ka_offered++;
	    http_unlock_counters();
	}
    }
    /*
     * Log data transfer statistics.
     */
    if ( http_log_level > 2 )
    tlog_putlog ( 2, "!AZ      response stats: hdr = !SL, data = !SL bytes!/",
	log_prefix, rsphdr.l, scb.data_bytes );
    log_access ( &scb );

    if ( !scb.keepalive_pending && (status&1) == 1 ) status = 0;
    return status;
}
#ifndef VMS
#define SYS$NUMTIM SYS_NUMTIM
#endif
/**************************************************************************/
/*
 * Generate summary log line in common logfile format.
 * Format:
 * 	remotehost rfc931 authuser [date] "request" status bytes 
 */
int log_access ( session_ctx scb )
{
    char *stat_code, *hostname, *user;
    unsigned char host[4];
    int local_port, remote_port, SYS$NUMTIM(), stspos, status, length, len2;
    int host_len, user_len, method_len, path_len, protocol_len, ext_limit;
    string *request;
    time_t now;
    short int time[8];
    static futc_tcache timestamp_cache = FUTC_TCACHE_INIT;
    char numeric_host[16], time_str[28], extension[300];
    static char *month_names[12] = { "Jan", "Feb", "Mar", "Apr", "May",
		"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

    if ( scb->completed ) return 0;	/* Already logged */
    scb->completed = 1;			/* Mark as logged */
    /*
     * Update interval counter block if present.
     */
    if ( http_counters ) if ( http_counters->icb ) {
	http_lock_counters();
	http_counters->icb->responses++;
	http_counters->icb->data_bytes += scb->data_bytes;
	http_unlock_counters();
    }
    /*
     * Locate HTTP response within the header (skip version field of
     * response line).  If no status, use 000.
     */
     request = scb->request;
     stspos = request[2].l > 0 ? 9 : 0;
     if ( stspos+3 >= scb->rsphdr->l ) {
	stspos = scb->rsphdr->l+1;
	tu_strcpy ( &scb->rsphdr->s[stspos], "000" );
    }
    /*
     * Lookup hostname or format numerically.
     */
    if ( http_dns_enable ) {
	if ( http_dns_enable == 2 ) {
	    hostname = ts_tcp_remote_host();
	} else {
	    hostname = ts_tcp_remote_host();
	}
        host_len = tu_strlen(hostname);
     } else {
	/*
	 * format numeric host.
	 */
        ts_tcp_info ( &local_port, &remote_port, (unsigned int *) host );
	hostname = tu_strint ( host[0], numeric_host );
	host_len = tu_strlen ( hostname ); hostname[host_len++] = '.';
	tu_strint ( host[1], &hostname[host_len] );    
	host_len += tu_strlen ( &hostname[host_len] ); 
	hostname[host_len++] = '.';
	tu_strint ( host[2], &hostname[host_len] );    
	host_len += tu_strlen ( &hostname[host_len] ); 
	hostname[host_len++] = '.';
	tu_strint ( host[3], &hostname[host_len] );    
	host_len += tu_strlen ( &hostname[host_len] );
    }    
    /*
     * Compute remaining component lengths (change null user to "-");
     * Limit method and protocol to 30 characters max.
     */
    user = scb->acc->user;
    if ( *user ) user_len = tu_strlen(user);
    else { user = "-"; user_len = 1; }
    method_len = (request[0].l<30) ? request[0].l : 30;
    protocol_len = (request[2].l<30) ? request[2].l : 30;
    /*
     * Compute budget for request
     *    host - user [dd/mon/year:hh:mm:ss +0000] "mth path proto" rsp bytes
     *ovr:    123    4567890123456789012345678901234   5    6     789012     3
     *var:256    ??                                 30  300    30         10
     *
     * Total: 43 + 6 (6 is min overhead for log extension).
     */
    path_len = MAX_LOG_LINE - 49 - user_len - method_len - protocol_len -
	host_len;
    if ( path_len > request[1].l ) {
        ext_limit = path_len - request[1].l;
	path_len = request[1].l;
    } else ext_limit = 0;			/* put nothing in extension  */
    /*
     * Check for extended log.
     */
    extension[0] = '\n'; extension[1] = '\0';	/* no extension by default */
    if ( http_log_extension ) if ( http_log_extension[-1-scb->accesslog]&1 ) {
	/*
	 * Extension appends '"referer" "user-agent"' to end of log line
	 */
	extension[0] = ' '; extension[1] = '"';
	if ( ext_limit > sizeof(extension)-7 ) ext_limit=sizeof(extension)-7;
	if ( ext_limit > 0 ) {
	    status = http_extract_header ( http_std_atoms.referer, scb,
		&extension[2], ext_limit, &length );
	    if ( status <= 0 ) length = 0;
	    else ext_limit = ext_limit - length;
	    length += 2;
	} else {
	    length = 2;
	}
	tu_strcpy ( &extension[length], "\" \"" );
	length += 3;

	if ( ext_limit > 0 ) {
	    status = http_extract_header_with_space ( 
		http_std_atoms.user_agent, scb,
		&extension[length], ext_limit, &len2 );
	    if ( status <= 0 ) len2 = 0;
	} else len2 = 0;
	tu_strcpy ( &extension[length+len2], "\"\n" );
    }
    /*
     * Get current time and output formatted record.
     */
#ifdef VMS
    now = futc_current_time ( (time_t *) 0 );	/* Time is GMT */
    if ( 0 == futc_test_cache ( now, time_str, sizeof(time_str),
		&timestamp_cache ) ) {
	long vms_now[2]; int j;
	futc_vms_time ( now, vms_now );
        SYS$NUMTIM ( time, 0 );		/* Time request is logged */
        /* 
	 * Format: dd/mmm/yyyy:hh:mm:ss +0000 
	 */
	j = 0;				/* output position */
	tu_strint ( time[2], &time_str[j] );		/* day */
	if ( time[2] < 10 ) {time_str[j+1] = time_str[j]; time_str[j] = '0';}
	j += 2;
	time_str[j++] = '/';
	tu_strcpy ( &time_str[j], month_names[time[1]-1] ); j+= 3;
	time_str[j++] = '/';
	tu_strint ( time[0], &time_str[j] );		/* year */
	j += tu_strlen ( &time_str[j] );
	time_str[j++] = ':';
	tu_strint ( time[3], &time_str[j] );		/* hour */
	if ( time[3] < 10 ) {time_str[j+1] = time_str[j]; time_str[j] = '0';}
	j += 2;
	time_str[j++] = ':';
	tu_strint ( time[4], &time_str[j] );		/* minute */
	if ( time[4] < 10 ) {time_str[j+1] = time_str[j]; time_str[j] = '0';}
	j += 2;
	time_str[j++] = ':';
	tu_strint ( time[5], &time_str[j] );		/* second */
	if ( time[5] < 10 ) {time_str[j+1] = time_str[j]; time_str[j] = '0';}
	j += 2;
	tu_strcpy ( &time_str[j], " +0000" );

	/* sprintf(time_str,"%02d/%s/%04d:%02d:%02d:%02d +0000", time[2], 
	    month_names[time[1]-1], time[0], time[3], time[4], time[5] ); */
	futc_update_cache ( now, time_str, sizeof(time_str), &timestamp_cache);
    }
#else
    SYS$NUMTIM ( time, 0 );		/* Time request is logged */
    sprintf(time_str,"%02d/%s/%04d:%02d:%02d:%02d +0000", time[2], 
	    month_names[time[1]-1], time[0], time[3], time[4], time[5] );
#endif

   tlog_putlog ( scb->accesslog,
      "!AZ - !AZ [!AZ] \"!AF !AF !AF\" !AF !SL!AZ",
	hostname, user, time_str,
	method_len, request[0].s,  
	path_len, request[1].s,
	protocol_len, request[2].s, 
	3, &scb->rsphdr->s[stspos], scb->data_bytes, extension );
    /*
     * check for referer and user-agent
     */
    if ( http_log_level > 1 ) {
	char referer[200];
	status = http_extract_header ( http_std_atoms.referer, scb,
		referer, sizeof(referer)-1, &length );
        if ( status > 0 ) {
	    tlog_putlog ( 0, "!AZ !AF <- !AF!/", scb->log_prefix,
		path_len > 50 ? 50 : path_len, request[1].s, length, referer );
	}
    }
    return 1;
}
/**************************************************************************/
/* Append text followed by CRLF to response buffer.   If standard flag is true,
 *  append default headers to buffer. If standard flag is 2, use html content.
 */
int http_add_response ( tu_text rsp, char *text, int standard )
{
    int status;
    status = tu_add_text ( rsp, text, MAX_RSP_HDR_LINE );
    status = tu_add_text ( rsp, "\r\n", 3 );
    if ( standard ) {
	status = tu_add_text ( rsp, "MIME-version: 1.0\r\n", 80 );
	status = tu_add_text ( rsp, "Server: OSU/", 80 );
	status = tu_add_text ( rsp, http_server_version, 80 );
	status = tu_add_text ( rsp, (standard == 2) ? 
		"\r\nContent-type: text/html\r\n" : 
		"\r\nContent-type: text/plain\r\n", 80 );
	status = tu_add_text (rsp, "Content-transfer-encoding: 8bit\r\n", 80);
    }
    return status;
}
/*****************************************************************************/
/*  Generate plain/text document to report error and send to client.
 */
int http_send_error ( session_ctx scb, char *status_msg, char *err_text )
{
    int i, status;
    void *cnx;
    string *request;
    /*
     * Build and send standard response header.
     */
    cnx = scb->cnx;
    request = scb->request;
    status = http_add_response ( scb->rsphdr, status_msg, 1 );
    status = http_send_response_header ( scb );
    if ( (status&1) == 1 ) {
	/*
	 * Send additional error text as body of message.
	 */
	status = ts_tcp_write ( cnx,"-ERROR-(", 8 );
	status = ts_tcp_write ( cnx, status_msg, 3 );
	status = ts_tcp_write ( cnx, "):  ", 4 );
	status = ts_tcp_write ( cnx, err_text, tu_strlen(err_text) );
	if ( (status&1) == 0 ) return status;
	/*
	 * Send initial request parse result.
	 */
	if ( request[0].l > 0 ) {
	    status = ts_tcp_write ( cnx, "\r\nRequested method: ", 20 );
	    if ( (status&1) == 0 ) return status;
	    status = ts_tcp_write ( cnx, request[0].s, request[0].l );
	}
	if ( request[1].l > 0 ) {
	    status = ts_tcp_write ( cnx, "\r\nRequested URL:    ", 20 );
	    if ( (status&1) == 0 ) return status;
	    status = ts_tcp_write ( cnx, request[1].s, request[1].l );
	}
	if ( request[2].l > 0 ) {
	    status = ts_tcp_write ( cnx, "\r\nHTTP protocol:    ", 20 );
	    if ( (status&1) == 0 ) return status;
	    status = ts_tcp_write ( cnx, request[2].s, request[2].l );
	}
	/*
	 * Send any remaining request lines.
	 */
	status = ts_tcp_write ( cnx, 
	/*               5678901234567890123456789012345678901234567890 */
		"\r\n\r\n-------- additional request headers --------\r\n",50);
	for ( i = 3; (request[i].l > 0) && (status&1); i++ ) {
	    status = ts_tcp_write ( cnx, request[i].s, request[i].l );
	    if ( (status&1) == 0 ) return status;
	    status = ts_tcp_write ( cnx, "\r\n", 2 );
	    if ( (status&1) == 0 ) return status;
	}
    }
    log_access ( scb );			/* enter into log file */
    return status;
}
/***************************************************************************/
/* Send contents of response header buffer to client.  If protocol is 1.x,
 * include date: header.  Send expires header if acc.retention non-zero and
 * acc.fopt.etype is zero.
 */
int http_send_response_header ( session_ctx scb )
{
    int status, length;
    char *buffer;
    tu_text rsp;

    rsp = scb->rsphdr;
    buffer = rsp->s;
    if ( (rsp->l > 9) && (0 == tu_strncmp(rsp->s,"HTTP/1.", 7)) ) {
	time_t now, exp;
	char date_header[90];
#ifdef VMS
	/* Fast UTC functions are a hack added to work around DECC poor RTL
	 * performance.  Non-VMS system's don't need them.
	 */
	static futc_tcache header_cache = FUTC_TCACHE_INIT;
	static futc_tcache expires_cache = FUTC_TCACHE_INIT;

	futc_current_time ( &now );
	if ( 0 == futc_test_cache ( now, date_header, sizeof(date_header),
		&header_cache ) ) {
	    tu_strcpy ( date_header, "Date: " );
	    tf_format_time ( now, &date_header[6] );
	    futc_update_cache ( now, date_header, sizeof(date_header),
		&header_cache );
	}
	if ( (scb->acc->retention > 0) && (scb->acc->fopt.etype == 0) ) {
	    length = tu_strlen ( date_header );
	    exp = now + scb->acc->retention;
	    if ( 0 == futc_test_cache ( exp, &date_header[length], 
		sizeof(date_header)-length, &expires_cache ) ) {
		if ( http_log_level > 1 ) tlog_putlog ( 2,
		    "!AZ Adding expires header (now+retention)!/",
		    scb->log_prefix );
	        tu_strcpy ( &date_header[length], "\r\nExpires: " );
	        tf_format_time ( exp, &date_header[length+11] );
		futc_update_cache ( exp, &date_header[length],
			sizeof(date_header)-length, &expires_cache );
	    } else if ( http_log_level > 1 ) tlog_putlog ( 2,
		"!AZ futc_test_cache failure on expires generation!/",
		 scb->log_prefix );
	}
#else
	/* Use standard C RTL time functions. */
	tu_strcpy ( date_header, "Date: " );
	tf_format_time ( time(&now), &date_header[6] );
	if ( scb->acc->retention > 0 ) {
	    length = tu_strlen ( date_header );
	    exp = now + scb->acc->retention;
	    tu_strcpy ( date_header, "\r\nExpires: " );
	    tf_format_time ( exp, &date_header[length+11] );
	}
#endif
	tu_add_text ( rsp, date_header, sizeof(date_header) );
	tu_add_text ( rsp, "\r\n\r\n", 5 );		/* add null line */
	length = rsp->l;
    } else if ( *buffer == '3' ) {  /* force full re-direct headers */
	/*
	 * Response buffer is old protocol, skip the version field and
	 * truncate after first line.
	 */
	/* buffer = &buffer[9]; */
	for ( length = 0; 
		buffer[length] && (buffer[length] != '\n'); length++);
	if ( buffer[length] == '\n' ) length++;
	buffer = &buffer[length];
	for ( ; buffer[length] && (buffer[length] != '\n'); length++);
	if ( buffer[length] == '\n' ) length++;
    } else {
	/*
	 * Don't send anything.
	 */
	length = 0;
    }
    if ( length > 0 ) status = ts_tcp_write ( scb->cnx, buffer, length );
    else status = 1;
    return status;
}
/***************************************************************************/
int http_parse_url 
	( char *url, 			/* locator to parse */
	char *info,			/* Scratch area for result pts*/
	char **service,			/* Protocol (e.g. http) indicator */
	char **node,			/* Node name. */
	char **ident,			/* File specification. */
	char **arg )			/* Search argument */
	
{
    int i, state;
    char *last_slash, *p, c, arg_c;
    /*
     * Copy contents of url into info area.
     */
    *service = *node = *ident = *arg = last_slash = "";

    for ( state = i = 0; (info[i] = url[i]) != 0; ) {
	c = info[i];
	switch ( state ) {
	    case 0:
		if ( c == ':' ) {
		    info[i] = '\0';	/* terminate string */
		    *service = info;
		    state = 1;
		}
	    case 1:
		if ( c == '/' ) {
		    *ident = last_slash = &info[i];
		    state = 2;
		}
		break;
	    case 2:
		state = 4;
		if ( c == '/' ) {	/* 2 slashes in a row */
		    *node = *ident;
		    state = 3;
		}
		else if ( (c == '#') || (c == '?') ) {
		    arg_c = c;
		    info[i] = '\0';
		    *arg = &info[i+1];
		    state = 5;
		}
		break;
	    case 3:			/* find end of host spec */
		if ( c == '/' ) {
		    state = 4;
		    *ident = last_slash = &info[i];
		    for ( p = *node; p < *ident; p++ ) p[0] = p[1];
		    info[i-1] = '\0';	/* Terminate host string */
		}
		break;
	    case 4:			/* Find end of filename */
		if ( c == '/' ) last_slash = &info[i];
		else if ( (c == '#') || (c == '?') ) {
		    arg_c = c;
		    info[i] = '\0';
		    *arg = &info[i+1];
		    state = 5;
		}
	    case 5:
		break;
        }
	i++;
    }
    /*
     * Insert arg delimiter back into string.
     */
    if ( **arg ) {
	char tmp;
	for ( p = *arg; arg_c; p++ ) { tmp = *p; *p = arg_c; arg_c = tmp; }
	*p = '\0';
    }
    return 1;
}
/*
 * Scan ident and return pointer to suffix porition of filename, including
 * the period.
 * Return "/" if no filename is present and "" if no extension.
 */
char *http_url_suffix ( char *ident )
{
    char *p, *suffix;
    suffix = "";
    for ( p = ident; *p; p++ )
	if ( *p == '.' ) suffix = p;
	else if ( *p == '/' ) suffix = "";
    /*
     * Check if last thing on line is "/".
     */
    if ( (*suffix == '\0') && (p > ident) )
	if ( *--p == '/' ) suffix = p;

    return suffix;
}
/***************************************************************************/ 
/* Scan request headers for connection headers that specify keep-alive.
 */
static int keepalive_scan ( session_ctx scb, char *temp, int bufsize )
{
    int status, length;
    int i, j, req_len, limit; 
    char *s;
    limit = 0;
    /*
     * First extract connection headers.  Also don't support keep-alive
     * if request includes data (content-length present).
     */
    status = http_extract_header ( http_std_atoms.connection, scb,
		temp, bufsize-1, &length );
    if ( status <= 0 ) return 0;	/* no connection headers */
    if ( (scb->req_summary&http_std_amask.content_length) != 0 ) return 0;
    /*
     * Scan connection header values for keep-alive, case blind compares.
     */
    temp[length] = '\0';
    tu_strupcase ( temp, temp );
    temp[length++] = ',';
    for ( j = i = 0; i < length; i++ ) if ( temp[i] == ',' ) {
	if ( 0 == tu_strncmp ( "KEEP-ALIVE", &temp[j], 10 ) ) {
	    return http_keepalive_limit;
	}
	j = i + 1;
    }
    return 0;	/* not found */
}
/***************************************************************************/
/* Set attributes for a port based upon string of format:
 *
 *	nnn[/[+][:]scheme]
 *
 * where
 *     nnn	Port number.
 *     +	(optional), if present, indicate port permitted keepalives.
 *     :	(optional), if present, indicates port is not scheme's standard
 *		port number
 *     scheme	URL scheme (e.g. https).
 *
 * Return value is index number assigned to port.
 */
int http_set_port_attributes ( 
    int is_nocache,		/* 0-regular port, 1-no-cache port */
    char *port_spec )
{
    char *slash, *port_attr, dummy[512];
    int port_num, ndx, i, nc_port_ndx;
    /*
     * Parse the port_spec string in port_num and port_attr strings.
     */
    slash = tu_strstr ( port_spec, "/" );
    if ( slash  ) {
	/*
	 * Make copy of attributes string.
	 */
	port_attr = malloc ( tu_strlen(slash) );
	tu_strcpy ( port_attr, slash+1 );
	tu_strnzcpy ( dummy, port_spec, sizeof(dummy) - 1 );
	port_spec = dummy;
	slash = tu_strstr ( port_spec, "/" );
	if ( slash ) *slash++ = '\0';
    } else {
	port_attr = (char *) 0;
    }

    if ( (port_spec[0] < '0') || (port_spec[0] > '9') ) {
	/*
	 * see if string is environment variable.
	 */
	char *val = getenv(port_spec);
	if ( !val ) tlog_putlog(0,
	    "Invalid variable name in Port rule: '!AZ'!/", port_spec );
	port_spec = val ? val : "1";
    }
    port_num = atoi ( port_spec );
    /*
     * Validate the port number and provide default attributes.
     */
    if ( (port_num <= 0) || (port_num > 0x0ffff) ) {
	tlog_putlog ( 0, "Invalid port number: '!AZ'!/", port_spec );
	return -1;
    }

    if ( !port_attr ) {
	/* 
	 * Default attributes are http with keep-alive.
	 * Check for non-80 and SSL ports.  If port number is
	 * 'standard', omit ':' from attributes string.
	 */
	port_attr = "+:http";
	if ( port_num == 80 ) port_attr = "+http";
	else if ( port_num == 443 ) port_attr = "https";
    }
    /*
     * Add to ports list, get current list of lists.
     */
    for ( i = 0; port_num != http_ports[i]; i++ ) {
	if ( 0 == http_ports[i] ) {
	    /* Number not found, extend list if possible. */
	    if ( i < (PORT_LIMIT-1) ) {
		http_ports[i] = port_num;
		http_ports[i+1] = 0;		/* new end of list */
		break;
	    } else {
	        tlog_putlog ( 0, "Number of ports exceeds !SL, ignoring '!AZ'!/",
		    PORT_LIMIT-1, port_spec );
		return -1;
	    }
	}
    }
    http_port_attributes[i] =  port_attr;
    ndx = i;
    /*
     * See if on nocache list and add/remove as needed.
     */
    nc_port_ndx = -1;
    for ( i = 0; http_nocache_ports[i]; i++ ) {
	if ( port_num == http_nocache_ports[i] ) nc_port_ndx = i;
    }
    if ( is_nocache && (nc_port_ndx < 0) ) {
	/* Add to list. */
	http_nocache_ports[i] = port_num;
	http_nocache_ports[i+1] = 0;		/* new end of list */
    } else if ( !is_nocache && ( nc_port_ndx >= 0 ) ) {
	/* Trim from list (replace with last item in list and shorten) */
	http_nocache_ports[nc_port_ndx] = http_nocache_ports[i-1];
	http_nocache_ports[i-1] = 0;
    }

    return ndx;
}
