Skip to content
Snippets Groups Projects
websrvr.c 84.9 KiB
Newer Older
	session->req.send_location=NO_LOCATION;
	SAFECOPY(error_code,message);
	sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
deuce's avatar
deuce committed
	session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
	send_headers(session,message);
	if(!stat(session->req.physical_path,&sb)) {
		int	snt=0;
		snt=sock_sendfile(session->socket,session->req.physical_path);
		if(snt<0)
			snt=0;
		if(session->req.ld!=NULL)
			session->req.ld->size=snt;
		lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
			,session->socket,session->req.physical_path);
		safe_snprintf(sbuf,sizeof(sbuf)
			,"<HTML><HEAD><TITLE>%s Error</TITLE></HEAD>"
			"<BODY><H1>%s Error</H1><BR><H3>In addition, "
			"I can't seem to find the %s error file</H3><br>"
			"please notify <a href=\"mailto:sysop@%s\">"
			"%s</a></BODY></HTML>"
			,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
		sockprint(session->socket,sbuf);
		if(session->req.ld!=NULL)
			session->req.ld->size=strlen(sbuf);
static BOOL check_ars(http_session_t * session)
{
	char	*username;
	char	*password;
	uchar	*ar;
	char	auth_req[MAX_REQUEST_LINE+1];
		if(startup->options&WEB_OPT_DEBUG_RX)
			lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket);
	SAFECOPY(auth_req,session->req.auth);
	username=strtok(auth_req,":");
	password=strtok(NULL,":");
	/* Require a password */
	if(password==NULL)
	session->user.number=matchuser(&scfg, username, FALSE);
	if(session->user.number==0) {
		SAFECOPY(session->username,unknown);
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s"
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s"
	getuserdat(&scfg, &session->user);
	if(session->user.pass[0] && stricmp(session->user.pass,password)) {
		SAFECOPY(session->username,unknown);
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
				,session->socket,username,password,session->user.pass);
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
	ar = arstr(NULL,session->req.ars,&scfg);
	authorized=chk_ar(&scfg,ar,&session->user);
deuce's avatar
deuce committed
		FREE_AND_NULL(ar);
deuce's avatar
deuce committed
	if(session->req.dynamic==IS_SSJS)  {
		if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
			,NULL /* ftp index file */, NULL /* subscan */)) 
			lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
	if(authorized)  {
		if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)  {
			add_env(session,"AUTH_TYPE","Basic");
			/* Should use real name if set to do so somewhere ToDo */
			add_env(session,"REMOTE_USER",session->user.alias);
		}
		if(session->req.ld!=NULL)
			session->req.ld->user=strdup(username);
		SAFECOPY(session->username,username);
	SAFECOPY(session->username,unknown);

	lprintf(LOG_WARNING,"%04d !AUTHORIZATION FAILURE for user %s, ARS: %s"
		,session->socket,username,session->req.ars);
static BOOL read_mime_types(char* fname)
{
	char	str[1024];
	char *	ext;
	char *	type;
	FILE*	mime_config;

	if((mime_config=fopen(fname,"r"))==NULL)
	while (!feof(mime_config)&&mime_count<MAX_MIME_TYPES) {
		if(fgets(str,sizeof(str),mime_config)!=NULL) {
			truncsp(str);
			ext=strtok(str," \t");
			if(ext!=NULL) {
				while(*ext && *ext<=' ') ext++;
				if(*ext!=';') {
					type=strtok(NULL," \t");
					if(type!=NULL) {
						while(*type && *type<=' ') type++;
						if(strlen(ext)>0 && strlen(type)>0) {
							SAFECOPY((mime_types[mime_count]).ext,ext);
							SAFECOPY((mime_types[mime_count++]).type,type);
						}
					}
				}
			}
		}
	}
	fclose(mime_config);
	lprintf(LOG_DEBUG,"Loaded %d mime types", mime_count);
static int sockreadline(http_session_t * session, char *buf, size_t length)
	for(i=0;TRUE;) {
		if(!socket_check(session->socket,&rd,NULL,60000) || !rd || recv(session->socket, &ch, 1, 0)!=1)  {
			session->req.keep_alive=FALSE;
			close_request(session);
			session->socket=INVALID_SOCKET;
			return(-1);        /* time-out */
		}

		if(ch=='\n')
			break;

		if(i<length)
			buf[i++]=ch;
	}

	/* Terminate at length if longer */
	if(i>length)
		i=length;
		
	if(i>0 && buf[i-1]=='\r')
		buf[--i]=0;
	if(startup->options&WEB_OPT_DEBUG_RX)
		lprintf(LOG_DEBUG,"%04d RX: %s",session->socket,buf);
	return(i);
}

static int pipereadline(int pipe, char *buf, size_t length)
{
	char	ch;
	DWORD	i;
	time_t	start;

	start=time(NULL);
		if(time(NULL)-start>startup->max_cgi_inactivity) {
			return(-1);
		}
		
			start=time(NULL);
			
			if(ch=='\n')
				break;

			if(i<length)
				buf[i++]=ch;
		}
	}

	/* Terminate at length if longer */
	if(i>length)
		i=length;
		
	if(i>0 && buf[i-1]=='\r')
		buf[--i]=0;
	return(i);
