/*
 * mod_cgisock V 0.4.2 23/1/2000 
 * socket portions copyright Michael Voase under the APACHE license .
 * The software is supplied 'as-is' without warranty . 
 * Refer to the LICENSE document supplied with this package for license
 * details.
 *
 * http_script: keeps all script-related ramblings together.
 * 
 * Compliant to CGI/1.1 spec (Well , sort of ....)
 * 
 * Adapted by rst from original NCSA code by Rob McCool
 *
 * Apache adds some new env vars; REDIRECT_URL and REDIRECT_QUERY_STRING for
 * custom error responses, and DOCUMENT_ROOT because we found it useful.
 * It also adds SERVER_ADMIN - useful for scripts to know who to mail when 
 * they fail.
 *
 * Version 0.3+ of the cgisock contains contributions by Pete Vassoff
 * pfv@grex.org ( header length additions ).
 *
 * 	Note: The previous array header info has been replaced with a single 
 *	fixed width header size. This makes for faster serving. If you have
 *	started using the array header stuff, then set the ARRAY_COUNT define
 *	just below to return the 0.3 behaviour. This will settle down as time
 *	goes on....
 */

#undef ARRAY_COUNT
#ifndef CORE_PRIVATE
#define CORE_PRIVATE
#endif
#undef CGISOCK_LOG
#define CGISOCK_MAGIC_TYPE "interface/x-httpd-cgisock"
#ifndef ARRAY_COUNT
#define CGISOCK_VERSION "CGISOCK=0.4"
#else
#define CGISOCK_VERSION "CGISOCK=0.3" /* Backward compatibility to try and stop people */
#endif				/* from getting burnt too badly by these changes */
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_main.h"
#include "http_log.h"
#include "util_script.h"
#include "http_conf_globals.h"
#include "ap_config.h"
#include <sys/socket.h>

module MODULE_VAR_EXPORT cgisock_module;

/* Configuration stuff */
#define DEFAULT_TRANSLATE_TABLE_SIZE 20
#define DEFAULT_LOGBYTES 1038576
#define DEFAULT_BUFBYTES 8192

/* Note - script writers, change the next value if cgisock keeps mercilessly
timing out your script. I could turn this into a config option but the possibility
exists for a malicious DoS attack if the value is set to zero.
*/

#define CGISOCK_DEFAULT_TIMEOUT 300

/* This is a KLUDGE . For some reason even though the defines below are 
   defined in earlier includes , gcc still seems to throw a fit if you
   build the module on its own .... hence this KLUDGE .
*/
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif

#ifndef sockaddr_un
struct sockaddr_un {
    unsigned short sun_family;
    char sun_path[UNIX_PATH_MAX];
    };
#endif


typedef struct {
    char *logname;
    long logbytes;
    int bufbytes;
} cgisock_server_conf;

typedef struct {
    char *dirname;
    char *anchor;
    int timeout;
#ifdef CGISOCK_LOG
    char *debuglog;
#endif
} cgisock_dir_conf;

static void *create_server_config(pool *p, server_rec *s)
{
    cgisock_server_conf *c =
    (cgisock_server_conf *) ap_pcalloc(p, sizeof(cgisock_server_conf));

    c->logname = NULL;
    c->logbytes = DEFAULT_LOGBYTES;
    c->bufbytes = DEFAULT_BUFBYTES;
    return c;
}

static void *merge_server_config(pool *p, void *basev, void *overridesv)
{
    cgisock_server_conf *a = 
    (cgisock_server_conf *) ap_pcalloc(p, sizeof(cgisock_server_conf));

    cgisock_server_conf *base = 
    (cgisock_server_conf *) basev, 
    *overrides = (cgisock_server_conf *) overridesv;
    a->logname ? overrides->logname : base->logname ;
    return a;
}    
static void *create_dir_config(pool *p , char *dummy )
{
    cgisock_dir_conf *a = 
    (cgisock_dir_conf *) ap_palloc( p , sizeof(cgisock_dir_conf ));
    a->anchor = NULL;
    a->dirname = NULL;
    a->timeout = CGISOCK_DEFAULT_TIMEOUT;
    return a;
}

