Skip to content
Snippets Groups Projects
websrvr.c 106 KiB
Newer Older
		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;

		/* Don't send 404 unless authourized... prevent info leak */
		if(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;
	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 */
	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(!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);
	if(stat(path,&sb) || IS_PATH_DELIM(*(lastchar(path))) || send404) {
		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);
		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.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);
	}

static str_list_t get_cgi_env(http_session_t *session)
{
	char		path[MAX_PATH+1];
	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;

	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(iniFileName(path,sizeof(path),scfg.ctrl_dir,"cgi_env.ini"),/* create? */FALSE))==NULL)
		return(env_list);

	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);
		}
		strListFree(&add_list);
	}

	fclose(fp);

	return(env_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];
	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;
	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;

		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);

		for(idx=0;session->req.cgi_env[idx]!=NULL;idx++)
			putenv(session->req.cgi_env[idx]);
		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);
		}

		/* Execute command */
		execl(cmdline,cmdline,NULL);
		lprintf(LOG_ERR,"%04d !FAILED! execl()",session->socket);
		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;
	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(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);
						directive=strtok(header,":");
						if(directive != NULL)  {
							value=strtok(NULL,"");
							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_STAT;
										if(cgi_status[0]==0)
									}
									break;
								case HEAD_STATUS:
									SAFECOPY(cgi_status,value);
									break;
									session->req.keep_alive=orig_keep;
									strListPush(&session->req.dynamic_heads,buf);
								case HEAD_TYPE:
									got_valid_headers=TRUE;
									strListPush(&session->req.dynamic_heads,buf);
									strListPush(&session->req.dynamic_heads,buf);
							session->req.dynamic=IS_CGI;
								SAFECOPY(cgi_status,session->req.status);
							send_headers(session,cgi_status);
			}
			if(FD_ISSET(err_pipe[0],&read_set))  {
				i=read(err_pipe[0],buf,sizeof(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));
	/* Drain STDERR */	
	tv.tv_sec=1;
	tv.tv_usec=0;
	FD_ZERO(&read_set);
	FD_SET(err_pipe[0],&read_set);
	if(select(high_fd+1,&read_set,&write_set,NULL,&tv)>0)
	if(FD_ISSET(err_pipe[0],&read_set)) {
		while(pipereadline(err_pipe[0],buf,sizeof(buf))!=-1)
			lprintf(LOG_ERR,"%s",buf);
	if(!done_wait)
		done_wait = (waitpid(child,&status,WNOHANG)==child);
			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);
	close(out_pipe[0]);		/* close read-end of pipe */
	close(err_pipe[0]);		/* close read-end of pipe */
		lprintf(LOG_ERR,"%04d CGI Process %s did not generate valid headers"
			,session->socket,getfname(cmdline));
		return(FALSE);
	}

	if(!done_parsing_headers) {
		lprintf(LOG_ERR,"%04d CGI Process %s did not send data header termination"
			,session->socket,getfname(cmdline));

	/* These are (more or less) copied from the Unix version */
	char*	p;
	char	cmdline[MAX_PATH+256];
	char	buf[4096];
	int		i;
	BOOL	orig_keep;
	BOOL	done_parsing_headers=FALSE;
	BOOL	got_valid_headers=FALSE;
	char	cgi_status[MAX_REQUEST_LINE+1];
	char	content_type[MAX_REQUEST_LINE+1];
	char	header[MAX_REQUEST_LINE+1];
	char	*directive=NULL;
	char	*value=NULL;
	time_t	start;

	/* Win32-specific */
	char*	env_block;
	char	startup_dir[MAX_PATH+1];
	int		wr;
	HANDLE	rdpipe=INVALID_HANDLE_VALUE;
	HANDLE	wrpipe=INVALID_HANDLE_VALUE;
	HANDLE	rdoutpipe;
	HANDLE	wrinpipe;
	DWORD	waiting;
	DWORD	msglen;
	DWORD	retval;
    PROCESS_INFORMATION process_info;
	SECURITY_ATTRIBUTES sa;
    STARTUPINFO startup_info={0};

    startup_info.cb=sizeof(startup_info);
	startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
    startup_info.wShowWindow=SW_HIDE;

	SAFECOPY(cmdline,session->req.physical_path);

	SAFECOPY(startup_dir,cmdline);
	if((p=strrchr(startup_dir,'/'))!=NULL || (p=strrchr(startup_dir,'\\'))!=NULL)
		*p=0;
	else
		SAFECOPY(startup_dir,cgi_dir);

	lprintf(LOG_DEBUG,"%04d CGI startup dir: %s", session->socket, startup_dir);

	get_cgi_handler(cmdline, sizeof(cmdline));

	lprintf(LOG_INFO,"%04d Executing CGI: %s",session->socket,cmdline);

	orig_keep=session->req.keep_alive;
	session->req.keep_alive=FALSE;

	memset(&sa,0,sizeof(sa));
	sa.nLength= sizeof(SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = TRUE;

	// Create the child output pipe (override default 4K buffer size)
	if(!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,sizeof(buf))) {
		lprintf(LOG_ERR,"%04d !ERROR %d creating stdout pipe",session->socket,GetLastError());
		return(FALSE);
	}
	startup_info.hStdError=startup_info.hStdOutput;

	// Create the child input pipe.
	if(!CreatePipe(&startup_info.hStdInput,&wrinpipe,&sa,0 /* default buffer size */)) {
		lprintf(LOG_ERR,"%04d !ERROR %d creating stdin pipe",session->socket,GetLastError());
		return(FALSE);
	}

	DuplicateHandle(
		GetCurrentProcess(), rdoutpipe,
		GetCurrentProcess(), &rdpipe, 0, FALSE, DUPLICATE_SAME_ACCESS);

	DuplicateHandle(
		GetCurrentProcess(), wrinpipe,
		GetCurrentProcess(), &wrpipe, 0, FALSE, DUPLICATE_SAME_ACCESS);

	CloseHandle(rdoutpipe);
	CloseHandle(wrinpipe);

	env_list=get_cgi_env(session);
	env_block = strListCreateBlock(env_list);
	strListFree(&env_list);

    success=CreateProcess(
		NULL,			// pointer to name of executable module
		cmdline,  		// pointer to command line string
		NULL,  			// process security attributes
		NULL,   		// thread security attributes
		TRUE,	 		// handle inheritance flag
		CREATE_NEW_CONSOLE, // creation flags
        env_block,  	// pointer to new environment block
		startup_dir,	// pointer to current directory name
		&startup_info,  // pointer to STARTUPINFO
		&process_info  	// pointer to PROCESS_INFORMATION
		);

	strListFreeBlock(env_block);
	
	if(!success) {
		lprintf(LOG_ERR,"%04d !ERROR %d running %s",session->socket,GetLastError(),cmdline);
		return(FALSE);
    }

	start=time(NULL);

	SAFECOPY(cgi_status,session->req.status);
	SAFEPRINTF2(content_type,"%s: %s",get_header(HEAD_TYPE),startup->default_cgi_content);
	while(server_socket!=INVALID_SOCKET) {

		if((time(NULL)-start) >= startup->max_cgi_inactivity)  {
			lprintf(LOG_WARNING,"%04d CGI Process %s Timed out"
				,session->socket,getfname(cmdline));
			break;
		}

		waiting = 0;
		PeekNamedPipe(
			rdpipe,             // handle to pipe to copy from
			NULL,               // pointer to data buffer
			0,					// size, in bytes, of data buffer
			NULL,				// pointer to number of bytes read
			&waiting,			// pointer to total number of bytes available
			NULL				// pointer to unread bytes in this message
			);
		if(waiting) {

			/* reset inactivity timer */
			start=time(NULL);	

			msglen=0;
			if(done_parsing_headers) {
				if(ReadFile(rdpipe,buf,sizeof(buf),&msglen,NULL)==FALSE) {
					lprintf(LOG_ERR,"%04d !ERROR %d reading from pipe"
						,session->socket,GetLastError());
					break;
				}
				i=pipereadline(rdpipe,buf,sizeof(buf));
				if(i<0)  {
					got_valid_headers=FALSE;
					break;
				}
				lprintf(LOG_DEBUG,"%04d CGI header line: %s"
					,session->socket, buf);
				if(strchr(header,':')!=NULL) {
					directive=strtok(header,":");
					value=strtok(NULL,"");
					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)
									SAFECOPY(cgi_status,error_302);
							} else  {
								SAFECOPY(session->req.virtual_path,value);
								session->req.send_location=MOVED_STAT;
								if(cgi_status[0]==0)
									SAFECOPY(cgi_status,error_302);
							}
							break;
						case HEAD_STATUS:
							SAFECOPY(cgi_status,value);
							break;
						case HEAD_LENGTH:
							session->req.keep_alive=orig_keep;
							strListPush(&session->req.dynamic_heads,buf);
							break;
						case HEAD_TYPE:
							got_valid_headers=TRUE;
							SAFECOPY(content_type,buf);
							break;
						default:
							strListPush(&session->req.dynamic_heads,buf);
				if(i) {
					strcat(buf,"\r\n");	/* Add back the missing line terminator */
					msglen=strlen(buf);	/* we will send this text later */
				}
				done_parsing_headers = TRUE;	/* invalid header */
				session->req.dynamic=IS_CGI;
				strListPush(&session->req.dynamic_heads,content_type);
				send_headers(session,cgi_status);
			}
			if(msglen) {
				lprintf(LOG_DEBUG,"%04d Sending %d bytes: %.*s"
					,session->socket,msglen,msglen,buf);
				wr=sendsocket(session->socket,buf,msglen);
				/* log actual bytes sent */
				if(session->req.ld!=NULL && wr>0)
					session->req.ld->size+=wr;	
		}
		Sleep(1);

		if(WaitForSingleObject(process_info.hProcess,0)==WAIT_OBJECT_0)
			break;
	}

    if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE)
	    lprintf(LOG_ERR,"%04d !ERROR GetExitCodeProcess(%s) returned %d"
			,session->socket,getfname(cmdline),GetLastError());
		lprintf(LOG_WARNING,"%04d Terminating CGI process: %s"
			,session->socket,getfname(cmdline));
		TerminateProcess(process_info.hProcess, GetLastError());
	}	

	if(rdpipe!=INVALID_HANDLE_VALUE)
		CloseHandle(rdpipe);
	if(wrpipe!=INVALID_HANDLE_VALUE)
		CloseHandle(wrpipe);
	CloseHandle(process_info.hProcess);

		lprintf(LOG_WARNING,"%04d !CGI Process %s did not generate valid headers"
			,session->socket,getfname(cmdline));
		lprintf(LOG_WARNING,"%04d !CGI Process %s did not send data header termination"
			,session->socket,getfname(cmdline));