int recvbufsocket(int sock, char *buf, long count)
{
	int		rd=0;
	int		i;
	time_t	start;
	while(rd<count && socket_check(sock,NULL,NULL,60000))  {
		i=recv(sock,buf,count-rd,0);
		if(i<=0)  {
			*buf=0;
			return(0);
		}

		rd+=i;
		start=time(NULL);
	return(0);
/* Wasn't this done up as a JS thing too?  ToDo */
static void unescape(char *p)
{
	char *	dst;
	char	code[3];
	
	dst=p;
	for(;*p;p++) {
		if(*p=='%' && isxdigit(*(p+1)) && isxdigit(*(p+2))) {
			sprintf(code,"%.2s",p+1);
			*(dst++)=(char)strtol(code,NULL,16);
			p+=2;
		}
		else  {
			if(*p=='+')  {
				*(dst++)=' ';
			}
			else  {
				*(dst++)=*p;
			}
		}
	}
	*(dst)=0;
}

static void js_parse_post(http_session_t * session)  
{
	char		*p;
	char		*key;
	char		*value;
	JSString*	js_str;

	if(session->req.post_data == NULL)
		return;

	p=session->req.post_data;
	while((key=strtok(p,"="))!=NULL)  {
		p=NULL;
		if(key == NULL)
			continue;
		value=strtok(NULL,"&");
		if(value == NULL)
			continue;

		unescape(value);
		unescape(key);
		if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL)
			return;
		JS_DefineProperty(session->js_cx, session->js_query, key, STRING_TO_JSVAL(js_str)
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
static void js_add_header(http_session_t * session, char *key, char *value)  
{
	JSString*	js_str;

	if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL)
		return;
	JS_DefineProperty(session->js_cx, session->js_header, key, STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
}

static BOOL parse_headers(http_session_t * session)
{
	char	req_line[MAX_REQUEST_LINE+1];
	char	next_char[2];
	char	*value;
	char	*p;
	int		i;
	char	env_name[128];
	while(sockreadline(session,req_line,sizeof(req_line)-1)>0) {
deuce's avatar
deuce committed
		/* Multi-line headers */
		while((recvfrom(session->socket,next_char,1,MSG_PEEK,NULL,0)>0) 
			&& (next_char[0]=='\t' || next_char[0]==' ')) {
			sockreadline(session,req_line+i,sizeof(req_line)-i-1);
		if((value=strtok(NULL,""))!=NULL) {
			i=get_header_type(req_line);
			while(*value && *value<=' ') value++;
			if(session->req.dynamic==IS_SSJS)
				js_add_header(session,req_line,value);
			switch(i) {
				case HEAD_AUTH:
					strtok(value," ");
					p=strtok(NULL," ");
					if(p==NULL)
						break;
					while(*p && *p<' ') p++;
					b64_decode(session->req.auth,sizeof(session->req.auth),p,strlen(p));
					if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)
						add_env(session,"CONTENT_LENGTH",value);
					content_len=atoi(value);
					if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)
						add_env(session,"CONTENT_TYPE",value);
					break;
				case HEAD_IFMODIFIED:
					session->req.if_modified_since=decode_date(value);
					break;
				case HEAD_CONNECTION:
					if(!stricmp(value,"Keep-Alive")) {
						session->req.keep_alive=TRUE;
					}
					if(!stricmp(value,"Close")) {
						session->req.keep_alive=FALSE;
					}
					if(session->req.host[0]==0) {
						SAFECOPY(session->req.host,value);
						if(startup->options&WEB_OPT_DEBUG_RX)
							lprintf(LOG_INFO,"%04d Grabbing from virtual host: %s"
					if(session->req.ld!=NULL)
						session->req.ld->referrer=strdup(value);
					if(session->req.ld!=NULL)
						session->req.ld->agent=strdup(value);
			if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)  {
				sprintf(env_name,"HTTP_%s",req_line);
				add_env(session,env_name,value);
			}
		if((session->req.post_data=malloc(content_len+1)) != NULL)  {
			recvbufsocket(session->socket,session->req.post_data,content_len);
			if(session->req.dynamic==IS_SSJS)  {
				js_parse_post(session);
			}
			session->req.post_data[content_len]=0;
			lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,content_len);
			return(FALSE);
	if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)
		add_env(session,"SERVER_NAME",session->req.host[0] ? session->req.host : startup->host_name );
	return TRUE;
}

static int get_version(char *p)
{
	int		i;
	if(p==NULL)
		return(0);
	while(*p && *p<' ') p++;
	if(*p==0)
		return(0);
	for(i=1;http_vers[i]!=NULL;i++) {
		if(!stricmp(p,http_vers[i])) {
			return(i);
		}
	}
	return(i-1);
}

static void js_parse_query(http_session_t * session, char *p)  {
	char	*key;
	char	*value;
	JSString*	js_str;
	
	while((key=strtok(p,"="))!=NULL)  {
		p=NULL;
		if(key != NULL)  {
			value=strtok(NULL,"&");
			if(value != NULL)  {
				unescape(value);
				unescape(key);
				if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL)
					return;
				JS_DefineProperty(session->js_cx, session->js_query, key, STRING_TO_JSVAL(js_str)
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
			}
		}
	}
}

static int is_dynamic_req(http_session_t* session)
{
	char	drive[4];
	char	dir[MAX_PATH+1];
	char	fname[MAX_PATH+1];
	char	ext[MAX_PATH+1];
	_splitpath(session->req.physical_path, drive, dir, fname, ext);
	if(!(startup->options&BBS_OPT_NO_JAVASCRIPT) && stricmp(ext,startup->ssjs_ext)==0)  {
		lprintf(LOG_INFO,"%04d Setting up JavaScript support", session->socket);
			lprintf(LOG_ERR,"%04d !ERROR setting up JavaScript support", session->socket);
			send_error(session,error_500);
			return(IS_STATIC);
		}
	
		sprintf(path,"%s/SBBS_SSJS.%d.html",startup->cgi_temp_dir,session->socket);
		if((session->req.fp=fopen(path,"w"))==NULL) {
			lprintf(LOG_ERR,"%04d !ERROR %d opening/creating %s", session->socket, errno, path);
			send_error(session,error_500);
	if(!(startup->options&WEB_OPT_NO_CGI)) {
		for(i=0; startup->cgi_ext!=NULL && startup->cgi_ext[i]!=NULL; i++)  {
			if(stricmp(ext,startup->cgi_ext[i])==0)  {
				return(IS_CGI);
			}
		}
		/* It would be more secure if this was a true physical directory comparision */
		sprintf(path,"/%s/",cgi_dir);
		if(stricmp(dir,path)==0)  {
			return(IS_CGI);
static char *get_request(http_session_t * session, char *req_line)
	char*	query;
	SAFECOPY(session->req.virtual_path,req_line);
	strtok(session->req.virtual_path," \t");
	retval=strtok(NULL," \t");
	strtok(session->req.virtual_path,"?");
	query=strtok(NULL,"");

	/* Must initialize physical_path before calling is_dynamic_req() */
	SAFECOPY(session->req.physical_path,session->req.virtual_path);
	unescape(session->req.physical_path);
	if(!strnicmp(session->req.physical_path,http_scheme,http_scheme_len)) {
		/* Set HOST value... ignore HOST header */
		SAFECOPY(session->req.host,session->req.physical_path+http_scheme_len);
		strtok(session->req.physical_path,"/");
		p=strtok(NULL,"/");
		if(p==NULL) {
			/* Do not allow host values larger than 128 bytes */
			session->req.host[0]=0;
			p=session->req.physical_path+http_scheme_len;
		}
		offset=p-session->req.physical_path;
		memmove(session->req.physical_path
			,session->req.physical_path+offset
			,strlen(session->req.physical_path+offset)+1	/* move '\0' terminator too */
			);
	}

	session->req.dynamic=is_dynamic_req(session);
	if(session->req.dynamic)
		SAFECOPY(session->req.query_str,query);
	if(query!=NULL)  {
		switch(session->req.dynamic) {
				add_env(session,"QUERY_STRING",query);
				js_parse_query(session,query);
static char *get_method(http_session_t * session, char *req_line)
{
	int i;

	for(i=0;methods[i]!=NULL;i++) {
		if(!strnicmp(req_line,methods[i],strlen(methods[i]))) {
			session->req.method=i;
			if(strlen(req_line)<strlen(methods[i])+2) {
				send_error(session,"400 Bad Request");
				return(NULL);
			}
			return(req_line+strlen(methods[i])+1);
		}
	}
rswindell's avatar
rswindell committed
	if(req_line!=NULL && *req_line>=' ')
		send_error(session,"501 Not Implemented");
static BOOL get_req(http_session_t * session, char *request_line)
	char	req_line[MAX_REQUEST_LINE+1];

	req_line[0]=0;
	if(request_line == NULL) {
		if(sockreadline(session,req_line,sizeof(req_line)-1)<0)
		lprintf(LOG_DEBUG,"%04d Handling Internal Redirect to: %s",session->socket,request_line);
		SAFECOPY(req_line,request_line);
	}
	if(session->req.ld!=NULL)
		session->req.ld->request=strdup(req_line);
		if(startup->options&WEB_OPT_DEBUG_RX)
			lprintf(LOG_DEBUG,"%04d Got request line: %s",session->socket,req_line);
		p=NULL;
		p=get_method(session,req_line);
		if(p!=NULL) {
			p=get_request(session,p);
			session->http_ver=get_version(p);
			if(session->http_ver>=HTTP_1_1)
				session->req.keep_alive=TRUE;
			if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)  {
				add_env(session,"REQUEST_METHOD",methods[session->req.method]);
				add_env(session,"SERVER_PROTOCOL",session->http_ver ? 
					http_vers[session->http_ver] : "HTTP/0.9");
			return(TRUE);
	session->req.keep_alive=FALSE;
	close_request(session);
	return FALSE;
}

/* This may exist somewhere else - ToDo */
static char *find_last_slash(char *str)
{
#ifdef _WIN32
	char * LastFSlash;
	char * LastBSlash;
	LastFSlash=strrchr(str,'/');
	LastBSlash=strrchr(str,'\\');
	if(LastFSlash==NULL)
		return(LastBSlash);
	if(LastBSlash==NULL)
		return(LastFSlash);
	if(LastBSlash < LastFSlash)
		return(LastFSlash);
	return(LastBSlash);
#else
	return(strrchr(str,'/'));
#endif
}

/* This may exist somewhere else - ToDo */
static char *find_first_slash(char *str)
{
#ifdef _WIN32
	char * FirstFSlash;
	char * FirstBSlash;
	FirstFSlash=strchr(str,'/');
	FirstBSlash=strchr(str,'\\');
	if(FirstFSlash==NULL)
		return(FirstBSlash);
	if(FirstBSlash==NULL)
		return(FirstFSlash);
	if(FirstBSlash > FirstFSlash)
		return(FirstFSlash);
	return(FirstBSlash);
#else
	return(strchr(str,'/'));
#endif
}

static BOOL check_extra_path(http_session_t * session, char *path)
{
	char	*p;
	char	rpath[MAX_PATH+1];
	char	vpath[MAX_PATH+1];
	char	epath[MAX_PATH+1];
	char	str[MAX_PATH+1];
	struct	stat sb;

	epath[0]=0;
	if(((stat(session->req.physical_path,&sb))==-1) /* && errno==ENOTDIR */)
	{
		SAFECOPY(vpath,session->req.virtual_path);
		SAFECOPY(rpath,path);

		while((p=find_last_slash(vpath))!=NULL)
		{
			*p=0;
			if((p=find_last_slash(rpath))==NULL)
				return(FALSE);
			*p=0;
			SAFECOPY(str,epath);
			sprintf(epath,"/%s%s",(p+1),str);
			if(stat(rpath,&sb)!=-1 && (!(sb.st_mode&S_IFDIR)))
			{
				SAFECOPY(session->req.extra_path_info,epath);
				SAFECOPY(session->req.virtual_path,vpath);
				/* This is dependent on the size of path in check_request() */
				sprintf(path,"%.*s",MAX_PATH,rpath);
				session->req.dynamic=IS_CGI;
				return(TRUE);
			}
		}
	}
	return(FALSE);
}

static BOOL check_request(http_session_t * session)
{
	char	path[MAX_PATH+1];
	char	str[MAX_PATH+1];
deuce's avatar
deuce committed
	char*	p;
	struct stat sb;
	if(!(startup->options&WEB_OPT_VIRTUAL_HOSTS))
		session->req.host[0]=0;
	if(session->req.host[0]) {
		sprintf(str,"%s/%s",root_dir,session->req.host);
		if(isdir(str))
			sprintf(str,"%s/%s%s",root_dir,session->req.host,session->req.physical_path);
		sprintf(str,"%s%s",root_dir,session->req.physical_path);
deuce's avatar
deuce committed
	if(FULLPATH(path,str,sizeof(session->req.physical_path))==NULL) {
		send_error(session,error_404);
deuce's avatar
deuce committed
	if(startup->options&WEB_OPT_DEBUG_TX)
		lprintf(LOG_DEBUG,"%04d Path is: %s",session->socket,path);
		if(!IS_PATH_DELIM(last_ch))  {
deuce's avatar
deuce committed
			session->req.send_location=MOVED_PERM;
			strcat(path,"/");
		last_ch=*lastchar(session->req.virtual_path);
		if(!IS_PATH_DELIM(last_ch))  {
deuce's avatar
deuce committed
			session->req.send_location=MOVED_PERM;
			strcat(session->req.virtual_path,"/");
		}
		last_slash=find_last_slash(path);
deuce's avatar
deuce committed
		if(last_slash==NULL) {
			send_error(session,error_404);
deuce's avatar
deuce committed
			return(FALSE);
		}
		for(i=0; startup->index_file_name!=NULL && startup->index_file_name[i]!=NULL ;i++)  {
			*last_slash=0;
			strcat(path,startup->index_file_name[i]);
deuce's avatar
deuce committed
			if(startup->options&WEB_OPT_DEBUG_TX)
				lprintf(LOG_DEBUG,"%04d Checking for %s",session->socket,path);
			if(!stat(path,&sb))
deuce's avatar
deuce committed
				break;
		if(startup->index_file_name[i] == NULL)  {
			send_error(session,error_404);
deuce's avatar
deuce committed
		strcat(session->req.virtual_path,startup->index_file_name[i]);
		if(session->req.send_location != MOVED_PERM)
			session->req.send_location=MOVED_STAT;
	if(strnicmp(path,root_dir,strlen(root_dir))) {
		send_error(session,"400 Bad Request");
		lprintf(LOG_NOTICE,"%04d !ERROR Request for %s is outside of web root %s"
			,session->socket,path,root_dir);
	if(stat(path,&sb)) {
		/* Check if sneaky CGI script */
		if(!check_extra_path(session,path))
		{
			if(startup->options&WEB_OPT_DEBUG_TX)
				lprintf(LOG_DEBUG,"%04d 404 - %s does not exist",session->socket,path);
			send_error(session,error_404);
			return(FALSE);
		}
	SAFECOPY(session->req.physical_path,path);
	if(session->req.dynamic==IS_CGI)  {
		add_env(session,"SCRIPT_NAME",session->req.virtual_path);
	}
	SAFECOPY(str,session->req.virtual_path);
	last_slash=find_last_slash(str);
	if(session->req.dynamic==IS_CGI && *(session->req.extra_path_info))
	{
		sprintf(str,"%s%s",startup->root_dir,session->req.extra_path_info);
		add_env(session,"PATH_TRANSLATED",str);
		add_env(session,"PATH_INFO",session->req.extra_path_info);
	}

	/* Set default ARS to a 0-length string */
	session->req.ars[0]=0;
	/* Walk up from root_dir checking for access.ars */
	SAFECOPY(str,path);
	last_slash=str+strlen(root_dir)-1;
	/* Loop while there's more /s in path*/
deuce's avatar
deuce committed
	p=last_slash;
	while((last_slash=find_first_slash(p+1))!=NULL) {
deuce's avatar
deuce committed
		p=last_slash;
		/* Terminate the path after the slash */
		*(last_slash+1)=0;
		strcat(str,"access.ars");
		if(!stat(str,&sb)) {
			if(!strcmp(path,str)) {
				send_error(session,"403 Forbidden");
				return(FALSE);
			}
			/* Read access.ars file */
			if((file=fopen(str,"r"))!=NULL) {
rswindell's avatar
rswindell committed
				fgets(session->req.ars,sizeof(session->req.ars),file);
				fclose(file);
			}
			else  {
				/* If cannot open access.ars, only allow sysop access */
				SAFECOPY(session->req.ars,"LEVEL 90");
				break;
			}
			/* Truncate at \r or \n - can use last_slash since I'm done with it.*/
			truncsp(session->req.ars);
		}
		SAFECOPY(str,path);
	}
	
	if(session->req.ars[0]) {
		if(!check_ars(session)) {
			/* No authentication provided */
			sprintf(str,"401 Unauthorized%s%s: Basic realm=\"%s\""
				,newline,get_header(HEAD_WWWAUTH),scfg.sys_name);
			send_error(session,str);
			return(FALSE);
	else
		SAFECOPY(session->username,unknown);
static BOOL exec_cgi(http_session_t *session)
{
	char	cmdline[MAX_PATH+256];
#ifdef __unix__
	/* ToDo: Damn, that's WAY too many variables */
	int		i=0;
	int		status=0;
	pid_t	child=0;
	int		in_pipe[2];
	int		out_pipe[2];
	int		err_pipe[2];
	struct timeval tv={0,0};
	int		high_fd=0;
	char	buf[1024];
	size_t	post_offset=0;
	BOOL	done_parsing_headers=FALSE;
	BOOL	done_reading=FALSE;
	char	cgi_status[MAX_REQUEST_LINE+1];
	char	header[MAX_REQUEST_LINE+1];
	char	*directive=NULL;
	char	*value=NULL;
	BOOL	got_valid_headers=FALSE;
	time_t	start;
	char	cgipath[MAX_PATH+1];
	char	*p;
	SAFECOPY(cmdline,session->req.physical_path);
	lprintf(LOG_INFO,"%04d Executing %s",session->socket,cmdline);
	/* ToDo: Should only do this if the Content-Length header was NOT sent */
	session->req.keep_alive=FALSE;

	/* Set up I/O pipes */
	if(pipe(in_pipe)!=0) {
		lprintf(LOG_ERR,"%04d Can't create in_pipe",session->socket,buf);
		lprintf(LOG_ERR,"%04d Can't create out_pipe",session->socket,buf);
		lprintf(LOG_ERR,"%04d Can't create err_pipe",session->socket,buf);
		return(FALSE);
	}

	if((child=fork())==0)  {
		/* Do a full suid thing. */
		if(startup->setuid!=NULL)
			startup->setuid(TRUE);

		/* Set up environment */
		while(session->req.cgi_env != NULL)  {
			putenv(session->req.cgi_env->val);
			session->req.cgi_env=session->req.cgi_env->next;
		}
		
		/* Set up STDIO */
		close(in_pipe[1]);		/* close write-end of pipe */
		dup2(in_pipe[0],0);		/* redirect stdin */
		close(in_pipe[0]);		/* close excess file descriptor */
		close(out_pipe[0]);		/* close read-end of pipe */
		dup2(out_pipe[1],1);	/* stdout */
		close(out_pipe[1]);		/* close excess file descriptor */
		close(err_pipe[0]);		/* close read-end of pipe */
		dup2(err_pipe[1],2);	/* stderr */
		close(err_pipe[1]);		/* close excess file descriptor */

		SAFECOPY(cgipath,cmdline);
		if((p=strrchr(cgipath,'/'))!=NULL)
		{
			*p=0;
			chdir(cgipath);
		}

		/* Execute command */
		execl(cmdline,cmdline,NULL);
		lprintf(LOG_ERR,"%04d !FAILED! execl()",session->socket);
		exit(EXIT_FAILURE); /* Should never happen */
	}
	close(in_pipe[0]);		/* close excess file descriptor */
	close(out_pipe[1]);		/* close excess file descriptor */
	close(err_pipe[1]);		/* close excess file descriptor */
	start=time(NULL);
	if(child==0)  {
		close(in_pipe[1]);		/* close write-end of pipe */
		close(out_pipe[0]);		/* close read-end of pipe */
		close(err_pipe[0]);		/* close read-end of pipe */
		return(FALSE);
	}
	post_offset+=write(in_pipe[1],
		session->req.post_data+post_offset,
		session->req.post_len-post_offset);
	high_fd=out_pipe[0];
	if(err_pipe[0]>high_fd)
		high_fd=err_pipe[0];
	if(in_pipe[1]>high_fd)
		high_fd=in_pipe[1];
	if(session->socket>high_fd)
		high_fd=session->socket;
	/* ToDo: Magically set done_parsing_headers for nph-* scripts */
	cgi_status[0]=0;
	while(!done_reading)  {
		if(!done_wait)
			done_wait = (waitpid(child,&status,WNOHANG)==child);
		tv.tv_sec=startup->max_cgi_inactivity;
		tv.tv_usec=0;
		FD_ZERO(&read_set);
		FD_SET(out_pipe[0],&read_set);
		FD_SET(err_pipe[0],&read_set);
		FD_SET(session->socket,&read_set);
		FD_ZERO(&write_set);
		if(post_offset < session->req.post_len)
			FD_SET(in_pipe[1],&write_set);

		if(!done_reading && (select(high_fd+1,&read_set,&write_set,NULL,&tv)>0))  {
			if(FD_ISSET(session->socket,&read_set))  {
				if(recv(session->socket,&ch,1,MSG_PEEK) < 1) /* Is there no data waiting? */
				{
					done_reading=TRUE;
				}
			}
			if(FD_ISSET(in_pipe[1],&write_set))  {
				if(post_offset < session->req.post_len)  {
					i=write(in_pipe[1],
						session->req.post_data+post_offset,
						session->req.post_len-post_offset);
					post_offset += i;
					if(i<0)
						done_reading=TRUE;
					else if(post_offset>=session->req.post_len)
					else if(i!=post_offset)
			}
			if(FD_ISSET(out_pipe[0],&read_set))  {
				if(done_parsing_headers && got_valid_headers)  {
					i=read(out_pipe[0],buf,sizeof(buf));
						if(session->req.method!=HTTP_HEAD) {
							snt=write(session->socket,buf,i);
							if(session->req.ld!=NULL && snt>0) {
								session->req.ld->size+=snt;
					i=pipereadline(out_pipe[0],buf,sizeof(buf));
					if(i<0)  {
						done_reading=TRUE;
						got_valid_headers=FALSE;
					}
					if(!done_parsing_headers && *buf)  {
						SAFECOPY(header,buf);