/*
 * This module handles server access protection operations.  The cache
 * access inhibiting falls under this category.
 *
 *   int http_set_cache_inhibit ( int port );
 *
 *   int http_check_protection ( session_ctx scb, char *ident, string *iobuf );
 *
 *	input:
 *	    scb->ac	Protection info for ident:
 *			    owner UIC	(if /~username)
 *			    setup file (from rule file protect/defprot) rule.
 *
 *	    ident	Ident (filename) to access, after translation of
 *			URL according to rule file rules.
 *
 *	    scb->request Request and headers received from client.  This will
 *			be scanned for authorization lines.
 *
 *	modified:
 *	    iobuf	Scratch buffer for reading of setup file.
 *
 *	    scb->rsphdr	On error, will be loaded with HTTP error response,
 *			including authenicatation parameters.  If access
 *		 	check succeeds, rsphdr will not be modified.
 *		
 *	   ret. value:	0	Access is denied.
 *			1	Access is allowed.
 *
 *	int http_protect_fail ( string *request, tu_text rsphdr );
 *
 *  int http_check_cache_access ( access_info acc );
 *
 * Author:	David Jones
 * Revised:	9-JUN-1994		enhanced parsing, accept getmask.
 * Revised:	24-JUL-1994		Support protection by hostname.
 * Revised:	 8-FEB-1994		Extra diagnositice info in log output.
 * Revised:	11-MAY-1995		Fixup for OSF version (removes level2).
 * Revised:	22-NOV-1996		Update http_authenticate interface.
 * Revised:	15-MAY-1996		Fix macro for non-VMS systems.
 * Revised:	15-AUG-1997		Support new file cache.
 * Revised:	26-SEP-1997		Support :portnum at end of host mask.
 * Revised:	14-DEC-1997		use file_cache bitmap for nocache checks.
 * Revised:	27-APR-2000		Provide response header on protection
 *					file open failure.
 * Revised:	27-FEB-2002		Support 'host mask size' (/nnn) on
 *					numeric masks.
 */
#include <stdio.h>
#include "session.h"
#include "file_access.h"
#include "tserver_tcp.h"
#include "file_cache.h"

struct ctx { void * f;
	int used, filled, state, bufsize; 
	char *buf;
	struct tfc_context tfc; };

static int cache_inhibit_port = 0;
int http_log_level;			/* global variable */
int tlog_putlog ( int level, char *ctlstr, ... );
int http_dns_enable;
int http_add_response();
#ifdef VMS
int http_authenticate();
#else
#define http_authenticate(a,b) 0
#endif
/*
 * Define structure to hold parse result for hostmask item:
 *    username@host:port/mask_size
 */
struct mask_elements {
    char *username;		/* '*' if no username */
    char *host;
    char *port;
    char *mask_size;		/* Network size */
    int numeric_host;		/* true host is of form nnn.nnn.nnn.nnn */
    int network_bits;
    int port_num;		/* zero if not specified */
    unsigned char address[16];
};
/*
 * Parse mask string and fill in mask_elements structure.  Mask string
 * may be modified and mask_elements pointers may point to it.
 */