/********************/
/* JavaScript stuff */
/********************/

JSObject* DLLCALL js_CreateHttpReplyObject(JSContext* cx
										   ,JSObject* parent, http_session_t *session)
{
	JSObject*	reply;
	JSObject*	headers;
	jsval		val;
	JSString*	js_str;
	
	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,parent,"http_reply",&val) && val!=JSVAL_VOID)  {
		reply = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,reply);
	}
	else
		reply = JS_DefineObject(cx, parent, "http_reply", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	if((js_str=JS_NewStringCopyZ(cx, session->req.status))==NULL)
		return(FALSE);
	JS_DefineProperty(cx, reply, "status", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE);
	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,reply,"header",&val) && val!=JSVAL_VOID)  {
		headers = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,headers);
	}
	else
		headers = JS_DefineObject(cx, reply, "header", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);

	if((js_str=JS_NewStringCopyZ(cx, "text/html"))==NULL)
		return(FALSE);
	JS_DefineProperty(cx, headers, "Content-Type", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE);

	return(reply);
}

JSObject* DLLCALL js_CreateHttpRequestObject(JSContext* cx
											 ,JSObject* parent, http_session_t *session)
deuce's avatar
deuce committed
{
/*	JSObject*	cookie; */
deuce's avatar
deuce committed
	jsval		val;

	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,parent,"http_request",&val) && val!=JSVAL_VOID)  {
		session->js_request=JSVAL_TO_OBJECT(val);
deuce's avatar
deuce committed
	else
		session->js_request = JS_DefineObject(cx, parent, "http_request", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	js_add_request_prop(session,"path_info",session->req.extra_path_info);
	js_add_request_prop(session,"method",methods[session->req.method]);
	js_add_request_prop(session,"virtual_path",session->req.virtual_path);
deuce's avatar
deuce committed

	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,session->js_request,"query",&val) && val!=JSVAL_VOID)  {
		session->js_query = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,session->js_query);
