Skip to content
Snippets Groups Projects
websrvr.c 143 KiB
Newer Older
					if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
						while((key=strtok_r(p,"=",&last))!=NULL) {
							if((val=strtok_r(p,";\t\n\v\f\r ",&last))!=NULL) {	/* Whitespace */
				default:
					break;
			}
		}
	}
	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 int is_dynamic_req(http_session_t* session)
{
	char	fname[MAX_PATH+1];
	char	ext[MAX_PATH+1];
	check_extra_path(session);
	_splitpath(session->req.physical_path, drive, dir, fname, ext);
	if(stricmp(ext,startup->ssjs_ext)==0)
		i=IS_SSJS;
	else if(get_xjs_handler(ext,session))
		i=IS_SSJS;
	else if(stricmp(ext,startup->js_ext)==0)
		i=IS_JS;
	if(!(startup->options&BBS_OPT_NO_JAVASCRIPT) && i)  {
		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);
	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)  {
				init_enviro(session);
		_splitpath(session->req.cgi_dir?session->req.cgi_dir:cgi_dir, cgidrive, cgidir, fname, ext);
		if(stricmp(dir,cgidir)==0 && stricmp(drive,cgidrive)==0)  {
			init_enviro(session);
			return(IS_CGI);
static char *get_request(http_session_t * session, char *req_line)
	char*	query;
	SAFECOPY(session->req.virtual_path,req_line);
	if(strtok_r(session->req.virtual_path," \t",&last))
		retval=strtok_r(NULL," \t",&last);
	else
		retval=NULL;
	SAFECOPY(session->req.request_line,session->req.virtual_path);
	if(strtok_r(session->req.virtual_path,"?",&last))
		query=strtok_r(NULL,"",&last);
	else
		query=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);
		SAFECOPY(session->req.vhost,session->req.host);
		/* Remove port specification */
		strtok_r(session->req.vhost,":",&last);
		if(strtok_r(session->req.physical_path,"/",&last))
			p=strtok_r(NULL,"/",&last);
		else
			p=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 */
			);
	}
		SAFECOPY(session->req.query_str,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_request_headers(http_session_t * session)
{
	char	head_line[MAX_REQUEST_LINE+1];
	char	next_char;
	char	*value;
	int		i;

	while(sockreadline(session,head_line,sizeof(head_line)-1)>0) {
		/* Multi-line headers */
rswindell's avatar
rswindell committed
		while((i=recv(session->socket,&next_char,1,MSG_PEEK))>0
			&& (next_char=='\t' || next_char==' ')) {
				close_socket(&session->socket);
			i=strlen(head_line);
			if(i>sizeof(head_line)-1) {
				lprintf(LOG_ERR,"%04d !ERROR long multi-line header. The web server is broken!", session->socket);
				i=sizeof(head_line)/2;
				break;
			}
			sockreadline(session,head_line+i,sizeof(head_line)-i-1);
		}
		strListPush(&session->req.headers,head_line);
		if((strtok_r(head_line,":",&last))!=NULL && (value=strtok_r(NULL,"",&last))!=NULL) {
			i=get_header_type(head_line);
			while(*value && *value<=' ') value++;
			switch(i) {
				case HEAD_HOST:
					if(session->req.host[0]==0) {
						SAFECOPY(session->req.host,value);
						SAFECOPY(session->req.vhost,value);
						/* Remove port part of host (Win32 doesn't allow : in dir names) */
						/* Either an existing : will be replaced with a null, or nothing */
						/* Will happen... the return value is not relevent here */
						strtok_r(session->req.vhost,":",&last);
		SAFECOPY(session->req.vhost, startup->host_name);
	if(!(session->req.host[0]))
		SAFECOPY(session->req.host, startup->host_name);
	return TRUE;
}

static BOOL get_fullpath(http_session_t * session)
{
	char	str[MAX_PATH+1];

	if(session->req.vhost[0] && startup->options&WEB_OPT_VIRTUAL_HOSTS) {
		safe_snprintf(str,sizeof(str),"%s/%s",root_dir,session->req.vhost);
			safe_snprintf(str,sizeof(str),"%s/%s%s",root_dir,session->req.vhost,session->req.physical_path);
		else
			safe_snprintf(str,sizeof(str),"%s%s",root_dir,session->req.physical_path);
	} else
		safe_snprintf(str,sizeof(str),"%s%s",root_dir,session->req.physical_path);

	if(FULLPATH(session->req.physical_path,str,sizeof(session->req.physical_path))==NULL) {
		send_error(session,error_500);
		return(FALSE);
	}

	return(TRUE);
}

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) {
		/* Eat leaing blank lines... as apache does...
		 * "This is a legacy issue. The CERN webserver required POST data to have an extra
		 * CRLF following it. Thus many clients send an extra CRLF that is not included in the
		 * Content-Length of the request. Apache works around this problem by eating any empty
		 * lines which appear before a request."
		 * http://httpd.apache.org/docs/misc/known_client_problems.html
		 */
		while((len=sockreadline(session,req_line,sizeof(req_line)-1))==0);
		if(len<0)
		if(req_line[0])
			lprintf(LOG_DEBUG,"%04d Request: %s",session->socket,req_line);
		if(session->req.ld!=NULL && session->req.ld->request==NULL)
			/* FREE()d in http_logging_thread() */
			session->req.ld->request=strdup(req_line);
		lprintf(LOG_DEBUG,"%04d Handling Internal Redirect to: %s",session->socket,request_line);
		SAFECOPY(req_line,request_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(!is_redir)
				get_request_headers(session);
			if(!get_fullpath(session)) {
				send_error(session,error_500);
			if(session->req.ld!=NULL && session->req.ld->vhost==NULL)
				/* FREE()d in http_logging_thread() */
				session->req.ld->vhost=strdup(session->req.vhost);
			session->req.dynamic=is_dynamic_req(session);
				add_env(session,"QUERY_STRING",session->req.query_str);
			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;
	send_error(session,"400 Bad Request");
/* 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	rpath[MAX_PATH+1];
	char	vpath[MAX_PATH+1];
	char	epath[MAX_PATH+1];
	char	str[MAX_PATH+1];
	struct	stat sb;
	if(IS_PATH_DELIM(*lastchar(session->req.physical_path)) || stat(session->req.physical_path,&sb)==-1 /* && errno==ENOTDIR */)
	{
		SAFECOPY(vpath,session->req.virtual_path);
		SAFECOPY(rpath,session->req.physical_path);
		while((vp_slash=find_last_slash(vpath))!=NULL)
			*vp_slash=0;
			if((rp_slash=find_last_slash(rpath))==NULL)
				return(FALSE);
			SAFECOPY(str,epath);
			if(*rp_slash)
				sprintf(epath,"%s%s",rp_slash,str);
			*(rp_slash+1)=0;

			/* Check if this contains an index */
			end=strchr(rpath,0);
			if(use_epath || session->req.path_info_index || strchr(epath+1,'/')!=NULL) {
				use_epath=1;
				if(isdir(rpath) && !isdir(session->req.physical_path)) {
					for(i=0; startup->index_file_name!=NULL && startup->index_file_name[i]!=NULL ;i++)  {
						*end=0;
						strcat(rpath,startup->index_file_name[i]);
						if(!stat(rpath,&sb)) {
							/* *end=0; /* Removed Wed, Aug 09, 2006 to allow is_dynamic_req to detect correctly */
							SAFECOPY(session->req.extra_path_info,epath);
							SAFECOPY(session->req.virtual_path,vpath);
							strcat(session->req.virtual_path,"/");
							SAFECOPY(session->req.physical_path,rpath);
							return(TRUE);
						}
					/* rpath was an existing path and DID NOT contain an index. */
					/* We do not allow scripts to mask existing dirs/files */
					return(FALSE);
			if(vp_slash==vpath)
				return(FALSE);

			/* Check if this is a script */
			*rp_slash=0;
			if(vp_slash!=vpath) {
				if(stat(rpath,&sb)!=-1 && (!(sb.st_mode&S_IFDIR)))
				{
					SAFECOPY(session->req.extra_path_info,epath);
					SAFECOPY(session->req.virtual_path,vpath);
					SAFECOPY(session->req.physical_path,rpath);
					return(TRUE);
				}
static BOOL check_request(http_session_t * session)
{
	char	path[MAX_PATH+1];
deuce's avatar
deuce committed
	char*	p;
	struct stat sb;
	char	filename[MAX_PATH+1];
	char	*spec;
	str_list_t	specs;
	BOOL	recheck_dynamic=FALSE;
	SAFECOPY(path,session->req.physical_path);
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,"/");
			strcat(session->req.physical_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_500);
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;
			SAFECOPY(path,session->req.physical_path);

		/* Don't send 404 unless authourized... prevent info leak */
		if(startup->index_file_name==NULL || startup->index_file_name[i] == NULL)
			send404=1;
		else {
			strcat(session->req.virtual_path,startup->index_file_name[i]);
			if(session->req.send_location != MOVED_PERM)
				session->req.send_location=MOVED_STAT;
		filename[0]=0;
	}
	else {
		last_slash=find_last_slash(path);
		if(last_slash==NULL)
			last_slash=path;
		else
			last_slash++;
		strcpy(filename,last_slash);
	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);

	/* Set default ARS to a 0-length string */
	session->req.ars[0]=0;
	/* Walk up from root_dir checking for access.ars and webconfig.ini */
	SAFECOPY(curdir,path);
	last_slash=curdir+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 */
		sprintf(str,"%saccess.ars",curdir);
		if(!stat(str,&sb)) {
			/* NEVER serve up an access.ars file */
			lprintf(LOG_WARNING,"%04d !WARNING! access.ars support is depreciated and will be REMOVED very soon.",session->socket);
			lprintf(LOG_WARNING,"%04d !WARNING! access.ars found at %s.",session->socket,str);
			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);
		}
		sprintf(str,"%swebctrl.ini",curdir);
			/* NEVER serve up a webctrl.ini file */
			if(!strcmp(path,str)) {
				send_error(session,"403 Forbidden");
				return(FALSE);
			}
			if((file=fopen(str,"r"))!=NULL) {
deuce's avatar
deuce committed
				/* FREE()d in this block */
				specs=iniReadSectionList(file,NULL);
				/* Read in globals */
				if(iniReadString(file, NULL, "AccessRequirements", session->req.ars,str)==str)
					SAFECOPY(session->req.ars,str);
deuce's avatar
deuce committed
				if(iniReadString(file, NULL, "Realm", scfg.sys_name,str)==str) {
					FREE_AND_NULL(session->req.realm);
					/* FREE()d in close_request() */
					session->req.realm=strdup(str);
				if(iniReadString(file, NULL, "ErrorDirectory", error_dir,str)==str) {
					prep_dir(root_dir, str, sizeof(str));
deuce's avatar
deuce committed
					FREE_AND_NULL(session->req.error_dir);
					/* FREE()d in close_request() */
					session->req.error_dir=strdup(str);
				}
				if(iniReadString(file, NULL, "CGIDirectory", cgi_dir,str)==str) {
					prep_dir(root_dir, str, sizeof(str));
deuce's avatar
deuce committed
					FREE_AND_NULL(session->req.cgi_dir);
					/* FREE()d in close_request() */
					session->req.cgi_dir=strdup(str);
				session->req.path_info_index=iniReadBool(file, NULL, "PathInfoIndex", FALSE);
				/* Read in per-filespec */
				while((spec=strListPop(&specs))!=NULL) {
					if(wildmatch(filename,spec,TRUE)) {
						if(iniReadString(file, spec, "AccessRequirements", session->req.ars,str)==str)
							SAFECOPY(session->req.ars,str);
						if(iniReadString(file, spec, "Realm", scfg.sys_name,str)==str) {
							FREE_AND_NULL(session->req.realm);
							/* FREE()d in close_request() */
							session->req.realm=strdup(str);
						if(iniReadString(file, spec, "ErrorDirectory", error_dir,str)==str) {
							FREE_AND_NULL(session->req.error_dir);
							prep_dir(root_dir, str, sizeof(str));
							/* FREE()d in close_request() */
							session->req.error_dir=strdup(str);
						}
						if(iniReadString(file, spec, "CGIDirectory", cgi_dir,str)==str) {
							FREE_AND_NULL(session->req.cgi_dir);
							prep_dir(root_dir, str, sizeof(str));
							/* FREE()d in close_request() */
							session->req.cgi_dir=strdup(str);
						session->req.path_info_index=iniReadBool(file, spec, "PathInfoIndex", FALSE);
				iniFreeStringList(specs);
				if(session->req.path_info_index)
					recheck_dynamic=TRUE;
				/* If cannot open webctrl.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(curdir,path);
		session->req.dynamic=is_dynamic_req(session);
		if(session->req.dynamic)	/* Need to re-copy path here in case of re-checked PathInfoIndex change */
			SAFECOPY(path,session->req.physical_path);
	}
	if(!session->req.dynamic && session->req.extra_path_info[0])
	if(!check_ars(session)) {
		/* No authentication provided */
		sprintf(str,"401 Unauthorized%s%s: Basic realm=\"%s\""
			,newline,get_header(HEAD_WWWAUTH),session->req.realm?session->req.realm:scfg.sys_name);
		send_error(session,str);
		return(FALSE);
	if(stat(path,&sb) || IS_PATH_DELIM(*(lastchar(path))) || send404) {
		/* OPTIONS requests never return 404 errors (ala Apache) */
		if(session->req.method!=HTTP_OPTIONS) {
			if(startup->options&WEB_OPT_DEBUG_TX)
				lprintf(LOG_DEBUG,"%04d 404 - %s does not exist",session->socket,path);
			strcat(session->req.physical_path,session->req.extra_path_info);
			strcat(session->req.virtual_path,session->req.extra_path_info);
			send_error(session,error_404);
			return(FALSE);
		}
	if(session->req.range_start || session->req.range_end) {
		if(session->req.range_start < 0)
			session->req.range_start=sb.st_size-session->req.range_start;
		if(session->req.range_end < 0)
			session->req.range_end=sb.st_size-session->req.range_end;
		if(session->req.range_end >= sb.st_size)
deuce's avatar
deuce committed
			session->req.range_end=sb.st_size-1;
		if(session->req.range_end < session->req.range_start || session->req.dynamic) {
			send_error(session,error_416);
			return(FALSE);
		}
		if(session->req.range_start < 0 || session->req.range_end < 0) {
			send_error(session,error_416);
			return(FALSE);
		}
		if(session->req.range_start >= sb.st_size) {
			send_error(session,error_416);
			return(FALSE);
		}
		SAFECOPY(session->req.status,"206 Partial Content");
	}
	SAFECOPY(session->req.physical_path,path);
	add_env(session,"SCRIPT_NAME",session->req.virtual_path);
	add_env(session,"SCRIPT_FILENAME",session->req.physical_path);
	SAFECOPY(str,session->req.virtual_path);
	last_slash=find_last_slash(str);
	if(last_slash!=NULL)
		*(last_slash+1)=0;
	if(*(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);
	}

static str_list_t get_cgi_env(http_session_t *session)
{
	char		value[INI_MAX_VALUE_LEN+1];
	char*		deflt;
	char		defltbuf[INI_MAX_VALUE_LEN+1];
	char		append[INI_MAX_VALUE_LEN+1];
	char		prepend[INI_MAX_VALUE_LEN+1];
	char		env_str[(INI_MAX_VALUE_LEN*4)+2];
	FILE*		fp;
	size_t		i;
	str_list_t	env_list;
	str_list_t	add_list;

deuce's avatar
deuce committed
	/* Return value */
	if((env_list=strListInit())==NULL)
		return(NULL);

	strListAppendList(&env_list, session->req.cgi_env);

	strListPush(&env_list,"REDIRECT_STATUS=200");	/* Kludge for php-cgi */

	if((fp=iniOpenFile(cgi_env_ini,/* create? */FALSE))==NULL)
deuce's avatar
deuce committed
	/* FREE()d in this block */
	if((add_list=iniReadSectionList(fp,NULL))!=NULL) {

		for(i=0; add_list[i]!=NULL; i++) {
			if((deflt=getenv(add_list[i]))==NULL)
				deflt=iniReadString(fp,add_list[i],"default",NULL,defltbuf);
			if(iniReadString(fp,add_list[i],"value",deflt,value)==NULL)
				continue;
			iniReadString(fp,add_list[i],"append","",append);
			iniReadString(fp,add_list[i],"prepend","",prepend);
			safe_snprintf(env_str,sizeof(env_str),"%s=%s%s%s"
				,add_list[i], prepend, value, append);
			strListPush(&env_list,env_str);
		}
deuce's avatar
deuce committed
		iniFreeStringList(add_list);
static BOOL exec_cgi(http_session_t *session)
	/* ToDo: Damn, that's WAY too many variables */
	int		i=0;
	int		status=0;
	pid_t	child=0;
	struct timeval tv={0,0};
	int		high_fd=0;
	char	buf[1024];
deuce's avatar
deuce committed
	char	fbuf[1026];
	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;
	BOOL	orig_keep=FALSE;
deuce's avatar
deuce committed
	str_list_t	tmpbuf;
	size_t	tmpbuflen=0;
	BOOL	no_chunked=FALSE;
	SAFECOPY(cmdline,session->req.physical_path);
	lprintf(LOG_INFO,"%04d Executing CGI: %s",session->socket,cmdline);
	orig_keep=session->req.keep_alive;
	session->req.keep_alive=FALSE;

deuce's avatar
deuce committed
		lprintf(LOG_ERR,"%04d Can't create out_pipe",session->socket);
deuce's avatar
deuce committed
		lprintf(LOG_ERR,"%04d Can't create err_pipe",session->socket);
		return(FALSE);
	}

	if((child=fork())==0)  {
deuce's avatar
deuce committed
		str_list_t  env_list;

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

deuce's avatar
deuce committed
		env_list=get_cgi_env(session);
		dup2(session->socket,0);		/* redirect stdin */
		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);
		}

		if(get_cgi_handler(cgipath, sizeof(cgipath))) {
			lprintf(LOG_INFO,"%04d Using handler %s to execute %s",session->socket,cgipath,cmdline);
			execle(shell,shell,"-c",cgipath,NULL,env_list);
		}
		else {
			execle(cmdline,cmdline,NULL,env_list);
		}
deuce's avatar
deuce committed

		lprintf(LOG_ERR,"%04d !FAILED! execle() (%d)",session->socket,errno);
		exit(EXIT_FAILURE); /* Should never happen */
	}
deuce's avatar
deuce committed

	if(child==-1)  {
		lprintf(LOG_ERR,"%04d !FAILED! fork() errno=%d",session->socket,errno);
		close(out_pipe[0]);		/* close read-end of pipe */
		close(err_pipe[0]);		/* close read-end of pipe */
	}

	close(out_pipe[1]);		/* close excess file descriptor */
	close(err_pipe[1]);		/* close excess file descriptor */
deuce's avatar
deuce committed
	if(child==-1)
		return(FALSE);

	start=time(NULL);
	high_fd=out_pipe[0];
	if(err_pipe[0]>high_fd)
		high_fd=err_pipe[0];
	/* ToDo: Magically set done_parsing_headers for nph-* scripts */
	cgi_status[0]=0;
deuce's avatar
deuce committed
	/* FREE()d following this block */
deuce's avatar
deuce committed
	tmpbuf=strListInit();
	while(!done_reading)  {
		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_ZERO(&write_set);

		if(select(high_fd+1,&read_set,&write_set,NULL,&tv)>0)  {
			if(FD_ISSET(out_pipe[0],&read_set))  {
				if(done_parsing_headers && got_valid_headers)  {
					i=read(out_pipe[0],buf,sizeof(buf));
					if(i!=-1 && i!=0)  {
						if(session->req.method!=HTTP_HEAD) {
							if(session->req.ld!=NULL) {
								session->req.ld->size+=snt;
deuce's avatar
deuce committed
					i=pipereadline(out_pipe[0],buf,sizeof(buf), fbuf, sizeof(fbuf));
						done_reading=TRUE;
						got_valid_headers=FALSE;
					}
					if(!done_parsing_headers && *buf)  {
deuce's avatar
deuce committed
						if(tmpbuf != NULL)
							strListPush(&tmpbuf, fbuf);
						directive=strtok_r(header,":",&last);
							value=strtok_r(NULL,"",&last);
							i=get_header_type(directive);
							switch (i)  {
								case HEAD_LOCATION:
									got_valid_headers=TRUE;
									if(*value=='/')  {
										unescape(value);
										SAFECOPY(session->req.virtual_path,value);
										session->req.send_location=MOVED_STAT;
										if(cgi_status[0]==0)
									} else  {
										SAFECOPY(session->req.virtual_path,value);
										session->req.send_location=MOVED_TEMP;
									}
									break;
								case HEAD_STATUS:
									SAFECOPY(cgi_status,value);
									break;
									session->req.keep_alive=orig_keep;
									strListPush(&session->req.dynamic_heads,buf);
									no_chunked=TRUE;
								case HEAD_TYPE:
									got_valid_headers=TRUE;
									strListPush(&session->req.dynamic_heads,buf);
								case HEAD_TRANSFER_ENCODING:
									no_chunked=TRUE;
									break;
									strListPush(&session->req.dynamic_heads,buf);
						if(directive == NULL || value == NULL) {
							/* Invalid header line */
							done_parsing_headers=TRUE;
						}
					}
					else  {
						if(!no_chunked && session->http_ver>=HTTP_1_1) {
							session->req.keep_alive=orig_keep;
						if(got_valid_headers)  {
							session->req.dynamic=IS_CGI;
							if(cgi_status[0]==0)
								SAFECOPY(cgi_status,session->req.status);
							send_headers(session,cgi_status,set_chunked);
						}
						else {
							/* Invalid headers... send 'er all as plain-text */
deuce's avatar
deuce committed
							char    content_type[MAX_REQUEST_LINE+1];
deuce's avatar
deuce committed

							lprintf(LOG_DEBUG,"%04d Recieved invalid CGI headers, sending result as plain-text",session->socket);

deuce's avatar
deuce committed
							/* free() the non-headers so they don't get sent, then recreate the list */
							strListFreeStrings(session->req.dynamic_heads);

							/* Copy current status */
							SAFECOPY(cgi_status,session->req.status);

							/* Add the content-type header (REQUIRED) */
							SAFEPRINTF2(content_type,"%s: %s",get_header(HEAD_TYPE),startup->default_cgi_content);
							strListPush(&session->req.dynamic_heads,content_type);
							send_headers(session,cgi_status,FALSE);
deuce's avatar
deuce committed

							/* Now send the tmpbuf */
							for(i=0; tmpbuf != NULL && tmpbuf[i] != NULL; i++) {
								if(strlen(tmpbuf[i])>0) {
									snt=writebuf(session,tmpbuf[i],strlen(tmpbuf[i]));
									if(session->req.ld!=NULL) {
										session->req.ld->size+=snt;
									}
								}
							}
							if(strlen(fbuf)>0) {
								snt=writebuf(session,fbuf,strlen(fbuf));
deuce's avatar
deuce committed
								if(session->req.ld!=NULL && snt>0) {
									session->req.ld->size+=snt;
								}
deuce's avatar
deuce committed
							got_valid_headers=TRUE;
						}
			}
			if(FD_ISSET(err_pipe[0],&read_set))  {
				i=read(err_pipe[0],buf,sizeof(buf)-1);
				if(i>0) {
					buf[i]=0;
					lprintf(LOG_ERR,"%04d CGI Error: %s",session->socket,buf);
			if(!done_wait)
				done_wait = (waitpid(child,&status,WNOHANG)==child);
			if(!FD_ISSET(err_pipe[0],&read_set) && !FD_ISSET(out_pipe[0],&read_set) && done_wait)
				done_reading=TRUE;
		}
		else  {
			if((time(NULL)-start) >= startup->max_cgi_inactivity)  {
				lprintf(LOG_ERR,"%04d CGI Process %s Timed out",session->socket,getfname(cmdline));
deuce's avatar
deuce committed
	if(tmpbuf != NULL)
		strListFree(&tmpbuf);

	if(!done_wait)
		done_wait = (waitpid(child,&status,WNOHANG)==child);
	if(!done_wait)  {
		if(start)
			lprintf(LOG_NOTICE,"%04d CGI Process %s still alive on client exit"
				,session->socket,getfname(cmdline));
		kill(child,SIGTERM);
		mswait(1000);
		done_wait = (waitpid(child,&status,WNOHANG)==child);
		if(!done_wait)  {
			kill(child,SIGKILL);
			done_wait = (waitpid(child,&status,0)==child);
		}
	}

	/* Drain STDERR & STDOUT */