static void *merge_dir_config(pool *p , void *basev , void *addv )
{
    cgisock_dir_conf *base = (cgisock_dir_conf *) basev;
    cgisock_dir_conf *newv = (cgisock_dir_conf *) addv;
    cgisock_dir_conf *a = 
    (cgisock_dir_conf *) ap_palloc( p , sizeof(cgisock_dir_conf));
    a->anchor = newv->anchor ? newv->anchor : base->anchor;
    a->dirname = newv->dirname ? newv->dirname : base->dirname;
    if ( newv->timeout > base->timeout ) a->timeout = newv->timeout; 
    else a->timeout =  base->timeout;

#ifdef CGISOCK_LOG
    a->debuglog = newv->debuglog ? newv->debuglog : base->debuglog;
#endif
    return a;
}

static const char *set_socketlog(cmd_parms *cmd, void *dummy, char *arg)
{
    server_rec *s = cmd->server;
    cgisock_server_conf *conf =
    (cgisock_server_conf *) ap_get_module_config(s->module_config, &cgisock_module);

    conf->logname = arg;
    return NULL;
}

static const char *set_socketlog_length(cmd_parms *cmd, void *dummy, char *arg)
{
    server_rec *s = cmd->server;
    cgisock_server_conf *conf =
    (cgisock_server_conf *) ap_get_module_config(s->module_config, &cgisock_module);

    conf->logbytes = atol(arg);
    return NULL;
}

static const char *set_socketlog_buffer(cmd_parms *cmd, void *dummy, char *arg)
{
    server_rec *s = cmd->server;
    cgisock_server_conf *conf =
    (cgisock_server_conf *) ap_get_module_config(s->module_config, &cgisock_module);

    conf->bufbytes = atoi(arg);
    return NULL;
}

static const char *add_socket_path(cmd_parms *cmd, cgisock_dir_conf *c ,char *real_path ) 
{
    c->dirname = real_path;
    return NULL;
}

static const char *add_socket_anchor(cmd_parms *cmd, cgisock_dir_conf *c ,char *real_path ) 
{
    c->anchor = real_path;
    return NULL;
}

static const char *add_socket_timeout(cmd_parms *cmd, cgisock_dir_conf *c ,char *timeout ) 
{
    char *t,n;
    t = timeout;
    while(t) {
	if (!isdigit(*t)) return NULL;
	t++;
	};
    c->timeout = atoi(timeout);
    return NULL;
}

#ifdef CGISOCK_LOG
static const char *add_debug_log(cmd_parms *cmd,  cgisock_dir_conf *d ,char *deblog )
{
    d->debuglog = deblog ;
    return NULL;
}
#endif
static const command_rec cgisock_cmds[] =
{
    {"CgisockLog", set_socketlog, NULL, RSRC_CONF, TAKE1,
     "the name of a log for socket debugging info"},
    {"CgisockLogLength", set_socketlog_length, NULL, RSRC_CONF, TAKE1,
     "the maximum length (in bytes) of the socket debug log"},
    {"CgisockLogBuffer", set_socketlog_buffer, NULL, RSRC_CONF, TAKE1,
     "the maximum size (in bytes) to record of a POST request"},
    {"CgisockPath", add_socket_path ,NULL , ACCESS_CONF , TAKE1 ,
     "the translate path to the real location of your socket"} ,
    {"CgisockAnchor", add_socket_anchor ,NULL , ACCESS_CONF , TAKE1 ,
     "the fixed real location of your socket"} ,
    {"CgisockTimeout", add_socket_timeout ,NULL , ACCESS_CONF , TAKE1 ,
     "the timeout before cgisock gives up on a slow socket"} ,
#ifdef CGISOCK_LOG
    {"CgisockDebugLog",add_debug_log , NULL , ACCESS_CONF , TAKE1 ,
     "the per directory debug log path for user error messages"},
#endif
    {NULL}
};

