/*
 * This program is a CGI script that proxies HTTP requests to a 
 * remote host.
 *
 * In DECC, this program must be compiled /prefix=all to get the socket
 * routines.
 *
 * Add the following rule to your configuration file to enable this script:
 *   ProxyScript /htbin/proxygw/
 *
 * Author:	David Jones
 * Date:	16-FEB-1998
 */
#include <stdlib.h>
#include <unixio.h>
#include <stdio.h>
#include <string.h>
#include <types.h>
#include <errno.h>
#include <ctype.h>
#include <socket.h>
#include <in.h>
#include <netdb.h>
#include "scriptlib.h"		/* net_link_... routines */

struct string { int l; char *s; };
static int open_remote(), send_query(), read_response();
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 */
	
/********************************************************************/
/* Main routine.
 */
int main ( int argc, char **argv )
{
    int sd, tcnt, length, i, j, used, status, content_length;
    char *inp, *cmp, *ident, *uarg, *node, *scheme;
    char orig_url[4096], url_parts[5000];
    char hdrbuf[8192];
    struct string hdr[128];
    char bin_path[256], remhost[256];
    /*
     * Validate command line arguments.  The target machine
     * is appended to the script name that invokes this program.
     */
    if ( argc < 3 ) {
	fprintf( stderr, "Missing arguments, usage: prog method url\n");
	exit ( 1 );
    }
    /*
     * Open network link and get original URL and headers, noting along
     * the way if a content-length present.
     */
    status = net_link_open();
    if ( (status&1) == 0 ) { perror("Network connect fail"); exit (status); }

    status = net_link_query ( "<DNETRQURL>", orig_url, sizeof(orig_url)-1,
	&length );
    if ( (status&1) == 0 ) { perror("Network I/O failure"); exit (status); }
    if ( length < 0 ) exit ( 1 );
    orig_url[length] = '\0';

    cmp = "CONTENT-LENGTH:";
    content_length = 0;
    j = 0;
    used = 0;
    for ( status = net_link_query ("<DNETHDR>",hdrbuf,sizeof(hdrbuf),&length); 
	((status&1) == 1) && (length > 0);
        status = net_link_read(&hdrbuf[used], sizeof(hdrbuf)-used-1, &length)) {
	/*
	 * Add  to string list.  Terminate strings with null chars for safety.
	 */
	hdr[j].l = length;
	hdr[j].s = &hdrbuf[used];
	hdrbuf[used+length] = '\0';
	used += (length + 1);
 	/*
	 * Check if line is 'content-length:' and save.
	 */
	for ( i = 0; i < length; i++ ) {
	    if ( cmp[i] != _toupper(hdr[j].s[i]) ) break;
	    if ( cmp[i] == ':' ) {
		content_length = atoi ( &hdr[j].s[i+1] );
		break;
	    }
	}
	j++;		/* advance to next string */
    }
#ifdef ECHO_TEST
    /*
     * For now, echo back the info.
     */
    status = net_link_write ( "<DNETTEXT>", 10 );
    if ( (status&1) == 0 ) return status;
    status = net_link_write ( "200 proxy request echo\r\n", 24 );
    status = net_link_write ( orig_url, strlen(orig_url) );
    if ( (status&1) == 0 ) return status;
    for ( i = 0; i < j; i++ ) {
        status = net_link_write ( hdr[i].s, hdr[i].l );
        if ( (status&1) == 0 ) break;
    }
    status = net_link_write ( "</DNETTEXT>", 11 );
    return status;
#else
   /*
    * Parse original URL.
    */
   status = http_parse_url ( orig_url, url_parts, &scheme, &node, &ident, 
		&uarg );
   if ( !status || !scheme || !node ) {
	/*
	 * Couldn't make sense of request.
	 */
	net_link_write ( "<DNETTEXT>", 10 );
	net_link_write ( "400 Invalid proxy request syntax", 32 );
	net_link_write ( "</DNETTEXT>", 11 );
	return status;
   }
   cmp = "HTTP";
    for ( i = 0; scheme[i]; i++ ) {
	if ( cmp[i] != _toupper(scheme[i]) ) break;
    }
    if ( i != 4 ) {
	/*
	 * Only know how to do HTTP
	 */
	net_link_write ( "<DNETTEXT>", 10 );
	net_link_write ( "500 unsupported scheme requested", 32 );
	net_link_write ( "</DNETTEXT>", 11 );
	return status;
    }
   /*
    * Open connection.
    */
   sd = open_remote ( node );
   if ( sd < 0 ) {
	/*
	 * Network error.
	 */
	net_link_write ( "<DNETTEXT>", 10 );
	net_link_write ( "500 error connecting to remote  ", 32 );
	net_link_write ( node, strlen(node) );
	net_link_write ( "</DNETTEXT>", 11 );
	return status;
   }
   /*
    * Send request.  method and protocol version were on command line.
    */
   status = send_query ( sd, hdr, j, argv[1], ident, argv[3] );
   if ( (status&1) == 1 ) status = read_response ( sd );
   return status;
#endif
    
}
/*****************************************************************************/
/* Create socket and connect to remote host.
 */