deuce's avatar
deuce committed
	else
		session->js_query = JS_DefineObject(cx, session->js_request, "query", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	/* Return existing object if it's already been created */
	if(JS_GetProperty(cx,session->js_request,"header",&val) && val!=JSVAL_VOID)  {
		session->js_header = JSVAL_TO_OBJECT(val);
		JS_ClearScope(cx,session->js_header);
		session->js_header = JS_DefineObject(cx, session->js_request, "header", NULL
									, NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
	return(session->js_request);
static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	char	line[64];
	char	file[MAX_PATH+1];
	char*	warning;
	http_session_t* session;

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return;
	
	if(report==NULL) {
		lprintf(LOG_ERR,"%04d !JavaScript: %s", session->socket, message);
		if(session->req.fp!=NULL)
			fprintf(session->req.fp,"!JavaScript: %s", message);
		return;
    }

	if(report->filename)
		sprintf(file," %s",report->filename);
	else
		file[0]=0;

	if(report->lineno)
		sprintf(line," line %u",report->lineno);
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
			warning="strict warning";
		else
			warning="warning";
	} else
		warning="";

	lprintf(LOG_ERR,"%04d !JavaScript %s%s%s: %s",session->socket,warning,file,line,message);
	if(session->req.fp!=NULL)
		fprintf(session->req.fp,"!JavaScript %s%s%s: %s",warning,file,line,message);
}

static JSBool
js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
    uintN		i;
	http_session_t* session;

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

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

	if((!session->req.prev_write) && (!session->req.sent_headers)) {
		/* "Fast Mode" requested? */
		jsval		val;
		JSObject*	reply;

		JS_GetProperty(cx, session->js_glob, "http_reply", &val);
		reply=JSVAL_TO_OBJECT(val);
		JS_GetProperty(cx, reply, "fast", &val);
		if(JSVAL_IS_BOOLEAN(val) && JSVAL_TO_BOOLEAN(val)) {
			session->req.keep_alive=FALSE;
			if(!ssjs_send_headers(session))
				return(JS_FALSE);
		}
	}

	session->req.prev_write=TRUE;

		if((str=JS_ValueToString(cx, argv[i]))==NULL)
			continue;
		if(session->req.sent_headers) {
			if(session->req.method!=HTTP_HEAD)
				sendsocket(session->socket, JS_GetStringBytes(str), JS_GetStringLength(str));
		}
		else
			fwrite(JS_GetStringBytes(str),1,JS_GetStringLength(str),session->req.fp);
	if(str==NULL)
		*rval = JSVAL_VOID;
	else
		*rval = STRING_TO_JSVAL(str);

	return(JS_TRUE);
}