static int log_socketerror(request_rec *r, cgisock_server_conf *conf, int ret,
			   int show_errno, char *error)
{
    FILE *f;
    struct stat finfo;

    ap_log_error(APLOG_MARK, show_errno|APLOG_ERR, r->server, 
		"%s: %s", error, r->filename);

    if (!conf->logname ||
	((stat(ap_server_root_relative(r->pool, conf->logname), &finfo) == 0)
	 &&   (finfo.st_size > conf->logbytes)) ||
         ((f = ap_pfopen(r->pool, ap_server_root_relative(r->pool, conf->logname),
		      "a")) == NULL)) {
	return ret;
    }

    /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
    fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
	    r->args ? "?" : "", r->args ? r->args : "", r->protocol);
    /* "%% 500 /usr/local/apache/cgi-bin */
    fprintf(f, "%%%% %d %s\n", ret, r->filename);

    fprintf(f, "%%cgisock error\n%s\n", error);

    ap_pfclose(r->pool, f);
    return ret;

}

static int cgi_select ( int sock_rd, int sock_wr, int secs, int usecs ) 
{
    fd_set sock_set_rd, sock_set_wr;
    fd_set *rd = NULL, *wr = NULL;
    struct timeval tv;
    if ( sock_rd > 0 ) {
	FD_ZERO(&sock_set_rd);
	FD_SET( sock_rd,&sock_set_rd );
	rd = &sock_set_rd;
    };
    if ( sock_wr > 0 ) {
	FD_ZERO(&sock_set_wr);
	FD_SET( sock_wr,&sock_set_wr );
	rd = &sock_set_wr;
    };
    
    tv.tv_sec= secs;
    tv.tv_usec = usecs;
    return ap_select((sock_rd > sock_wr) ? sock_rd+1 : sock_wr+1, 
	rd, wr, NULL, &tv);
}

#ifdef CGISOCK_LOG
static int debuglog_cgisock(request_rec *r, cgisock_dir_conf *dirconf, 
	cgisock_server_conf *conf , char *message, char *stuff)
{
    FILE *f;
    struct stat finfo;

    if (dirconf->debuglog == NULL) 
	return;
	
    if ((stat(dirconf->debuglog , &finfo) == 0)
	 &&   (finfo.st_size > conf->logbytes)) 
	return;
	  
    if ((f = ap_pfopen(r->pool, dirconf->debuglog,"a")) == NULL) 
	return;
    

    /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
    fprintf(f, "%%%% [%s] %s %s%s%s %s\n", ap_get_time(), r->method, r->uri,
	    r->args ? "?" : "", r->args ? r->args : "", r->protocol);
    /* "%% 500 /usr/local/apache/cgi-bin */
    fprintf(f, "%s %s %s\n",r->filename,message, stuff);

    ap_pfclose(r->pool, f);
    return;

}

