Skip to content
Snippets Groups Projects
websrvr.c 67 KiB
Newer Older
		,sock,JAVASCRIPT_CONTEXT_STACK);

    if((js_cx = JS_NewContext(runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
		return(NULL);

	lprintf("%04d JavaScript: Context created",sock);

    JS_SetErrorReporter(js_cx, js_ErrorReporter);

	do {

		lprintf("%04d JavaScript: Initializing Global object",sock);
		if((js_glob=js_CreateGlobalObject(js_cx, &scfg))==NULL) 
			break;

		if (!JS_DefineFunctions(js_cx, js_glob, js_global_functions)) 
			break;

		lprintf("%04d JavaScript: Initializing System object",sock);
		if(js_CreateSystemObject(js_cx, js_glob, &scfg, uptime, startup->host_name)==NULL) 
			break;

		if((server=JS_DefineObject(js_cx, js_glob, "server", NULL,NULL,0))==NULL)
			break;

		sprintf(ver,"%s %s",server_name,revision);
		if((js_str=JS_NewStringCopyZ(js_cx, ver))==NULL)
			break;
		val = STRING_TO_JSVAL(js_str);
		if(!JS_SetProperty(js_cx, server, "version", &val))
			break;

		if((js_str=JS_NewStringCopyZ(js_cx, web_ver()))==NULL)
			break;
		val = STRING_TO_JSVAL(js_str);
		if(!JS_SetProperty(js_cx, server, "version_detail", &val))
			break;

		if(glob!=NULL)
			*glob=js_glob;

		success=TRUE;

	} while(0);

	if(!success) {
		JS_DestroyContext(js_cx);
static BOOL js_setup(http_session_t* session)
	if(session->js_runtime == NULL) {
		lprintf("%04d JavaScript: Creating runtime: %lu bytes"
			,session->socket,startup->js_max_bytes);

		if((session->js_runtime=JS_NewRuntime(startup->js_max_bytes))==NULL) {
			lprintf("%04d !ERROR creating JavaScript runtime",session->socket);
			send_error(session,"500 Error creating JavaScript runtime");
			return(FALSE);
		}
	}

	if(session->js_cx==NULL) {	/* Context not yet created, create it now */
		if(((session->js_cx=js_initcx(session->js_runtime, session->socket
deuce's avatar
deuce committed
			,&session->js_glob, session))==NULL)) {
			lprintf("%04d !ERROR initializing JavaScript context",session->socket);
			send_error(session,"500 Error initializing JavaScript context");
			return(FALSE);
		}
		if(js_CreateUserClass(session->js_cx, session->js_glob, &scfg)==NULL) 
			lprintf("%04d !JavaScript ERROR creating user class",session->socket);

		if(js_CreateFileClass(session->js_cx, session->js_glob)==NULL) 
			lprintf("%04d !JavaScript ERROR creating File class",session->socket);
		if(js_CreateSocketClass(session->js_cx, session->js_glob)==NULL)
			lprintf("%04d !JavaScript ERROR creating Socket class",session->socket);

		if(js_CreateMsgBaseClass(session->js_cx, session->js_glob, &scfg)==NULL)
			lprintf("%04d !JavaScript ERROR creating MsgBase class",session->socket);

		if(js_CreateClientObject(session->js_cx, session->js_glob, "client", &client
			,session->socket)==NULL) 
			lprintf("%04d !JavaScript ERROR creating client object",session->socket);
#endif

		argv=JS_NewArrayObject(session->js_cx, 0, NULL);

		JS_DefineProperty(session->js_cx, session->js_glob, "argv", OBJECT_TO_JSVAL(argv)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
		JS_DefineProperty(session->js_cx, session->js_glob, "argc", INT_TO_JSVAL(0)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
	lprintf("     JavaScript: Initializing HttpRequest object");
	if(js_CreateHttpRequestObject(session->js_cx, session->js_glob, session)==NULL) {
		lprintf("%04d !ERROR initializing JavaScript HttpRequest object",session->socket);
		send_error(session,"500 Error initializing JavaScript HttpRequest object");
		return(FALSE);
	}

	lprintf("     JavaScript: Initializing HttpReply object");
	if(js_CreateHttpReplyObject(session->js_cx, session->js_glob, session)==NULL) {
		lprintf("%04d !ERROR initializing JavaScript HttpReply object",session->socket);
		send_error(session,"500 Error initializing JavaScript HttpReply object");
		return(FALSE);
	}

	JS_SetContextPrivate(session->js_cx, session);

	return(TRUE);
}

static BOOL exec_ssjs(http_session_t* session)  {
	JSString*	js_str;
	JSScript*	js_script;
	jsval		rval;
	jsval		val;
	JSObject*	reply;
	JSIdArray*	heads;
	JSObject*	headers;
	char		path[MAX_PATH+1];
	char		str[MAX_REQUEST_LINE+1];
	int			i;
	if((js_str=JS_NewStringCopyZ(session->js_cx, session->req.physical_path))==NULL)
		return(FALSE);
	JS_DefineProperty(session->js_cx, session->js_request, "real_path", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
	if((js_str=JS_NewStringCopyZ(session->js_cx, session->req.ars))==NULL)
	JS_DefineProperty(session->js_cx, session->js_request, "ars", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
	if((js_str=JS_NewStringCopyZ(session->js_cx, session->req.host))==NULL)
	JS_DefineProperty(session->js_cx, session->js_request, "host", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
	if((js_str=JS_NewStringCopyZ(session->js_cx, http_vers[session->http_ver]))==NULL)
		return(FALSE);
	JS_DefineProperty(session->js_cx, session->js_request, "http_ver", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
	if((js_str=JS_NewStringCopyZ(session->js_cx, session->host_ip))==NULL)
		return(FALSE);
	JS_DefineProperty(session->js_cx, session->js_request, "remote_ip", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

	if((js_str=JS_NewStringCopyZ(session->js_cx, session->host_name))==NULL)
		return(FALSE);
	JS_DefineProperty(session->js_cx, session->js_request, "remote_host", STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

	do {
		/* RUN SCRIPT */
		JS_ClearPendingException(session->js_cx);



		if((js_script=JS_CompileFile(session->js_cx, session->js_glob
			,session->req.physical_path))==NULL) {
			lprintf("%04d !JavaScript FAILED to compile script (%s)"
				,session->socket,session->req.physical_path);
			return(FALSE);
		}

		JS_ExecuteScript(session->js_cx, session->js_glob, js_script, &rval);

	} while(0);

	/* Read http_reply object */
	JS_GetProperty(session->js_cx,session->js_glob,"http_reply",&val);
	reply = JSVAL_TO_OBJECT(val);
	JS_GetProperty(session->js_cx,reply,"status",&val);
	SAFECOPY(session->req.status,JS_GetStringBytes(JSVAL_TO_STRING(val)));
	JS_GetProperty(session->js_cx,reply,"header",&val);
	headers = JSVAL_TO_OBJECT(val);
	heads=JS_Enumerate(session->js_cx,headers);
	for(i=0;i<heads->length;i++)  {
		JS_IdToValue(session->js_cx,heads->vector[i],&val);
		js_str=JSVAL_TO_STRING(val);
		JS_GetProperty(session->js_cx,headers,JS_GetStringBytes(js_str),&val);
		snprintf(str,MAX_REQUEST_LINE+1,"%s: %s",JS_GetStringBytes(js_str),JS_GetStringBytes(JSVAL_TO_STRING(val)));
		session->req.dynamic_heads=add_list(session->req.dynamic_heads,str);
	}
	JS_DestroyIdArray(session->js_cx, heads);

	/* Free up temporary resources here */

	if(js_script!=NULL) 
		JS_DestroyScript(session->js_cx, js_script);
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

	SAFECOPY(session->req.physical_path, path);
	session->req.dynamic=IS_SSJS;
	
	return(TRUE);
static void respond(http_session_t * session)
{
	if(session->req.dynamic==IS_CGI)  {
		if(!exec_cgi(session))  {
			send_error(session,"500 Internal Server Error");
			return;
		}
		close_request(session);
		return;
	}
	if(session->req.dynamic==IS_SSJS) {	/* Server-Side JavaScript */
		if(!exec_ssjs(session))  {
			send_error(session,"500 Internal Server Error");
			return;
		}
		sprintf(session->req.physical_path,"%s/SBBS_SSJS.%d.html",startup->cgi_temp_dir,session->socket);
	if(session->http_ver > HTTP_0_9)  {
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
		send_file=send_headers(session,session->req.status);
	if(send_file)  {
		lprintf("%04d Sending file",session->socket);
		sock_sendfile(session->socket,session->req.physical_path);
		lprintf("%04d Sent file",session->socket);
	}
	close_request(session);
}

void http_session_thread(void* arg)
{
	http_session_t	session=*(http_session_t*)arg;	/* copies arg BEFORE it's freed */
	lprintf("%04d Session thread started", session.socket);

	thread_up(FALSE /* setuid */);
	session.finished=FALSE;

	if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
		host=NULL;
	else
		host=gethostbyaddr ((char *)&session.addr.sin_addr
			,sizeof(session.addr.sin_addr),AF_INET);
	if(host!=NULL && host->h_name!=NULL)
		host_name=host->h_name;
	else
		host_name="<no name>";

	if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP))  {
		lprintf("%04d Hostname: %s", session.socket, host_name);
		SAFECOPY(session.host_name,host_name);
		for(i=0;host!=NULL && host->h_aliases!=NULL 
			&& host->h_aliases[i]!=NULL;i++)
			lprintf("%04d HostAlias: %s", session.socket, host->h_aliases[i]);
		if(trashcan(&scfg,host_name,"host")) {
			close_socket(session.socket);
			lprintf("%04d !CLIENT BLOCKED in host.can: %s", session.socket, host_name);
			thread_down();
			lprintf("%04d Free()ing session",socket);
			return;
		}

	/* host_ip wasn't defined in http_session_thread */
	if(trashcan(&scfg,session.host_ip,"ip")) {
		lprintf("%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip);
		lprintf("%04d Free()ing session",socket);
	while(!session.finished && server_socket!=INVALID_SOCKET) {
	    memset(&(session.req), 0, sizeof(session.req));
		SAFECOPY(session.req.status,"200 OK");
		if(get_req(&session)) {
			if((session.http_ver<HTTP_1_0)||parse_headers(&session)) {
				if(check_request(&session)) {
					respond(&session);

	if(session.js_cx!=NULL) {
		lprintf("%04d JavaScript: Destroying context",socket);
		JS_DestroyContext(session.js_cx);	/* Free Context */
	}

	if(session.js_runtime!=NULL) {
		lprintf("%04d JavaScript: Destroying runtime",socket);
		JS_DestroyRuntime(session.js_runtime);
	}
	lprintf("%04d Session thread terminated (%u clients, %u threads remain, %lu served)"
		,socket, active_clients, thread_count, served);
}

void DLLCALL web_terminate(void)
{
	recycle_server=FALSE;
	if(server_socket!=INVALID_SOCKET) {
    	lprintf("%04d Web Terminate: closing socket",server_socket);
		close_socket(server_socket);
		server_socket=INVALID_SOCKET;
    }
}

static void cleanup(int code)
{
	free_cfg(&scfg);

	if(server_socket!=INVALID_SOCKET) {
		close_socket(server_socket);
		server_socket=INVALID_SOCKET;
	}

	update_clients();

#ifdef _WINSOCKAPI_
	if(WSAInitialized && WSACleanup()!=0) 
		lprintf("0000 !WSACleanup ERROR %d",ERROR_VALUE);
#endif

	thread_down();
	status("Down");
    lprintf("#### Web Server thread terminated (%u threads remain, %lu clients served)"
		,thread_count, served);
	if(startup!=NULL && startup->terminated!=NULL)
		startup->terminated(code);
}

const char* DLLCALL web_ver(void)
{
	static char ver[256];
	char compiler[32];

	DESCRIBE_COMPILER(compiler);

	sscanf("$Revision$", "%*s %s", revision);

	sprintf(ver,"%s %s%s  "
		"Compiled %s %s with %s"
		,server_name
		,revision
#ifdef _DEBUG
		," Debug"
#else
		,""
#endif
		,__DATE__, __TIME__, compiler);

	return(ver);
}

void DLLCALL web_server(void* arg)
{
	int				i;
	int				result;
	time_t			start;
	WORD			host_port;
	char			path[MAX_PATH+1];
	char			logstr[256];
	SOCKADDR_IN		server_addr={0};
	SOCKADDR_IN		client_addr;
	socklen_t		client_addr_len;
	SOCKET			client_socket;
	SOCKET			high_socket_set;
	fd_set			socket_set;
	time_t			t;
	time_t			initialized=0;
	char			compiler[32];
	http_session_t *	session;
	struct timeval tv;
	startup=(web_startup_t*)arg;

rswindell's avatar
rswindell committed
	web_ver();	/* get CVS revision */

    if(startup==NULL) {
    	sbbs_beep(100,500);
    	fprintf(stderr, "No startup structure passed!\n");
    	return;
    }

rswindell's avatar
rswindell committed
	if(startup->size!=sizeof(web_startup_t)) {	/* verify size */
		sbbs_beep(100,500);
		sbbs_beep(300,500);
		sbbs_beep(100,500);
		fprintf(stderr, "Invalid startup structure!\n");
		return;
	}

	/* Setup intelligent defaults */
	if(startup->port==0)					startup->port=IPPORT_HTTP;
	if(startup->index_file_name[0][0]==0)	SAFECOPY(startup->index_file_name[0],"index.html");
	if(startup->root_dir[0]==0)				SAFECOPY(startup->root_dir,"../html");
	if(startup->error_dir[0]==0)			SAFECOPY(startup->error_dir,"../html/error");
	if(startup->max_inactivity==0) 			startup->max_inactivity=120; /* seconds */
	if(startup->js_max_bytes==0)			startup->js_max_bytes=JAVASCRIPT_MAX_BYTES;
	if(startup->cgi_ext[0][0]==0)			SAFECOPY(startup->cgi_ext[0],"cgi");
	if(startup->ssjs_ext[0]==0)				SAFECOPY(startup->ssjs_ext,"ssjs");

	/* Copy html directories */
	SAFECOPY(root_dir,startup->root_dir);
	SAFECOPY(error_dir,startup->error_dir);

	/* Change to absolute path */
	prep_dir(startup->ctrl_dir, root_dir, sizeof(root_dir));
	prep_dir(startup->ctrl_dir, error_dir, sizeof(error_dir));

	/* Trim off trailing slash/backslash */
	if(*(p=lastchar(root_dir))==BACKSLASH)	*p=0;
	if(*(p=lastchar(error_dir))==BACKSLASH)	*p=0;
	startup->recycle_now=FALSE;
	recycle_server=TRUE;
	do {

		thread_up(FALSE /* setuid */);

		status("Initializing");

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

rswindell's avatar
rswindell committed
		lprintf("%s Revision %s%s"
			,server_name
			,revision
#ifdef _DEBUG
			," Debug"
#else
			,""
#endif
			);

		DESCRIBE_COMPILER(compiler);

		lprintf("Compiled %s %s with %s", __DATE__, __TIME__, compiler);

		srand(time(NULL));	/* Seed random number generator */
		sbbs_random(10);	/* Throw away first number */

		if(!winsock_startup()) {
			cleanup(1);
			return;
		}

		t=time(NULL);
		lprintf("Initializing on %.24s with options: %lx"
			,CTIME_R(&t,logstr),startup->options);
		lprintf("Root HTML directory: %s", root_dir);
		lprintf("Error HTML directory: %s", error_dir);

		/* Initial configuration and load from CNF files */
		SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir);
		lprintf("Loading configuration files from %s", scfg.ctrl_dir);
		scfg.size=sizeof(scfg);
		SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
		if(!load_cfg(&scfg, NULL, TRUE, logstr)) {
			lprintf("!ERROR %s",logstr);
			lprintf("!FAILED to load configuration files");
			cleanup(1);
			return;
		}
		scfg_reloaded=TRUE;

		sprintf(path,"%smime_types.cfg",scfg.ctrl_dir);
		if(!read_mime_types(path)) {
			lprintf("!ERROR %d reading %s", errno,path);
			cleanup(1);
			return;
		}

		if(startup->host_name[0]==0)
			SAFECOPY(startup->host_name,scfg.sys_inetaddr);

		if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&BBS_OPT_LOCAL_TIMEZONE)) {
			if(putenv("TZ=UTC0"))
				lprintf("!putenv() FAILED");
			tzset();
		}

			uptime=time(NULL);	/* this must be done *after* setting the timezone */
		/* open a socket and wait for a client */

		server_socket = open_socket(SOCK_STREAM);

		if(server_socket == INVALID_SOCKET) {
			lprintf("!ERROR %d creating HTTP socket", ERROR_VALUE);
			cleanup(1);
			return;
		}
		lprintf("Web Server socket %d opened",server_socket);

		/*****************************/
		/* Listen for incoming calls */
		/*****************************/
		memset(&server_addr, 0, sizeof(server_addr));

		server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
		server_addr.sin_family = AF_INET;
		server_addr.sin_port   = htons(startup->port);

		if(startup->seteuid!=NULL)
			startup->seteuid(FALSE);
		result = bind(server_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
		if(startup->seteuid!=NULL)
			startup->seteuid(TRUE);
		if(result != 0) {
			lprintf("!ERROR %d (%d) binding HTTP socket to port %d"
				,result, ERROR_VALUE,startup->port);
			lprintf(BIND_FAILURE_HELP);
			cleanup(1);
			return;
		}

		result = listen(server_socket, 64);

		if(result != 0) {
			lprintf("!ERROR %d (%d) listening on HTTP socket", result, ERROR_VALUE);
			cleanup(1);
			return;
		}
		lprintf("Web Server listening on port %d",startup->port);
		status("Listening");

		/* signal caller that we've started up successfully */
		if(startup->started!=NULL)
    		startup->started();

		lprintf("Web Server thread started");
		sprintf(path,"%swebsrvr.rec",scfg.ctrl_dir);

		while(server_socket!=INVALID_SOCKET) {

			/* check for re-cycle semaphores */
			if(!(startup->options&BBS_OPT_NO_RECYCLE)) {
				t=fdate(path);
				if(!active_clients && t!=-1 && t>initialized) {
					lprintf("0000 Recycle semaphore file (%s) detected",path);
					initialized=t;
					break;
				}
				if(!active_clients && startup->recycle_now==TRUE) {
					lprintf("0000 Recycle semaphore signaled");
					startup->recycle_now=FALSE;
					break;
				}
			}

			/* now wait for connection */

			FD_ZERO(&socket_set);
			FD_SET(server_socket,&socket_set);
			high_socket_set=server_socket+1;

			tv.tv_sec=2;
			tv.tv_usec=0;

			if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
				if(i==0) {
					mswait(1);
					continue;
				}
				if(ERROR_VALUE==EINTR)
					lprintf("Web Server listening interrupted");
				else if(ERROR_VALUE == ENOTSOCK)
            		lprintf("Web Server socket closed");
				else
					lprintf("!ERROR %d selecting socket",ERROR_VALUE);
				break;
			}

			if(server_socket==INVALID_SOCKET)	/* terminated */
				break;

			client_addr_len = sizeof(client_addr);

			if(server_socket!=INVALID_SOCKET
				&& FD_ISSET(server_socket,&socket_set)) {
				client_socket = accept(server_socket, (struct sockaddr *)&client_addr
	        		,&client_addr_len);
			else {
				lprintf("!NO SOCKETS set by select");
				continue;
			}

			if(client_socket == INVALID_SOCKET)	{
				if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR || ERROR_VALUE == EINVAL) {
            		lprintf("Web Server socket closed");
					break;
				}
				lprintf("!ERROR %d accepting connection", ERROR_VALUE);
rswindell's avatar
rswindell committed
				break;
			SAFECOPY(host_ip,inet_ntoa(client_addr.sin_addr));

			if(trashcan(&scfg,host_ip,"ip-silent")) {
				close_socket(client_socket);
				continue;
			}

			host_port=ntohs(client_addr.sin_port);

			lprintf("%04d HTTP connection accepted from: %s port %u"
				,client_socket

			if(startup->socket_open!=NULL)
				startup->socket_open(TRUE);
	
			if((session=malloc(sizeof(http_session_t)))==NULL) {
				lprintf("%04d !ERROR allocating %u bytes of memory for service_client"
					,client_socket, sizeof(http_session_t));
				mswait(3000);
				close_socket(client_socket);
				continue;
			}

			memset(session, 0, sizeof(http_session_t));
			SAFECOPY(session->host_ip,host_ip);
   			session->socket=client_socket;

			_beginthread(http_session_thread, 0, session);
rswindell's avatar
rswindell committed
#if 0	/* this is handled in cleanup() */
rswindell's avatar
rswindell committed
		/* Close all open sockets  */
		lprintf("Closing Server Socket %d", server_socket);
		close_socket(server_socket);
		server_socket=INVALID_SOCKET;
rswindell's avatar
rswindell committed
		/* Wait for all node threads to terminate */
		if(http_threads_running) {
			lprintf("Waiting for %d connection threads to terminate...", http_threads_running);
			start=time(NULL);
			while(http_threads_running) {
				if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
					lprintf("!TIMEOUT waiting for %d http thread(s) to "
            			"terminate", http_threads_running);
					break;
				}
				mswait(100);
			}
		}

		cleanup(0);

		if(recycle_server) {
			lprintf("Recycling server...");
			mswait(2000);
		}

	} while(recycle_server);
}