static JSBool
js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	http_session_t* session;

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	js_write(cx, obj, argc, argv, rval);
	/* Should this do the whole \r\n thing for Win32 *shudder* */
	if(session->req.sent_headers) {
		if(session->req.method!=HTTP_HEAD)
			sendsocket(session->socket, "\n", 1);
	}
static JSBool
js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char		str[512];
    uintN		i=0;
	int32		level=LOG_INFO;
    JSString*	js_str;
	http_session_t* session;

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    if(startup==NULL || startup->lputs==NULL)
        return(JS_FALSE);

	if(JSVAL_IS_NUMBER(argv[i]))
		JS_ValueToInt32(cx,argv[i++],&level);

	str[0]=0;
    for(;i<argc && strlen(str)<(sizeof(str)/2);i++) {
		if((js_str=JS_ValueToString(cx, argv[i]))==NULL)
		    return(JS_FALSE);
		strncat(str,JS_GetStringBytes(js_str),sizeof(str)/2);
		strcat(str," ");
	}

	lprintf(level,"%04d %s",session->socket,str);

	*rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));

    return(JS_TRUE);
}

static JSBool
js_login(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	JSBool		inc_logons=JS_FALSE;
	user_t		user;
	JSString*	js_str;
	http_session_t*	session;

	*rval = BOOLEAN_TO_JSVAL(JS_FALSE);

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	/* User name */
	if((js_str=JS_ValueToString(cx, argv[0]))==NULL) 
		return(JS_FALSE);

	if((p=JS_GetStringBytes(js_str))==NULL) 
		return(JS_FALSE);

	memset(&user,0,sizeof(user));

	if(isdigit(*p))
		user.number=atoi(p);
	else if(*p)
		user.number=matchuser(&scfg,p,FALSE);

	if(getuserdat(&scfg,&user)!=0) {
		lprintf(LOG_NOTICE,"%04d !USER NOT FOUND: '%s'"
			,session->socket,p);
		return(JS_TRUE);
	}

	if(user.misc&(DELETED|INACTIVE)) {
		lprintf(LOG_WARNING,"%04d !DELETED OR INACTIVE USER #%d: %s"
			,session->socket,user.number,p);
		return(JS_TRUE);
	}

	/* Password */
	if(user.pass[0]) {
		if((js_str=JS_ValueToString(cx, argv[1]))==NULL) 
			return(JS_FALSE);

		if((p=JS_GetStringBytes(js_str))==NULL) 
			return(JS_FALSE);

		if(stricmp(user.pass,p)) { /* Wrong password */
			lprintf(LOG_WARNING,"%04d !INVALID PASSWORD ATTEMPT FOR USER: %s"
				,session->socket,user.alias);
			return(JS_TRUE);
		}
	}

	if(argc>2)
		JS_ValueToBoolean(cx,argv[2],&inc_logons);

	if(inc_logons) {
		user.logons++;
		user.ltoday++;
	}

	http_logon(session, &user);

	/* user-specific objects */
	if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
		,NULL /* ftp index file */, session->subscan /* subscan */)) {
		lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
		send_error(session,"500 Error initializing JavaScript User Objects");
		return(FALSE);
	}

	*rval=BOOLEAN_TO_JSVAL(JS_TRUE);

	return(JS_TRUE);
}