static int log_socket(request_rec *r, cgisock_server_conf * conf , 
	cgisock_dir_conf * dirconf, int ret, char *dbuf, const char *sbuf, 
	BUFF *socket_in)
{
    array_header *hdrs_arr = ap_table_elts(r->headers_in);
    table_entry *hdrs = (table_entry *) hdrs_arr->elts;
    char argsbuffer[HUGE_STRING_LEN];
    FILE *f;
    int i;
    struct stat finfo;

    if (!dirconf->debuglog ||
	((stat(dirconf->debuglog, &finfo) == 0)
	 &&   (finfo.st_size > conf->logbytes)) ||
         ((f = ap_pfopen(r->pool, dirconf->debuglog,"a")) == NULL)) {
	/* Soak up socket output */
	while (ap_bgets(argsbuffer, HUGE_STRING_LEN, socket_in) > 0)
	    continue;
	return ret;
    }

    /* "%% [Wed Jun 19 10:53:21 1996] GET /cgi-bin/printenv HTTP/1.0" */
    fprintf(f, "*** Begin Log Socket ***\n%%%% [%s] %s %s%s%s %s\n", 
	    ap_get_time(), r->method, r->uri,r->args ? "?" : "", 
	    r->args ? r->args : "", r->protocol);
    /* "%% 500 /usr/local/apache/cgi-bin" */
    fprintf(f, "%%%% %d %s\n", ret, r->filename);

    fputs("%request\n", f);
    for (i = 0; i < hdrs_arr->nelts; ++i) {
	if (!hdrs[i].key)
	    continue;
	fprintf(f, "%s: %s\n", hdrs[i].key, hdrs[i].val);
    }
    if ((r->method_number == M_POST || r->method_number == M_PUT)
	&& *dbuf) {
	fprintf(f, "\n%s\n", dbuf);
    }

    fputs("%response\n", f);
    hdrs_arr = ap_table_elts(r->err_headers_out);
    hdrs = (table_entry *) hdrs_arr->elts;

    for (i = 0; i < hdrs_arr->nelts; ++i) {
	if (!hdrs[i].key)
	    continue;
	fprintf(f, "Headers :\n%s: %s\n", hdrs[i].key, hdrs[i].val);
    }

    if (sbuf && *sbuf)
	fprintf(f, "Read buff :\n%s\n", sbuf);
    fputs("Reading output from socket\n",f);
    if ((cgi_select(socket_in->fd_in, 0 , d_conf->timeout, 0) <= 0)) {
	fputs("Blocked read from socket\n *** Premature end Log Socket *** \n",f);
        ap_bclose(socket_in);
        ap_pfclose(r->pool, f);
        return ret;
    }
    if (ap_bgets(argsbuffer, HUGE_STRING_LEN, socket_in) > 0) {
	fputs("%stdout\n", f);
	fputs(argsbuffer, f);
	while (ap_bgets(argsbuffer, HUGE_STRING_LEN, socket_in) > 0)
	    fputs(argsbuffer, f);
	fputs("\n*** End Log Socket *** \n", f);
    }

    ap_bclose(socket_in);
    ap_pfclose(r->pool, f);
    return ret;
}

#endif
/*****************************************************************************
 *****************************************************************************
 *
 * Actual CGISOCK handling...
 *
 *****************************************************************************
 *****************************************************************************
 */

static int cgisock_handler(request_rec *r)
{   

    int retval, nph, dbpos , cgi_sock , res_con , i = 0, j, k;
    int dbsize, len_read ,pfv_discount;
    fd_set rd_set, wr_set;
    struct timeval tv;
    char *argv0, *dbuf = NULL, argsbuffer[HUGE_STRING_LEN];
    char **env, *name_constr,*tmp;
    BUFF *socket_buff, *cgis_client;
    int is_included = !strcmp(r->protocol, "INCLUDED");
    void *sconf = r->server->module_config, (*handler) (int);
    struct sockaddr_un cgi_addr;
    struct stat filinfo;
    cgisock_server_conf *conf =
    (cgisock_server_conf *) ap_get_module_config(sconf, &cgisock_module);
    core_dir_config *core_path =
    (core_dir_config *) ap_get_module_config(r->per_dir_config , &core_module);
    cgisock_dir_conf *d_conf =
    (cgisock_dir_conf *) ap_get_module_config(r->per_dir_config , &cgisock_module);
    
    if (!(d_conf->dirname || d_conf->anchor)) 
	return log_socketerror( r, conf , NOT_FOUND , APLOG_NOERRNO ,
		"Please set a CgisockPath directory before using CGISOCK");
    
    if (r->method_number == M_OPTIONS) {
	/* 99 out of 100 CGI scripts, this is all they support */
	r->allowed |= (1 << M_GET);
	r->allowed |= (1 << M_POST);	
	return DECLINED;
    }

    if (argv0 = strrchr(r->filename, '/'))
	argv0++;
    else
	argv0 = r->filename;

    nph = !(strncmp(argv0, "nph-", 4));

    if (nph && is_included)
	return log_socketerror(r, conf, FORBIDDEN, APLOG_NOERRNO,
			       "attempt to include NPH CGI script");

    if (retval = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))
	return retval;

    
#ifdef CHARSET_EBCDIC
    /* XXX:@@@ Is the generated/included output ALWAYS in text/ebcdic format? */
    /* Or must we check the Content-Type first? */
    ap_bsetflag(r->connection->client, B_EBCDIC2ASCII, 1);