static int parse_mask ( char *mask, struct mask_elements *result )
{
    char *p, c;
    int state, dot_count, is_digit, value;
    /*
     * Set default values.
     */
    result->host = "";
    result->port = "*";
    result->mask_size = "32";
    result->network_bits = 32;
    result->numeric_host = 1;
    result->port_num = 0;
    /*
     * Scan string.
     */
    result->username = mask;
    state = 0;
    dot_count = 0;
    value = 0;
    for ( p = mask; *p; p++ ) {
	c = *p;
	is_digit = ((c >= '0') && (c <= '9'));
	if ( is_digit && (state > 0) ) {
	    value = (value*10) + (c-'0');
	}
#ifdef DEBUG
	printf ( "mask[%x] = '%c', state: %d, is_digit: %d (%d), numeric: %d\n",
		p, c, state, is_digit, value, result->numeric_host );
#endif
	switch ( state ) {
	  case 0:
	    /*  Look for end of username. */
	    if ( c == '@' ) {
	 	if ( result->username == p ) result->username = "*";
	 	*p = '\0';
		result->host = &p[1];
		state = 1;
	    }
	    break;

	  case 1:
	    /* Look for end of host string */
	    if ( c == ':' ) {
		*p = '\0';
		result->port = &p[1];
		state = 2;
	    } else if ( c == '/' ) {
		*p = '\0';
		result->mask_size = &p[1];
		state = 3;
	    } else if ( c == '.' ) {
		if ( result->numeric_host ) result->address[dot_count] = value;
		dot_count++;
		value = 0;
		if ( dot_count >= 4 ) result->numeric_host = 0;
	    } else if ( c == '*' ) {
		/* Host of form nnn.nnn.*.* must set network_bits to
		 * indicate number of bits to test.
		 */
		if ( result->numeric_host ) result->address[dot_count] = 0;
		if ( (dot_count*8) < result->network_bits )
		    result->network_bits = (dot_count*8);
	    } else if ( !is_digit ) {
		/* Alpha characters in host string. */
		result->numeric_host = 0;
	    } else if ( result->numeric_host ) {
		/* Decode */
		if ( value > 255 ) result->numeric_host = 0;
	    }
	    if ( state != 1 ) {
		if ( result->numeric_host ) result->address[dot_count] = value;
		value = 0;
	    }
	    break;

	  case 2:
	    /* Look for end of port string, validate all numeric. */
	    if ( c == '/' ) {
		*p = '\0';
		result->port_num = value;
		result->mask_size = &p[1];
		state = 3;
		value = 0;
	    } else if ( !is_digit ) {
		/* What to do, what to do? */
		value = 0;
		state = 4;
	    }
	    break;

	  case 3:
	    /* Look for end of mask_size string, validate all numeric */
	    if ( !is_digit ) {
		state = 4;
	    }
	    break;

	  case 4:
	    /* Error occurred. */
	    break;
	}
    }
    /*
     * Check for errors, do final assignment of decoded value.
     */
    switch ( state ) {
      case 0:			/* no '@' in mask string */
	result->numeric_host = 0;
	break;
      case 1:
	if ( result->numeric_host ) result->address[dot_count] = value;
	break;
      case 2:
	result->port_num = value;
	break;
      case 3:
	result->network_bits = value;
        break;
      case 4:
	break;
    }
    return (state < 4) ? 1 : 0;
}
/*****************************************************************************/
/* Set local port used to inhibit caching, returning previous value.
 */
int http_set_cache_inhibit ( int port )
{ int old;
   old = cache_inhibit_port;
   cache_inhibit_port = port;
   tfc_mark_port_number ( port );
   return old;
}
/*****************************************************************************/
/* Determine if cache access is allowed. */
int http_check_cache_access ( access_info acc )
{
    if ( cache_inhibit_port ) {
	/*
	 * Get connection information and inhibit cache if the local port
	 * is equal to cache inhibit port number.
	 */
	unsigned int remote_address;
	int local_port, remote_port, status;

        status = ts_tcp_info ( &local_port, &remote_port, &remote_address );
        acc->cache_allowed = tfc_test_port_number ( local_port ) ? 0 : 1;
        if ( http_log_level > 5 )
	    tlog_putlog ( 4, "port !SL cache_allowed flag: !SL!/", local_port,
		acc->cache_allowed );
    } else acc->cache_allowed = 1;
    return 1;
}
/*****************************************************************************/
/* Read next line from input file.
 */
static int read_line ( struct ctx *inp, char *line, int maxlen )
{
    int length;
    char octet;
    length = 0;
    do {
	if ( inp->used >= inp->filled ) {
	    inp->filled = inp->used = 0;
	    if ( ! inp->f ) return length;	/* end of file */

	    inp->filled = tf_read ( inp->f, inp->buf, inp->bufsize );
	    if ( inp->filled <= 0 ) {
		tf_close ( inp->f ); inp->f = (void *) 0;
		return length;
	    }
	}
	octet = inp->buf[inp->used++];
	if ( length < maxlen ) line[length++] = octet;
    } while ( octet != '\n' );
    return length;
}
/*************************************************************************/
/* Read setup file and test current information against rules in file. */