static JSFunctionSpec js_global_functions[] = {
	{"write",           js_write,           1},		/* write to HTML file */
	{"writeln",         js_writeln,         1},		/* write line to HTML file */
	{"print",			js_writeln,			1},		/* write line to HTML file (alias) */
	{"log",				js_log,				0},		/* Log a string */
	{"login",           js_login,           2},		/* log in as a different user */
static JSBool
js_BranchCallback(JSContext *cx, JSScript *script)
{
	http_session_t* session;

	if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

    return(js_CommonBranchCallback(cx,&session->js_branch));
}

js_initcx(http_session_t *session)
	lprintf(LOG_INFO,"%04d JavaScript: Initializing context (stack: %lu bytes)"
		,session->socket,startup->js.cx_stack);
    if((js_cx = JS_NewContext(session->js_runtime, startup->js.cx_stack))==NULL)
	lprintf(LOG_INFO,"%04d JavaScript: Context created",session->socket);

    JS_SetErrorReporter(js_cx, js_ErrorReporter);

	JS_SetBranchCallback(js_cx, js_BranchCallback);

	lprintf(LOG_INFO,"%04d JavaScript: Creating Global Objects and Classes",session->socket);
	if((session->js_glob=js_CreateCommonObjects(js_cx, &scfg, NULL
									,NULL						/* global */
									,uptime						/* system */
									,startup->host_name			/* system */
									,SOCKLIB_DESC				/* system */
									,&session->js_branch		/* js */
									,&session->client			/* client */
									,session->socket			/* client */
		))==NULL
		|| !JS_DefineFunctions(js_cx, session->js_glob, js_global_functions)) {
static BOOL js_setup(http_session_t* session)
	if(session->js_runtime == NULL) {
		lprintf(LOG_INFO,"%04d JavaScript: Creating runtime: %lu bytes"
			,session->socket,startup->js.max_bytes);
		if((session->js_runtime=JS_NewRuntime(startup->js.max_bytes))==NULL) {