#endif /*CHARSET_EBCDIC*/

    ap_add_common_vars(r);
    ap_add_cgi_vars(r);

    dbuf =  ap_pcalloc(r->pool , conf->bufbytes+1 );
    socket_buff = ap_bcreate( r->pool , B_RDWR | B_SOCKET );
    name_constr = ap_palloc( r->pool , DEFAULT_BUFBYTES ); 

/* Before we start grabbing sockets etc , lets do some sanity checks
 *    so that we know exactly what we are looking at ...       
 */
    *name_constr = 0;
    i= strlen(core_path->d);
    if ( d_conf->anchor ) strcpy( name_constr, d_conf->anchor ); 
	else {
	    tmp = r->uri + i;
    	    strcpy(name_constr , d_conf->dirname);
	    strcat(name_constr , tmp);
	};
#ifdef CGISOCK_LOG
    debuglog_cgisock( r , d_conf , conf , "core dir config" , 
	core_path->d);
    debuglog_cgisock( r , d_conf , conf , "URI" , r->uri);
    debuglog_cgisock( r , d_conf , conf , "Translated URI" , name_constr);   

#endif

    if (strncmp( r->uri , core_path->d , i) != 0) {
	return log_socketerror (r ,conf,NOT_FOUND,APLOG_NOERRNO,
		    "URI and config path dont match . This may be a bug"); 
    }

    stat( name_constr , &filinfo );
    if (!S_ISSOCK(filinfo.st_mode)) 
	return log_socketerror ( r , conf , NOT_FOUND ,APLOG_NOERRNO,
		    "CgisockPath and URI does not translate to a socket");

    cgi_sock = ap_psocket( r-> pool , AF_UNIX , SOCK_STREAM , PF_UNIX);
    if ( cgi_sock <= 0 ) 
	    return log_socketerror (r,conf,NOT_FOUND,APLOG_NOERRNO,
			"cannot allocate a socket"); 
    ap_note_cleanups_for_socket( r->pool , cgi_sock );
    ap_bpushfd( socket_buff , cgi_sock , cgi_sock );
    cgi_addr.sun_family = AF_UNIX;
    strcpy(cgi_addr.sun_path , name_constr);
    ap_hard_timeout("CGISOCK connect", r);
    i = sizeof(cgi_addr);
    res_con = connect( cgi_sock , (struct sockaddr *)&cgi_addr , i );
    ap_kill_timeout( r );

    if ( res_con ) {
	ap_log_error(APLOG_MARK,APLOG_ERR,r->server,
	    "Connect fail path:%s, fd:%d, len:%d",cgi_addr.sun_path,cgi_sock,i);		
    	return log_socketerror(r,conf,SERVER_ERROR,APLOG_ERR,
		"Cannot connect to socket");
    }


/* To help with future version support , if demand requires it , cgisock can 
 * be extended wrt what information it can give to the script on the other end
 * and what actions it can take . The 'ACCEPTED' response can be substituted
 * for other parameters ( say 'REQUEST_REC' whatever ) to produce other
 * actions without comprimising the basic function of cgisock ( to provide
 * a gateway over a socket ) . Although these functions are not implemented
 * yet , they will remain quietly in the backround until *someone* needs
 * needs them . To ensure backward compatability with the cgisock module
 * this version string will always be supplied first so that scripts can
 * *quickly* decide what action they want to take next depending on what
 * this cgisock module is capable of ... 
 *
 * OK, Here is where we need to stop pretending the version is an env-var
 *	and send out up to 8192 characters - in a LINE - of "prelude".
 *	Question is "What should the server learn right now?" pfv.
 * 	Of course, one of things I wouldnt mind knowing is the length of
 * 	line itself... mhv
 */

    env = ap_create_environment ( r->pool , r->subprocess_env);

    *argsbuffer = 0; pfv_discount = 0; k = 0; 
    for ( j = 1, i = 0 ; env[i] ; ++i ) 
    {
	char *pp;
	
	pp = env[i];
	while (*pp) if( strrchr("\r\n",*pp)) *(pp++) = 0; else pp++; 
	if ( env[i][0] && env[i][0]>32 ) {
#ifdef ARRAY_COUNT
	    sprintf(argsbuffer+strlen(argsbuffer), "%d,", strlen(env[i])+1 );
#else
	    j += strlen(env[i]) + 1;
#endif /* ARRAY_COUNT */
	}
#ifdef ARRAY_COUNT
	else pfv_discount++;
#endif	
    };