static int scan_setup_file ( struct ctx *inp, unsigned char *remote_address,
	int local_port )
{
    int length, status, multi_line, rhost_len;
    struct mask_elements result;
    char *type, *value, *p, *colon, number[16], line[500];
    char remote_host[256];
    /*
     * process file line by line.
     */
    rhost_len = -1;
    multi_line = 0;
    while ( 0 < (length = read_line ( inp, line, sizeof(line)-1) ) ) {
	/*
	 * Parse line into type field and value field.
	 */
	line[length] = '\0';
	if ( length > 0 ) if ( line[length-1] == '\n' ) line[length-1] = '\0';
	p = line;
	type = value = "";
	for ( type = line; *type && ((*type==' ') || (*type=='\t')); type++);
	if ( (*type == '!') || (*type == '#') ) continue;
        if ( !multi_line ) {
	    /*
	     * Not continuation, get line type.
	     */
	    for ( p = type; *p && ((*p != ' ') && (*p != '\t')); p++ );
	    *p++ = '\0';
	    tu_strupcase ( type, type );
	    if ( ( tu_strncmp ( type, "GETMASK", 8 ) !=0 ) &&
		(tu_strncmp ( type, "MASKGROUP", 10 ) != 0) ) continue;
        }
	/*
	 * parse out values.
	 */
	while ( p < &line[length] ) {
	    /* 
	     * Skip to start of field, ignoring commas 
	     */
	    for ( value = p; *value && 
		((*value==' ') || (*value=='\t') || (*value==',')); value++)
			if ( *value == ',' ) multi_line = 1;;
	    if ( *value ) multi_line = 0;
	    /*
	     * Find of end of field and terminate string.
	     */
	    for ( p=value, colon = ""; *p && 
		((*p != ',') && (*p != ' ') && (*p != '\t')); p++ );
	    if ( *p == ',' ) multi_line = 1;
	    *p++ = '\0';
	    /*
	     * Parse value.
	     */
            if ( http_log_level > 6 )
	        tlog_putlog(7,"groupdef from prot file: '!AZ'!/", value );
	    status = parse_mask ( value, &result );
	    if ( http_log_level > 6 ) tlog_putlog ( 7,
		"groupdef parse result: !SL, is_numeric: !SL, bits: !SL!/", 
		status,	result.numeric_host, result.network_bits );
	    if ( !status ) continue;		/* ignore invalid values */
	    /*
	     * Ignore values with usernames, check for port number.
	     */
	    if ( result.username[0] == '0' ) continue;

	    if ( result.port_num > 0 ) {
		if ( http_log_level > 6 ) tlog_putlog ( 7,
		    "Port restriction: '!SL', local port: !SL!/", 
			result.port_num, local_port );
		if ( result.port_num != local_port ) continue;  /* fail */
	    }

	    if ( result.numeric_host ) {
		/*
		 * Test first result.network_bits bits in result.address
		 * against remote_address.
		 */
		int i, n, j;
		n = 0;
		for ( i = 0; n < result.network_bits; i++ ) {
		    j = result.network_bits - n;
		    if ( j >= 8 ) { 	/* test whole byte */
			if ( result.address[i] != remote_address[i] ) {
			    break;		/* doesn't match */
			}
		    } else {
			/*
			 * Test portion of byte,  high 'n' bits.
			 * The corresponding bits in the test mask are
			 * assumed to be zero.
			 */
			static unsigned char sub_byte[8] = { 
			    0, 0x080, 0x0c0, 0x0f0, 0x0f0, 0x0f8, 0x0fc,
			    0x0fe };

			if ( result.address[i] != 
			    (remote_address[i]&sub_byte[j]) ) break;
		    }
		    n = n + 8;
		}
		if ( n >= result.network_bits ) return 1; /* all octets match */

	    } else if ( http_dns_enable ) {
		/*
		 * Parse and compare based on name.
		 */
		char *d;
	        int i, j, k, len;
		if ( rhost_len < 0 ) {
		    tu_strnzcpy ( remote_host, ts_tcp_remote_host(), 254 );
		    tu_strupcase ( remote_host, remote_host );
		    rhost_len = tu_strlen ( remote_host );
		    remote_host[rhost_len] = '.';
		}
		/* Break hostname into separate labels */
		value = result.host;
		tu_strupcase ( value, value );
		if ( http_log_level > 6 ) {
		    remote_host[rhost_len+1] = '\0';
        	    if ( http_log_level > 6 )
		       tlog_putlog(7,"Comparing host '!AZ' with '!AZ!/", 
				remote_host, value );
		}
		d = &value[1];
		for (j=i=0; i <= rhost_len; i++ ) if (remote_host[i]=='.') {
		    /* String goes from j to i, ending in period */
		    for ( k=0; d[k] && (d[k]!='.'); k++ );
		    if ( http_log_level > 6 ) tlog_putlog(7,
			"remote_host[!SL..!SL] = '!AF' and d = '!AF'!/", j, i, 
			i-j, &remote_host[j], k, d);
		    len = i - j;
		    if ( (k != 1) || (d[0] != '*') ) {

		        if ( k != len) break;	/* mismatch */
		        if ( tu_strncmp(d,&remote_host[j],len) ) break;
		    }
		    d += k; if ( *d ) d++;
		    j = i+1;
		}
		/* We pass if If all labels matched no more value; */
		if ( (i > rhost_len) && !*d ) return 1;
	    }
	}
    }
    /*
     * Nothing matched, fail.
     */
    return 0;
}
/***********************************************************************/
/* Check if access allowed allowed by protection setup file for the
 * current request and connection information stored in the scb.  Return
 * 0 if access denied.
 */