static int open_remote ( char *remhost )
{
    struct sockaddr local, remote;
    struct hostent *hostinfo;
    int i, j, rem_port, sd, status;
    char *alt_port;
    /* */
    rem_port = 80;

    alt_port = strchr ( remhost, ':' );
    if ( alt_port ) {
	*alt_port++ = '\0';
	rem_port = atoi ( alt_port );
    }
    hostinfo = gethostbyname ( remhost );
    if ( !hostinfo ) {
	    fprintf(stderr, "Could not find host '%s'\n", remhost );
	return -1;
    }
    /*
     * Attempt connect to remote host.
     */
    sd = socket ( AF_INET, SOCK_STREAM, 0 );
    if ( sd < 0 ) {
	    fprintf(stderr,"Error creating socket: %s\n", strerror(errno) );
	return sd;
    }
    local.sa_family = AF_INET;
    for ( j = 0; j < sizeof(local.sa_data); j++ ) local.sa_data[j] = '\0';
    local.sa_data[0] = local.sa_data[1] = 0;
    status = bind ( sd, &local, sizeof ( local ) );
    if ( status < 0 ) {
	    fprintf(stderr,"Error binding socket: %s\n", strerror(errno) );
	return -1;
    }

    remote.sa_family = hostinfo->h_addrtype;
    for ( j = 0; j < hostinfo->h_length; j++ ) {
	    remote.sa_data[j+2] = hostinfo->h_addr_list[0][j];
    }
    remote.sa_data[0] = rem_port >> 8;
    remote.sa_data[1] = (rem_port&255);
    status = connect ( sd, &remote, sizeof(remote) );
    if ( status != 0 ) {
	return -1;
    }
    return sd;
}
/*****************************************************************************/
/* Get request information and send query.
 */
static int send_query ( int sd, struct string *hdr, int hdrcnt,
	char *method, char *path, char *version )
{
    int content_length, j, i, status, length, written;
    char *cmp, buffer[4096];
    /*
     *  Send initial part of request.
     */
    strcpy ( buffer, method );
    strcat ( buffer, " " ); strcat ( buffer, path );
    strcat ( buffer, " " ); strcat ( buffer, version );
    strcat ( buffer, "\r\n" );
    length = strlen ( buffer );
    written = write ( sd, buffer, length );
    if ( written != length ) return 0;
    /*
     * Fetch header lines.
     */
    cmp = "CONTENT-LENGTH:";
    content_length = 0;
    for ( j = 0; j < hdrcnt; j++ ) {
	/*
	 * Relay to remote.
	 */
	length = hdr[j].l;
	strncpy ( buffer, hdr[j].s, length );
	buffer[length++] = '\r';
	buffer[length++] = '\n';
	written = write ( sd, buffer, length );
        if ( written != length ) return 0;
 	/*
	 * Check if line is 'content-length:' and save.
	 */
	buffer[length-2] = '\0';
	for ( i = 0; i < length; i++ ) {
	    if ( cmp[i] != _toupper(buffer[i]) ) break;
	    if ( cmp[i] == ':' ) {
		content_length = atoi ( &buffer[i+1] );
		break;
	    }
	}
    }
    written = write ( sd, "\r\n", 2 );
    /*
     * Send additional.
     */
    while ( content_length > 0 ) {
	status = net_link_query ( "<DNETINPUT>", buffer, 
		sizeof(buffer), &length );
	if ( (status&1) == 0 ) break;
	if ( length > 0 ) {
	    written = write ( sd, buffer, length );
	    if ( written != length ) return 0;
	    content_length = content_length - length;
	}
    }
    return 1;
}
/*****************************************************************************/
static int read_response ( int sd )
{
    int status, length;
    char buffer[4096];

    status = net_link_write ( "<DNETRAW>", 9 );
    if ( (status&1) == 1 ) status = net_link_set_rundown ( "</DNETRAW>" );

    while ( (length = read ( sd, buffer, sizeof(buffer))) > 0 ) {
	status = net_link_write ( buffer, length );
	if ( (status&1) == 0 ) break;
    }
    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)+1;
		    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;
}