#ifdef ARRAY_COUNT
    *(argsbuffer+strlen(argsbuffer)-1) = ';';
#else
    if ( j > 99999999 ) 
	return log_socketerror ( r , conf,HTTP_BAD_GATEWAY,
	    APLOG_NOERRNO , "Environment args too long" );
#endif
    ap_hard_timeout("Writing env to socket ",r);
#ifdef ARRAY_COUNT
    ap_bprintf(socket_buff, "%s;%d:%s\n", CGISOCK_VERSION, i-pfv_discount, argsbuffer);
#else
    ap_bprintf(socket_buff, "%s;%08u;\n", CGISOCK_VERSION,j); 
#endif

    for ( i = 0 ; env[i] ; ++i ) {
	if ( env[i][0] && env[i][0]>32)
        ap_bprintf( socket_buff, "%s\n", env[i]);
    }
#ifndef ARRAY_COUNT
    ap_bprintf( socket_buff,"\n");
#endif

    ap_bflush(socket_buff);
    ap_kill_timeout(r);
    *name_constr = 0;
    res_con = cgi_select(cgi_sock, 0, d_conf->timeout, 0);
    if (res_con <= 0)  {
	ap_log_error(APLOG_MARK,APLOG_ERR,r->server,
	"Timeout:%d",d_conf->timeout);		
	return log_socketerror ( r , conf,HTTP_REQUEST_TIME_OUT,
	    APLOG_NOERRNO , "Timeout on cgisock");
    }
    len_read = ap_bread( socket_buff , dbuf , HUGE_STRING_LEN);
#ifdef CGISOCK_LOG
    debuglog_cgisock( r , d_conf , conf , "Socket reply" , dbuf);
#endif
    
/* Sorry about the huge if statement, this code really needs a cleanup...
 * This should have a real command parser rather than a quick strncmp...
 */
    if (!(strncmp(dbuf , "DECLINED" , 8))) return DECLINED;
/*****************************************************************************
 *****************************************************************************
 * 
 * Accepted..
 *
 */
    if (!(strncmp (dbuf ,"ACCEPTED", 8 ))) {
	    
   len_read = 0 ; dbpos = 0 ; ap_bflush(socket_buff);

    /* Transfer any put/post args, CERN style...
     * Note that if a buggy script fails to read everything we throw
     * at it, or a buggy client sends too much, we get a SIGPIPE, so
     * we have to ignore SIGPIPE while doing this.  CERN does the same
     * (and in fact, they pretty nearly guarantee themselves a SIGPIPE
     * on every invocation by chasing the real client data with a
     * spurious newline).
     */

    ap_hard_timeout("copy socket args", r);
#ifdef SIGPIPE
    handler = signal(SIGPIPE, SIG_IGN);
#endif
    if (ap_should_client_block(r)) {

	if (conf->logname) {
	    dbpos = 0;
	}

	
	len_read =0;
	while ((len_read =
		ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN)) > 0) {
	    if (conf->logname) {
		if ((dbpos + len_read) > conf->bufbytes) {
		    dbsize = conf->bufbytes - dbpos;
		}
		else {
		    dbsize = len_read;
		}
		memcpy(dbuf + dbpos, argsbuffer, dbsize);
		dbpos += dbsize;
	    }
	    ap_reset_timeout(r);
	    if (ap_bwrite(socket_buff, argsbuffer, len_read) < len_read) {
		/* script stopped reading, soak up remaining message */
		while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN) > 0) {
		
			    /* read the rest and forget about it */
		}
		break;
	    }
	}
    } else {
	len_read = ap_bwrite(socket_buff,"CGISOCK: no client block\0",25); 
	if (len_read<=0) {
	    ap_kill_timeout(r);
#ifdef SIGPIPE
	    signal(SIGPIPE,handler);
#endif
	    return log_socketerror( r , conf , HTTP_CONFLICT , 
		APLOG_NOERRNO , "CGI server must read the client block");
	}
    }	

	ap_bflush(socket_buff);	
	ap_kill_timeout(r);