int http_check_protection ( 
	session_ctx scb,			/* session control block */
	char *ident,				/* Ident to access */
	string *iobuf)				/* scratch space for I/O */
{
    int status, local_port, remote_port, length, left, cache_status;
    unsigned int remote_address;
    struct ctx inp;
    access_info acc;		/* local copy of scb->acc */
    /*
     * Get address of client and set default value whether caching permitted.
     */
    status = ts_tcp_info ( &local_port, &remote_port, &remote_address );
    acc = scb->acc;
    acc->cache_allowed = tfc_test_port_number ( local_port ) ? 0 : 1;
    if ( http_log_level > 5 )
	tlog_putlog ( 4, "port !SL cache_allowed flag: !SL prot file: '!AZ'!/", 
		local_port, acc->cache_allowed, acc->prot_file );
    /*
     * Check whether setup file is level 2 protection.
     */
    if ( acc->prot_file[0] == '+' ) {
	status = http_authenticate ( scb, ident );
	return status;
    }
    /*
     * See if setup_file in cache, otherwise read it.  Use unique encoding
     * to ensure that we don't retrieve served file.
     */
    inp.f = (void *) 0;
    inp.used = inp.filled = inp.state = 0;
    inp.bufsize = iobuf->l;
    inp.buf = iobuf->s;			/* data area for I/O */

    if ( acc->cache_allowed ) cache_status = 
		tfc_cached_open ( 1, acc->prot_file, &inp.f,
		inp.buf, &inp.tfc );
    else {
        cache_status = inp.tfc.mode = 0;
	inp.f = tf_open ( acc->prot_file, "r", inp.buf );
    }
    if ( cache_status == 1 ) {		/* Found existing */
	tfc_get ( &inp.tfc, (void **) &inp.buf, &inp.filled );    
    } else if ( inp.f ) {		/* file openned */
	
	for ( left=inp.bufsize; left > 0; left = left - length ) {
	    length = tf_read ( inp.f, &inp.buf[inp.bufsize-left], left );
	    if ( length <= 0 ) break;
	}
	inp.filled = inp.bufsize - left;
	if ( inp.tfc.mode == 2 ) {
	    tfc_put ( &inp.tfc, inp.buf, inp.filled );
	}
    } else {
	http_add_response ( scb->rsphdr, "403 Forbidden, open failure", 1 );
	return 0;			/* error reading file */
    }
    /*
     * Parse setup file.  While scanning validate remote_address against
     * group mask.
     */
    status = scan_setup_file ( &inp, (unsigned char *) &remote_address,
		local_port );
    if ( inp.f ) tf_close ( inp.f );
    if ( inp.tfc.mode ) tfc_rundown_context ( &inp.tfc, 0 );
    if ( (status &1) == 0 ) {
	/*
	 * Parse failed, for level 1 protection just return forbidded status.
	 */
	http_add_response ( scb->rsphdr, "403 Forbidden", 1 );
	return 0;
    }
    return status;
}
/****************************************************************************/
int http_protect_fail ( string *request, tu_text rsphdr )
{
    int status, http_add_response();
    status = http_add_response ( rsphdr, "401 Unauthorized", 0 );
    status = http_add_response ( rsphdr, 
		"WWW-authenticate: Basic realm=\"VMS password\"", 1 );
    return status;
}