#ifdef SIGPIPE
	signal(SIGPIPE, handler);
#endif

#ifdef CGISOCK_LOG
	debuglog_cgisock( r , d_conf , conf , "So far so good" , "reading body");
#endif
    
    /* Handle script return... */
    if (socket_buff && !nph) {
	const char *location;
	char sbuf[MAX_STRING_LEN];
	int ret;
    
        res_con = cgi_select(cgi_sock, 0, d_conf->timeout , 0 );
	if ( res_con <= 0 )  {
#ifdef CGISOCK_LOG
	    debuglog_cgisock( r , d_conf , conf , "Timeout on header" , sbuf);
	    log_socket(r, conf , d_conf , ret, dbuf, sbuf, socket_buff);
#endif
	    return log_socketerror ( r ,conf , HTTP_REQUEST_TIME_OUT , APLOG_NOERRNO ,
		    "Timeout on header");
	    }
	if ((ret = ap_scan_script_header_err_buff(r, socket_buff, sbuf))) {
#ifdef CGISOCK_LOG
	    debuglog_cgisock( r , d_conf , conf , "Failed header" , sbuf);
	    log_socket(r, conf , d_conf , ret, dbuf, sbuf, socket_buff);
#endif
	    return log_socketerror ( r ,conf , HTTP_BAD_GATEWAY , APLOG_NOERRNO ,
		    "Malformed header return by socket");
	}

	
#ifdef CHARSET_EBCDIC
        /* Now check the Content-Type to decide if conversion is needed */
        ap_checkconv(r);
#endif /*CHARSET_EBCDIC*/

	location = ap_table_get(r->headers_out, "Location");


	if (location && location[0] == '/' && r->status == 200) {

	    /* Soak up all the script output */
        res_con = cgi_select(cgi_sock, 0, d_conf->timeout, 0 );
	if (res_con < 0)  
	    return log_socketerror ( r ,conf , HTTP_REQUEST_TIME_OUT , APLOG_NOERRNO ,
		    "Request body timeout");

	    ap_hard_timeout("read from socket", r);
	    while (ap_bgets(argsbuffer, HUGE_STRING_LEN, socket_buff) > 0) {
		continue;
	    }
	    
	    ap_kill_timeout(r);
	    	    
	    /* This redirect needs to be a GET no matter what the original
	     * method was.
	     */
	    r->method = ap_pstrdup(r->pool, "GET");
	    r->method_number = M_GET;

	    /* We already read the message body (if any), so don't allow
	     * the redirected request to think it has one.  We can ignore 
	     * Transfer-Encoding, since we used REQUEST_CHUNKED_ERROR.
	     */
	    ap_table_unset(r->headers_in, "Content-Length");

	    ap_internal_redirect_handler(location, r);
	    return OK ;
	}
	else if (location && r->status == 200) {
	    /* XX Note that if a script wants to produce its own Redirect
	     * body, it now has to explicitly *say* "Status: 302"
	     */
	    return REDIRECT;
	}

	ap_send_http_header(r);
	if (!r->header_only) {
	    ap_send_fb(socket_buff, r);
	}
	ap_bclose(socket_buff);
    }

    if (socket_buff && nph) {
	ap_send_fb(socket_buff, r);
    }

    return OK;			/* NOT r->status, even if it has changed. */
    } /* if ACCEPTED */
/*****************************************************************************
 *****************************************************************************
 *
 * Stream mode
 *
 */
    if (!(strncmp (dbuf ,"STREAM", 6 ))) {

/* We jump straight to streaming mode with no possibility for redirects.
 * You can redirect too a streamer interface, but not from a server declaring
 * itself as a streamer interface then deciding to redirect or do something
 * else that doesnt fit with this model. The intent here is to provide a 
 * facility to handle nice things like VRML event interfaces....Possibly
 * Dynamic HTML if it ever eventuates one day...This mode will run until
 * either side closes its end. Note this facility is experimental, you
 * have been warned.
 */	
    tmp = dbuf + 6;
    if ( !strncmp( tmp, "_OUT", 4)) {
	cgis_client = socket_buff;
	socket_buff = r->connection->client;
	log_socketerror( r , conf , HTTP_OK, APLOG_INFO ,"Stream out selected");

	} else {
	    cgis_client= r->connection->client;
	    log_socketerror( r , conf , HTTP_OK, APLOG_INFO ,"Stream (in) selected");
	};
	    
	
#ifdef SIPIPE
    handler = signal( SIGPIPE, SIG_IGN);
#endif

    while(1) {
	FD_ZERO( &rd_set );
	FD_SET(cgis_client->fd, &rd_set);
	FD_SET(socket_buff->fd, &rd_set);
	tv.tv_usec = 0; tv.tv_sec = d_conf->timeout;
	i = ( cgis_client->fd > socket_buff->fd ) ?
	    cgis_client->fd + 1: socket_buff->fd + 1;
	if (!select(i, &rd_set, NULL, NULL, &tv )) break;
	if (FD_ISSET( socket_buff->fd, &rd_set)) {
	    ap_log_error(APLOG_MARK, APLOG_INFO, r->server, 
		"Caught bump off from receiver");
	    break;
	    }; 
	if ( B_EOF & cgis_client->flags ) {
		ap_log_error(APLOG_MARK, APLOG_INFO, r->server, 
		"Stream read error: %d", cgis_client->flags);
		break;
	};
	if ( B_EOUT & socket_buff->flags ) {
    		ap_log_error(APLOG_MARK,APLOG_INFO, r->server, 
		"Stream write error: %d", cgis_client->flags);
		break;
	};
	len_read = ap_bread( cgis_client, argsbuffer, DEFAULT_BUFBYTES);
	i = 0;
	while ((i = ap_bwrite( socket_buff, argsbuffer, len_read)) < len_read ) {
	    /* Do a select then try again */
	    if ( i < 0 ) break;
	    len_read -= i;
	    if ( !i && (cgi_select( 0, socket_buff->fd,d_conf->timeout,0)< 1))
		break;
	    };
	    /* rigth, now flush it. Unix buffering is out to get you...*/
	    ap_bflush( socket_buff ); 
	    if ( len_read - i > 0 ) {
    		ap_log_error(APLOG_MARK, APLOG_INFO, r->server, 
		"Stream write done");
		break;
	    };
    };

#ifdef SIPIPE
    signal( SIGPIPE, handler);
#endif
    log_socketerror( r , conf , HTTP_OK, APLOG_INFO ,"Stream complete");
    return OK;
    };
    
/* Additional methods can be added here if required */


    return log_socketerror( r , conf , NOT_ACCEPTABLE , APLOG_NOERRNO ,
	"Socket refused connection or version conflict");

} /* cgisock handler */

static const handler_rec cgisock_handlers[] =
{
    { CGISOCK_MAGIC_TYPE , cgisock_handler},
    {"cgi-socket", cgisock_handler},
    {NULL}
};

module MODULE_VAR_EXPORT cgisock_module =
{
    STANDARD_MODULE_STUFF,
    NULL,			/* initializer */
    create_dir_config,		/* dir config creater */
    merge_dir_config,		/* dir merger --- default is to override */
    create_server_config,		/* server config */
    merge_server_config,		/* merge server config */
    cgisock_cmds,		/* command table */
    cgisock_handlers,		/* handlers */
    NULL,			/* filename translation */
    NULL,			/* check_user_id */
    NULL,			/* check auth */
    NULL,			/* check access */
    NULL,			/* type_checker */
    NULL,			/* fixups */
    NULL,			/* logger */
    NULL,			/* header parser */
    NULL,			/* child_init */
    NULL,			/* child_exit */
    NULL			/* post read-request */
};
