/* websrvr.c */ /* Synchronet Web Server */ /* $Id$ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * * Copyright 2011 Rob Swindell - http://www.synchro.net/copyright.html * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * See the GNU General Public License for more details: gpl.txt or * * http://www.fsf.org/copyleft/gpl.html * * * * Anonymous FTP access to the most recent released source is available at * * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * * * * Anonymous CVS access to the development source and modification history * * is available at cvs.synchro.net:/cvsroot/sbbs, example: * * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * * (just hit return, no password is necessary) * * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * * * * For Synchronet coding style and modification guidelines, see * * http://www.synchro.net/source.html * * * * You are encouraged to submit any modifications (preferably in Unix diff * * format) via e-mail to mods@synchro.net * * * * Note: If this box doesn't appear square, then you need to fix your tabs. * ****************************************************************************/ /* * General notes: (ToDo stuff) * * Support the ident protocol... the standard log format supports it. * * Add in support to pass connections through to a different webserver... * probobly in access.ars... with like a simplified mod_rewrite. * This would allow people to run apache and Synchronet as the same site. * * Add support for multipart/form-data * */ //#define ONE_JS_RUNTIME /* Headers for CGI stuff */ #if defined(__unix__) #include <sys/wait.h> /* waitpid() */ #include <sys/types.h> #include <signal.h> /* kill() */ #endif #ifndef JAVASCRIPT #define JAVASCRIPT #endif #undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */ #include "sbbs.h" #include "sbbsdefs.h" #include "sockwrap.h" /* sendfilesocket() */ #include "threadwrap.h" #include "semwrap.h" #include "websrvr.h" #include "base64.h" #include "md5.h" #include "js_rtpool.h" #include "js_request.h" #include "xpmap.h" static const char* server_name="Synchronet Web Server"; static const char* newline="\r\n"; static const char* http_scheme="http://"; static const size_t http_scheme_len=7; static const char* error_301="301 Moved Permanently"; static const char* error_302="302 Moved Temporarily"; static const char* error_404="404 Not Found"; static const char* error_416="416 Requested Range Not Satisfiable"; static const char* error_500="500 Internal Server Error"; static const char* unknown="<unknown>"; #define TIMEOUT_THREAD_WAIT 60 /* Seconds */ #define MAX_REQUEST_LINE 1024 /* NOT including terminator */ #define MAX_HEADERS_SIZE 16384 /* Maximum total size of all headers (Including terminator )*/ #define MAX_REDIR_LOOPS 20 /* Max. times to follow internal redirects for a single request */ #define MAX_POST_LEN 1048576 /* Max size of body for POSTS */ #define OUTBUF_LEN 20480 /* Size of output thread ring buffer */ enum { CLEANUP_SSJS_TMP_FILE ,CLEANUP_POST_DATA ,MAX_CLEANUPS }; static scfg_t scfg; static volatile BOOL http_logging_thread_running=FALSE; static protected_int32_t active_clients; static volatile ulong sockets=0; static volatile BOOL terminate_server=FALSE; static volatile BOOL terminate_http_logging_thread=FALSE; static volatile ulong thread_count=0; static SOCKET server_socket=INVALID_SOCKET; static char revision[16]; static char root_dir[MAX_PATH+1]; static char error_dir[MAX_PATH+1]; static char temp_dir[MAX_PATH+1]; static char cgi_dir[MAX_PATH+1]; static char cgi_env_ini[MAX_PATH+1]; static char default_auth_list[MAX_PATH+1]; static volatile time_t uptime=0; static volatile ulong served=0; static web_startup_t* startup=NULL; static js_server_props_t js_server_props; static str_list_t recycle_semfiles; static str_list_t shutdown_semfiles; static str_list_t cgi_env; static volatile ulong session_threads=0; static named_string_t** mime_types; static named_string_t** cgi_handlers; static named_string_t** xjs_handlers; /* Logging stuff */ link_list_t log_list; struct log_data { char *hostname; char *ident; char *user; char *request; char *referrer; char *agent; char *vhost; int status; unsigned int size; struct tm completed; }; enum auth_type { AUTHENTICATION_UNKNOWN ,AUTHENTICATION_BASIC ,AUTHENTICATION_DIGEST }; char *auth_type_names[4] = { "Unknown" ,"Basic" ,"Digest" ,NULL }; enum algorithm { ALGORITHM_UNKNOWN ,ALGORITHM_MD5 ,ALGORITHM_MD5_SESS }; enum qop_option { QOP_NONE ,QOP_AUTH ,QOP_AUTH_INT ,QOP_UNKNOWN }; typedef struct { enum auth_type type; char username[(LEN_ALIAS > LEN_NAME ? LEN_ALIAS : LEN_NAME)+1]; char password[LEN_PASS+1]; char *digest_uri; char *realm; char *nonce; enum algorithm algorithm; enum qop_option qop_value; char *cnonce; char *nonce_count; unsigned char digest[16]; /* MD5 digest */ BOOL stale; } authentication_request_t; typedef struct { int method; char virtual_path[MAX_PATH+1]; char physical_path[MAX_PATH+1]; BOOL expect_go_ahead; time_t if_modified_since; BOOL keep_alive; char ars[256]; authentication_request_t auth; char host[128]; /* The requested host. (as used for self-referencing URLs) */ char vhost[128]; /* The requested host. (virtual host) */ int send_location; const char* mime_type; str_list_t headers; char status[MAX_REQUEST_LINE+1]; char * post_data; struct xpmapping *post_map; size_t post_len; int dynamic; char xjs_handler[MAX_PATH+1]; struct log_data *ld; char request_line[MAX_REQUEST_LINE+1]; BOOL finished; /* Done processing request. */ BOOL read_chunked; BOOL write_chunked; long range_start; long range_end; BOOL accept_ranges; time_t if_range; BOOL path_info_index; /* CGI parameters */ char query_str[MAX_REQUEST_LINE+1]; char extra_path_info[MAX_REQUEST_LINE+1]; str_list_t cgi_env; str_list_t dynamic_heads; /* Dynamically (sever-side JS) generated HTML parameters */ FILE* fp; char *cleanup_file[MAX_CLEANUPS]; BOOL sent_headers; BOOL prev_write; /* webctrl.ini overrides */ char *error_dir; char *cgi_dir; char *auth_list; char *realm; char *digest_realm; } http_request_t; typedef struct { SOCKET socket; SOCKADDR_IN addr; SOCKET socket6; SOCKADDR_IN addr6; http_request_t req; char host_ip[64]; char host_name[128]; /* Resolved remote host */ int http_ver; /* HTTP version. 0 = HTTP/0.9, 1=HTTP/1.0, 2=HTTP/1.1 */ BOOL finished; /* Do not accept any more imput from client */ user_t user; int last_user_num; time_t logon_time; char username[LEN_NAME+1]; int last_js_user_num; /* JavaScript parameters */ JSRuntime* js_runtime; JSContext* js_cx; JSObject* js_glob; JSObject* js_query; JSObject* js_header; JSObject* js_cookie; JSObject* js_request; js_callback_t js_callback; subscan_t *subscan; /* Ring Buffer Stuff */ RingBuf outbuf; sem_t output_thread_terminated; int outbuf_write_initialized; pthread_mutex_t outbuf_write; /* Client info */ client_t client; /* Synchronization stuff */ pthread_mutex_t struct_filled; } http_session_t; enum { HTTP_0_9 ,HTTP_1_0 ,HTTP_1_1 }; static char* http_vers[] = { "" ,"HTTP/1.0" ,"HTTP/1.1" ,NULL /* terminator */ }; enum { HTTP_HEAD ,HTTP_GET ,HTTP_POST ,HTTP_OPTIONS }; static char* methods[] = { "HEAD" ,"GET" ,"POST" ,"OPTIONS" ,NULL /* terminator */ }; enum { IS_STATIC ,IS_CGI ,IS_JS ,IS_SSJS }; enum { HEAD_DATE ,HEAD_HOST ,HEAD_IFMODIFIED ,HEAD_LENGTH ,HEAD_TYPE ,HEAD_AUTH ,HEAD_CONNECTION ,HEAD_WWWAUTH ,HEAD_STATUS ,HEAD_ALLOW ,HEAD_EXPIRES ,HEAD_LASTMODIFIED ,HEAD_LOCATION ,HEAD_PRAGMA ,HEAD_SERVER ,HEAD_REFERER ,HEAD_AGENT ,HEAD_TRANSFER_ENCODING ,HEAD_ACCEPT_RANGES ,HEAD_CONTENT_RANGE ,HEAD_RANGE ,HEAD_IFRANGE ,HEAD_COOKIE }; static struct { int id; char* text; } headers[] = { { HEAD_DATE, "Date" }, { HEAD_HOST, "Host" }, { HEAD_IFMODIFIED, "If-Modified-Since" }, { HEAD_LENGTH, "Content-Length" }, { HEAD_TYPE, "Content-Type" }, { HEAD_AUTH, "Authorization" }, { HEAD_CONNECTION, "Connection" }, { HEAD_WWWAUTH, "WWW-Authenticate" }, { HEAD_STATUS, "Status" }, { HEAD_ALLOW, "Allow" }, { HEAD_EXPIRES, "Expires" }, { HEAD_LASTMODIFIED, "Last-Modified" }, { HEAD_LOCATION, "Location" }, { HEAD_PRAGMA, "Pragma" }, { HEAD_SERVER, "Server" }, { HEAD_REFERER, "Referer" }, { HEAD_AGENT, "User-Agent" }, { HEAD_TRANSFER_ENCODING, "Transfer-Encoding" }, { HEAD_ACCEPT_RANGES, "Accept-Ranges" }, { HEAD_CONTENT_RANGE, "Content-Range" }, { HEAD_RANGE, "Range" }, { HEAD_IFRANGE, "If-Range" }, { HEAD_COOKIE, "Cookie" }, { -1, NULL /* terminator */ }, }; /* Everything MOVED_TEMP and everything after is a magical internal redirect */ enum { NO_LOCATION ,MOVED_PERM ,MOVED_TEMP ,MOVED_STAT }; static char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; static char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; static void respond(http_session_t * session); static BOOL js_setup(http_session_t* session); static char *find_last_slash(char *str); static BOOL check_extra_path(http_session_t * session); static BOOL exec_ssjs(http_session_t* session, char* script); static BOOL ssjs_send_headers(http_session_t* session, int chunked); static time_t sub_mkgmt(struct tm *tm) { int y, nleapdays; time_t t; /* days before the month */ static const unsigned short moff[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; /* * XXX: This code assumes the given time to be normalized. * Normalizing here is impossible in case the given time is a leap * second but the local time library is ignorant of leap seconds. */ /* minimal sanity checking not to access outside of the array */ if ((unsigned) tm->tm_mon >= 12) return (time_t) -1; if (tm->tm_year < 1970 - 1900) return (time_t) -1; y = tm->tm_year + 1900 - (tm->tm_mon < 2); nleapdays = y / 4 - y / 100 + y / 400 - ((1970-1) / 4 - (1970-1) / 100 + (1970-1) / 400); t = ((((time_t) (tm->tm_year - (1970 - 1900)) * 365 + moff[tm->tm_mon] + tm->tm_mday - 1 + nleapdays) * 24 + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; return (t < 0 ? (time_t) -1 : t); } time_t time_gm(struct tm *tm) { time_t t, t2; struct tm *tm2; int sec; /* Do the first guess. */ if ((t = sub_mkgmt(tm)) == (time_t) -1) return (time_t) -1; /* save value in case *tm is overwritten by gmtime() */ sec = tm->tm_sec; tm2 = gmtime(&t); if ((t2 = sub_mkgmt(tm2)) == (time_t) -1) return (time_t) -1; if (t2 < t || tm2->tm_sec != sec) { /* * Adjust for leap seconds. * * real time_t time * | * tm * / ... (a) first sub_mkgmt() conversion * t * | * tm2 * / ... (b) second sub_mkgmt() conversion * t2 * --->time */ /* * Do the second guess, assuming (a) and (b) are almost equal. */ t += t - t2; tm2 = gmtime(&t); /* * Either (a) or (b), may include one or two extra * leap seconds. Try t, t + 2, t - 2, t + 1, and t - 1. */ if (tm2->tm_sec == sec || (t += 2, tm2 = gmtime(&t), tm2->tm_sec == sec) || (t -= 4, tm2 = gmtime(&t), tm2->tm_sec == sec) || (t += 3, tm2 = gmtime(&t), tm2->tm_sec == sec) || (t -= 2, tm2 = gmtime(&t), tm2->tm_sec == sec)) ; /* found */ else { /* * Not found. */ if (sec >= 60) /* * The given time is a leap second * (sec 60 or 61), but the time library * is ignorant of the leap second. */ ; /* treat sec 60 as 59, sec 61 as 0 of the next minute */ else /* The given time may not be normalized. */ t++; /* restore t */ } } return (t < 0 ? (time_t) -1 : t); } static int lprintf(int level, const char *fmt, ...) { va_list argptr; char sbuf[1024]; va_start(argptr,fmt); vsnprintf(sbuf,sizeof(sbuf),fmt,argptr); sbuf[sizeof(sbuf)-1]=0; va_end(argptr); if(level <= LOG_ERR) { errorlog(&scfg,startup==NULL ? NULL:startup->host_name, sbuf); if(startup!=NULL && startup->errormsg!=NULL) startup->errormsg(startup->cbdata,level,sbuf); } if(startup==NULL || startup->lputs==NULL || level > startup->log_level) return(0); #if defined(_WIN32) if(IsBadCodePtr((FARPROC)startup->lputs)) return(0); #endif return(startup->lputs(startup->cbdata,level,sbuf)); } static int writebuf(http_session_t *session, const char *buf, size_t len) { size_t sent=0; size_t avail; while(sent < len) { avail=RingBufFree(&session->outbuf); if(!avail) { SLEEP(1); continue; } if(avail > len-sent) avail=len-sent; sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail); } return(sent); } static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed) { size_t sent=0; int result; int sel; fd_set wr_set; struct timeval tv; while(sent<len && *sock!=INVALID_SOCKET) { FD_ZERO(&wr_set); FD_SET(*sock,&wr_set); /* Convert timeout from ms to sec/usec */ tv.tv_sec=startup->max_inactivity; tv.tv_usec=0; sel=select(*sock+1,NULL,&wr_set,NULL,&tv); switch(sel) { case 1: result=sendsocket(*sock,buf+sent,len-sent); if(result==SOCKET_ERROR) { if(ERROR_VALUE==ECONNRESET) lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",*sock); else if(ERROR_VALUE==ECONNABORTED) lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",*sock); #ifdef EPIPE else if(ERROR_VALUE==EPIPE) lprintf(LOG_NOTICE,"%04d Unable to send to peer",*sock); #endif else lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE); if(failed) *failed=TRUE; return(sent); } break; case 0: lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock); if(failed) *failed=TRUE; return(sent); case -1: lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE); if(failed) *failed=TRUE; return(sent); } sent+=result; } if(failed && sent<len) *failed=TRUE; return(sent); } #ifdef _WINSOCKAPI_ static WSADATA WSAData; #define SOCKLIB_DESC WSAData.szDescription static BOOL WSAInitialized=FALSE; static BOOL winsock_startup(void) { int status; /* Status Code */ if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) { lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus); WSAInitialized=TRUE; return (TRUE); } lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status); return (FALSE); } #else /* No WINSOCK */ #define winsock_startup() (TRUE) #define SOCKLIB_DESC NULL #endif static void status(char* str) { if(startup!=NULL && startup->status!=NULL) startup->status(startup->cbdata,str); } static void update_clients(void) { if(startup!=NULL && startup->clients!=NULL) startup->clients(startup->cbdata,active_clients.value); } static void client_on(SOCKET sock, client_t* client, BOOL update) { if(startup!=NULL && startup->client_on!=NULL) startup->client_on(startup->cbdata,TRUE,sock,client,update); } static void client_off(SOCKET sock) { if(startup!=NULL && startup->client_on!=NULL) startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE); } static void thread_up(BOOL setuid) { thread_count++; if(startup!=NULL && startup->thread_up!=NULL) startup->thread_up(startup->cbdata,TRUE, setuid); } static void thread_down(void) { if(thread_count>0) thread_count--; if(startup!=NULL && startup->thread_up!=NULL) startup->thread_up(startup->cbdata,FALSE, FALSE); } /*********************************************************************/ /* Adds an environment variable to the sessions cgi_env linked list */ /*********************************************************************/ static void add_env(http_session_t *session, const char *name,const char *value) { char newname[129]; char *p; if(name==NULL || value==NULL) { lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket); return; } SAFECOPY(newname,name); for(p=newname;*p;p++) { *p=toupper(*p); if(*p=='-') *p='_'; } p=(char *)alloca(strlen(name)+strlen(value)+2); if(p==NULL) { lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket); return; } #if 0 /* this is way too verbose for every request */ lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value); #endif sprintf(p,"%s=%s",newname,value); strListPush(&session->req.cgi_env,p); } /***************************************/ /* Initializes default CGI envirnoment */ /***************************************/ static void init_enviro(http_session_t *session) { char str[128]; add_env(session,"SERVER_SOFTWARE",VERSION_NOTICE); sprintf(str,"%d",startup->port); add_env(session,"SERVER_PORT",str); add_env(session,"GATEWAY_INTERFACE","CGI/1.1"); if(!strcmp(session->host_name,session->host_ip)) add_env(session,"REMOTE_HOST",session->host_name); add_env(session,"REMOTE_ADDR",session->host_ip); add_env(session,"REQUEST_URI",session->req.request_line); } /* * Sends string str to socket sock... returns number of bytes written, or 0 on an error * Can not close the socket since it can not set it to INVALID_SOCKET */ static int bufprint(http_session_t *session, const char *str) { int len; len=strlen(str); return(writebuf(session,str,len)); } /**********************************************************/ /* Converts a month name/abbr to the 0-based month number */ /* ToDo: This probobly exists somewhere else already */ /**********************************************************/ static int getmonth(char *mon) { int i; for(i=0;i<12;i++) if(!stricmp(mon,months[i])) return(i); return 0; } /*******************************************************************/ /* Converts a date string in any of the common formats to a time_t */ /*******************************************************************/ static time_t decode_date(char *date) { struct tm ti; char *token; char *last; time_t t; ti.tm_sec=0; /* seconds (0 - 60) */ ti.tm_min=0; /* minutes (0 - 59) */ ti.tm_hour=0; /* hours (0 - 23) */ ti.tm_mday=1; /* day of month (1 - 31) */ ti.tm_mon=0; /* month of year (0 - 11) */ ti.tm_year=0; /* year - 1900 */ ti.tm_isdst=0; /* is summer time in effect? */ token=strtok_r(date,",",&last); if(token==NULL) return(0); /* This probobly only needs to be 9, but the extra one is for luck. */ if(strlen(date)>15) { /* asctime() */ /* Toss away week day */ token=strtok_r(date," ",&last); if(token==NULL) return(0); token=strtok_r(NULL," ",&last); if(token==NULL) return(0); ti.tm_mon=getmonth(token); token=strtok_r(NULL," ",&last); if(token==NULL) return(0); ti.tm_mday=atoi(token); token=strtok_r(NULL,":",&last); if(token==NULL) return(0); ti.tm_hour=atoi(token); token=strtok_r(NULL,":",&last); if(token==NULL) return(0); ti.tm_min=atoi(token); token=strtok_r(NULL," ",&last); if(token==NULL) return(0); ti.tm_sec=atoi(token); token=strtok_r(NULL,"",&last); if(token==NULL) return(0); ti.tm_year=atoi(token)-1900; } else { /* RFC 1123 or RFC 850 */ token=strtok_r(NULL," -",&last); if(token==NULL) return(0); ti.tm_mday=atoi(token); token=strtok_r(NULL," -",&last); if(token==NULL) return(0); ti.tm_mon=getmonth(token); token=strtok_r(NULL," ",&last); if(token==NULL) return(0); ti.tm_year=atoi(token); token=strtok_r(NULL,":",&last); if(token==NULL) return(0); ti.tm_hour=atoi(token); token=strtok_r(NULL,":",&last); if(token==NULL) return(0); ti.tm_min=atoi(token); token=strtok_r(NULL," ",&last); if(token==NULL) return(0); ti.tm_sec=atoi(token); if(ti.tm_year>1900) ti.tm_year -= 1900; } t=time_gm(&ti); return(t); } static SOCKET open_socket(int type) { char error[256]; SOCKET sock; sock=socket(AF_INET, type, IPPROTO_IP); if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) startup->socket_open(startup->cbdata,TRUE); if(sock!=INVALID_SOCKET) { if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error))) lprintf(LOG_ERR,"%04d !ERROR %s",sock,error); sockets++; } return(sock); } static int close_socket(SOCKET *sock) { int result; if(sock==NULL || *sock==INVALID_SOCKET) return(-1); /* required to ensure all data is send when SO_LINGER is off (Not functional on Win32) */ shutdown(*sock,SHUT_RDWR); result=closesocket(*sock); *sock=INVALID_SOCKET; if(startup!=NULL && startup->socket_open!=NULL) { startup->socket_open(startup->cbdata,FALSE); } sockets--; if(result!=0) { if(ERROR_VALUE!=ENOTSOCK) lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE); } return(result); } /* Waits for the outbuf to drain */ static void drain_outbuf(http_session_t * session) { if(session->socket==INVALID_SOCKET) return; /* Force the output thread to go NOW */ sem_post(&(session->outbuf.highwater_sem)); /* ToDo: This should probobly timeout eventually... */ while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET) SLEEP(1); /* Lock the mutex to ensure data has been sent */ while(session->socket!=INVALID_SOCKET && !session->outbuf_write_initialized) SLEEP(1); if(session->socket==INVALID_SOCKET) return; pthread_mutex_lock(&session->outbuf_write); /* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */ pthread_mutex_unlock(&session->outbuf_write); } /**************************************************/ /* End of a single request... */ /* This is called at the end of EVERY request */ /* Log the request */ /* Free request-specific data ie: dynamic stuff */ /* Close socket unless it's being kept alive */ /* If the socket is closed, the session is done */ /**************************************************/ static void close_request(http_session_t * session) { time_t now; int i; if(session->req.write_chunked) { drain_outbuf(session); session->req.write_chunked=0; writebuf(session,"0\r\n",3); if(session->req.dynamic==IS_SSJS) ssjs_send_headers(session,FALSE); else /* Non-ssjs isn't capable of generating headers during execution */ writebuf(session, newline, 2); } /* Force the output thread to go NOW */ sem_post(&(session->outbuf.highwater_sem)); if(session->req.ld!=NULL) { now=time(NULL); localtime_r(&now,&session->req.ld->completed); listPushNode(&log_list,session->req.ld); session->req.ld=NULL; } strListFree(&session->req.headers); strListFree(&session->req.dynamic_heads); strListFree(&session->req.cgi_env); if(session->req.post_map != NULL) { xpunmap(session->req.post_map); session->req.post_data=NULL; session->req.post_map=NULL; } FREE_AND_NULL(session->req.post_data); FREE_AND_NULL(session->req.error_dir); FREE_AND_NULL(session->req.cgi_dir); FREE_AND_NULL(session->req.auth_list); FREE_AND_NULL(session->req.realm); FREE_AND_NULL(session->req.digest_realm); FREE_AND_NULL(session->req.auth_list); FREE_AND_NULL(session->req.auth.digest_uri); FREE_AND_NULL(session->req.auth.cnonce); FREE_AND_NULL(session->req.auth.realm); FREE_AND_NULL(session->req.auth.nonce); FREE_AND_NULL(session->req.auth.nonce_count); /* * This causes all active http_session_threads to terminate. */ if((!session->req.keep_alive) || terminate_server) { drain_outbuf(session); close_socket(&session->socket); } if(session->socket==INVALID_SOCKET) session->finished=TRUE; if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) { JS_BEGINREQUEST(session->js_cx); JS_GC(session->js_cx); JS_ENDREQUEST(session->js_cx); } if(session->subscan!=NULL) putmsgptrs(&scfg, session->user.number, session->subscan); if(session->req.fp!=NULL) fclose(session->req.fp); for(i=0;i<MAX_CLEANUPS;i++) { if(session->req.cleanup_file[i]!=NULL) { if(!(startup->options&WEB_OPT_DEBUG_SSJS)) remove(session->req.cleanup_file[i]); free(session->req.cleanup_file[i]); } } memset(&session->req,0,sizeof(session->req)); } static int get_header_type(char *header) { int i; for(i=0; headers[i].text!=NULL; i++) { if(!stricmp(header,headers[i].text)) { return(headers[i].id); } } return(-1); } /* Opposite of get_header_type() */ static char *get_header(int id) { int i; if(headers[id].id==id) return(headers[id].text); for(i=0;headers[i].text!=NULL;i++) { if(headers[i].id==id) { return(headers[i].text); } } return(NULL); } static const char* unknown_mime_type="application/octet-stream"; static const char* get_mime_type(char *ext) { uint i; if(ext==NULL || mime_types==NULL) return(unknown_mime_type); for(i=0;mime_types[i]!=NULL;i++) if(stricmp(ext+1,mime_types[i]->name)==0) return(mime_types[i]->value); return(unknown_mime_type); } static char* get_cgi_handler(const char* fname) { char* ext; size_t i; if(cgi_handlers==NULL || (ext=getfext(fname))==NULL) return(NULL); for(i=0;cgi_handlers[i]!=NULL;i++) { if(stricmp(cgi_handlers[i]->name, ext+1)==0) return(cgi_handlers[i]->value); } return(NULL); } static BOOL get_xjs_handler(char* ext, http_session_t* session) { size_t i; if(ext==NULL || xjs_handlers==NULL || ext[0]==0) return(FALSE); for(i=0;xjs_handlers[i]!=NULL;i++) { if(stricmp(xjs_handlers[i]->name, ext+1)==0) { if(getfname(xjs_handlers[i]->value)==xjs_handlers[i]->value) /* no path specified */ SAFEPRINTF2(session->req.xjs_handler,"%s%s",scfg.exec_dir,xjs_handlers[i]->value); else SAFECOPY(session->req.xjs_handler,xjs_handlers[i]->value); return(TRUE); } } return(FALSE); } /* This function appends append plus a newline IF the final dst string would have a length less than maxlen */ static void safecat(char *dst, const char *append, size_t maxlen) { size_t dstlen,appendlen; dstlen=strlen(dst); appendlen=strlen(append); if(dstlen+appendlen+2 < maxlen) { strcat(dst,append); strcat(dst,newline); } } /*************************************************/ /* Sends headers for the reply. */ /* HTTP/0.9 doesn't use headers, so just returns */ /*************************************************/ static BOOL send_headers(http_session_t *session, const char *status, int chunked) { int ret; int stat_code; BOOL send_file=TRUE; time_t ti; size_t idx; const char *status_line; struct stat stats; struct tm tm; char *headers; char header[MAX_REQUEST_LINE+1]; BOOL send_entity=TRUE; if(session->socket==INVALID_SOCKET) { session->req.sent_headers=TRUE; return(FALSE); } lprintf(LOG_DEBUG,"%04d Request resolved to: %s" ,session->socket,session->req.physical_path); if(session->http_ver <= HTTP_0_9) { session->req.sent_headers=TRUE; if(session->req.ld != NULL) session->req.ld->status=atoi(status); return(TRUE); } headers=alloca(MAX_HEADERS_SIZE); if(headers==NULL) { lprintf(LOG_CRIT,"Could not allocate memory for response headers."); return(FALSE); } *headers=0; if(!session->req.sent_headers) { session->req.sent_headers=TRUE; status_line=status; ret=stat(session->req.physical_path,&stats); if(session->req.method==HTTP_OPTIONS) ret=-1; if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) { status_line="304 Not Modified"; ret=-1; send_file=FALSE; send_entity=FALSE; } if(!ret && session->req.if_range && (stats.st_mtime > session->req.if_range || session->req.dynamic)) { status_line="200 OK"; session->req.range_start=0; session->req.range_end=0; } if(session->req.send_location==MOVED_PERM) { status_line=error_301; ret=-1; send_file=FALSE; } if(session->req.send_location==MOVED_TEMP) { status_line=error_302; ret=-1; send_file=FALSE; } stat_code=atoi(status_line); if(session->req.ld!=NULL) session->req.ld->status=stat_code; if(stat_code==304 || stat_code==204 || (stat_code >= 100 && stat_code<=199)) { send_file=FALSE; chunked=FALSE; } /* Status-Line */ safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line); lprintf(LOG_DEBUG,"%04d Result: %s",session->socket,header); safecat(headers,header,MAX_HEADERS_SIZE); /* General Headers */ ti=time(NULL); if(gmtime_r(&ti,&tm)==NULL) memset(&tm,0,sizeof(tm)); safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT" ,get_header(HEAD_DATE) ,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon] ,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec); safecat(headers,header,MAX_HEADERS_SIZE); if(session->req.keep_alive) { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive"); safecat(headers,header,MAX_HEADERS_SIZE); } else { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close"); safecat(headers,header,MAX_HEADERS_SIZE); } /* Response Headers */ safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE); safecat(headers,header,MAX_HEADERS_SIZE); /* Entity Headers */ if(session->req.dynamic) { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS"); safecat(headers,header,MAX_HEADERS_SIZE); safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"none"); safecat(headers,header,MAX_HEADERS_SIZE); } else { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS"); safecat(headers,header,MAX_HEADERS_SIZE); safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"bytes"); safecat(headers,header,MAX_HEADERS_SIZE); } if(session->req.send_location) { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path)); safecat(headers,header,MAX_HEADERS_SIZE); } if(chunked) { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TRANSFER_ENCODING),"Chunked"); safecat(headers,header,MAX_HEADERS_SIZE); } /* DO NOT send a content-length for chunked */ if(send_entity) { if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) { if(ret) { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0"); safecat(headers,header,MAX_HEADERS_SIZE); } else { if((session->req.range_start || session->req.range_end) && atoi(status_line)==206) { safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),session->req.range_end-session->req.range_start+1); safecat(headers,header,MAX_HEADERS_SIZE); } else { safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size); safecat(headers,header,MAX_HEADERS_SIZE); } } } if(!ret && !session->req.dynamic) { safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type); safecat(headers,header,MAX_HEADERS_SIZE); gmtime_r(&stats.st_mtime,&tm); safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT" ,get_header(HEAD_LASTMODIFIED) ,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon] ,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec); safecat(headers,header,MAX_HEADERS_SIZE); } if(session->req.range_start || session->req.range_end) { switch(atoi(status_line)) { case 206: /* Partial reply */ safe_snprintf(header,sizeof(header),"%s: bytes %d-%d/%d",get_header(HEAD_CONTENT_RANGE),session->req.range_start,session->req.range_end,stats.st_size); safecat(headers,header,MAX_HEADERS_SIZE); break; default: safe_snprintf(header,sizeof(header),"%s: *",get_header(HEAD_CONTENT_RANGE)); safecat(headers,header,MAX_HEADERS_SIZE); break; } } } } if(session->req.dynamic) { /* Dynamic headers */ /* Set up environment */ for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++) safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE); /* free() the headers so they don't get sent again if more are sent at the end of the request (chunked) */ strListFreeStrings(session->req.dynamic_heads); } safecat(headers,"",MAX_HEADERS_SIZE); send_file = (bufprint(session,headers) && send_file); drain_outbuf(session); session->req.write_chunked=chunked; return(send_file); } static int sock_sendfile(http_session_t *session,char *path,unsigned long start, unsigned long end) { int file; int ret=0; int i; char buf[2048]; /* Input buffer */ unsigned long remain; if(startup->options&WEB_OPT_DEBUG_TX) lprintf(LOG_DEBUG,"%04d Sending %s",session->socket,path); if((file=open(path,O_RDONLY|O_BINARY))==-1) lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",session->socket,errno,path); else { if(start || end) { if(lseek(file, start, SEEK_SET)==-1) { lprintf(LOG_WARNING,"%04d !ERROR %d seeking to position %lu in %s",session->socket,ERROR_VALUE,start,path); return(0); } remain=end-start+1; } else { remain=-1L; } while((i=read(file, buf, remain>sizeof(buf)?sizeof(buf):remain))>0) { if(writebuf(session,buf,i)!=i) { lprintf(LOG_WARNING,"%04d !ERROR sending %s",session->socket,path); return(0); } ret+=i; remain-=i; } close(file); } return(ret); } /********************************************************/ /* Sends a specified error message, closes the request, */ /* and marks the session to be closed */ /********************************************************/ static void send_error(http_session_t * session, const char* message) { char error_code[4]; struct stat sb; char sbuf[MAX_PATH+1]; char sbuf2[MAX_PATH+1]; BOOL sent_ssjs=FALSE; if(session->socket==INVALID_SOCKET) return; session->req.if_modified_since=0; lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message); session->req.keep_alive=FALSE; session->req.send_location=NO_LOCATION; SAFECOPY(error_code,message); SAFECOPY(session->req.status,message); if(atoi(error_code)<500) { /* * Attempt to run SSJS error pages * If this fails, do the standard error page instead, * ie: Don't "upgrade" to a 500 error */ if(session->req.error_dir) { /* We have a custom error directory from webctrl.ini look there first */ sprintf(sbuf,"%s%s%s",session->req.error_dir,error_code,startup->ssjs_ext); if(stat(sbuf,&sb)) { /* No custom .ssjs error message... check for custom .html */ sprintf(sbuf2,"%s%s.html",session->req.error_dir,error_code); if(stat(sbuf2,&sb)) { /* Nope, no custom .html error either, check for global ssjs one */ sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext); } } } else sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext); if(!stat(sbuf,&sb)) { lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket); session->req.dynamic=IS_SSJS; if(js_setup(session)) { sent_ssjs=exec_ssjs(session,sbuf); if(sent_ssjs) { int snt=0; lprintf(LOG_INFO,"%04d Sending generated error page",session->socket); snt=sock_sendfile(session,session->req.physical_path,0,0); if(snt<0) snt=0; if(session->req.ld!=NULL) session->req.ld->size=snt; } else session->req.dynamic=IS_STATIC; } else session->req.dynamic=IS_STATIC; } } if(!sent_ssjs) { if(session->req.error_dir) { sprintf(session->req.physical_path,"%s%s.html",session->req.error_dir,error_code); if(stat(session->req.physical_path,&sb)) sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code); } else sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code); session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.')); send_headers(session,message,FALSE); if(!stat(session->req.physical_path,&sb)) { int snt=0; snt=sock_sendfile(session,session->req.physical_path,0,0); if(snt<0) snt=0; if(session->req.ld!=NULL) session->req.ld->size=snt; } else { 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); bufprint(session,sbuf); if(session->req.ld!=NULL) session->req.ld->size=strlen(sbuf); } } drain_outbuf(session); session->req.finished=TRUE; } void http_logon(http_session_t * session, user_t *usr) { char str[128]; if(usr==NULL) getuserdat(&scfg, &session->user); else session->user=*usr; if(session->user.number==session->last_user_num) return; lprintf(LOG_DEBUG,"%04d HTTP Logon (user #%d)",session->socket,session->user.number); if(session->subscan!=NULL) getmsgptrs(&scfg,session->user.number,session->subscan); session->logon_time=time(NULL); if(session->user.number==0) SAFECOPY(session->username,unknown); else { SAFECOPY(session->username,session->user.alias); /* Adjust Connect and host */ putuserrec(&scfg,session->user.number,U_MODEM,LEN_MODEM,"HTTP"); putuserrec(&scfg,session->user.number,U_COMP,LEN_COMP,session->host_name); putuserrec(&scfg,session->user.number,U_NOTE,LEN_NOTE,session->host_ip); putuserrec(&scfg,session->user.number,U_LOGONTIME,0,ultoa((ulong)session->logon_time,str,16)); } session->client.user=session->username; client_on(session->socket, &session->client, /* update existing client record? */TRUE); session->last_user_num=session->user.number; } void http_logoff(http_session_t* session, SOCKET socket, int line) { if(session->last_user_num<=0) return; lprintf(LOG_DEBUG,"%04d HTTP Logoff (user #%d) from line %d" ,socket,session->user.number, line); SAFECOPY(session->username,unknown); if(!logoutuserdat(&scfg, &session->user, time(NULL), session->logon_time)) lprintf(LOG_ERR,"%04d !ERROR in logoutuserdat", socket); memset(&session->user,0,sizeof(session->user)); session->last_user_num=session->user.number; } BOOL http_checkuser(http_session_t * session) { if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) { if(session->last_js_user_num==session->user.number) return(TRUE); lprintf(LOG_DEBUG,"%04d JavaScript: Initializing User Objects",session->socket); JS_BEGINREQUEST(session->js_cx); if(session->user.number>0) { if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user, &session->client ,NULL /* ftp index file */, session->subscan /* subscan */)) { JS_ENDREQUEST(session->js_cx); lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket); send_error(session,"500 Error initializing JavaScript User Objects"); return(FALSE); } } else { if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, /* user: */NULL, &session->client ,NULL /* ftp index file */, session->subscan /* subscan */)) { JS_ENDREQUEST(session->js_cx); lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript User Objects",session->socket); send_error(session,"500 Error initializing JavaScript User Objects"); return(FALSE); } } JS_ENDREQUEST(session->js_cx); session->last_js_user_num=session->user.number; } return(TRUE); } static void calculate_digest(http_session_t * session, char *ha1, char *ha2, unsigned char digest[MD5_DIGEST_SIZE]) { MD5 ctx; MD5_open(&ctx); MD5_digest(&ctx, ha1, strlen(ha1)); MD5_digest(&ctx, ":", 1); /* exception on next line (session->req.auth.nonce==NULL) */ MD5_digest(&ctx, session->req.auth.nonce, strlen(session->req.auth.nonce)); MD5_digest(&ctx, ":", 1); if(session->req.auth.qop_value != QOP_NONE) { MD5_digest(&ctx, session->req.auth.nonce_count, strlen(session->req.auth.nonce_count)); MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, session->req.auth.cnonce, strlen(session->req.auth.cnonce)); MD5_digest(&ctx, ":", 1); switch(session->req.auth.qop_value) { case QOP_AUTH: MD5_digest(&ctx, "auth", 4); break; case QOP_AUTH_INT: MD5_digest(&ctx, "auth-int", 7); break; } MD5_digest(&ctx, ":", 1); } MD5_digest(&ctx, ha2, strlen(ha2)); MD5_close(&ctx, digest); } static BOOL check_ars(http_session_t * session) { uchar *ar; BOOL authorized; int i; user_t thisuser; int auth_allowed=0; unsigned *auth_list; unsigned auth_list_len; auth_list=parseEnumList(session->req.auth_list?session->req.auth_list:default_auth_list, ",", auth_type_names, &auth_list_len); for(i=0; ((unsigned)i)<auth_list_len; i++) auth_allowed |= 1<<auth_list[i]; if(auth_list) free(auth_list); /* No authentication provided */ if(session->req.auth.type==AUTHENTICATION_UNKNOWN) { /* No authentication information... */ if(session->last_user_num!=0) { if(session->last_user_num>0) http_logoff(session,session->socket,__LINE__); session->user.number=0; http_logon(session,NULL); } if(!http_checkuser(session)) return(FALSE); if(session->req.ars[0]) { /* There *IS* an ARS string ie: Auth is required */ if(startup->options&WEB_OPT_DEBUG_RX) lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket); return(FALSE); } /* No auth required, allow */ return(TRUE); } /* Require a password */ i=matchuser(&scfg, session->req.auth.username, FALSE); if(i==0) { if(session->last_user_num!=0) { if(session->last_user_num>0) http_logoff(session,session->socket,__LINE__); session->user.number=0; http_logon(session,NULL); } if(!http_checkuser(session)) return(FALSE); if(scfg.sys_misc&SM_ECHO_PW && session->req.auth.type==AUTHENTICATION_BASIC) lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s" ,session->socket,session->req.auth.username,session->req.auth.password); else lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s" ,session->socket,session->req.auth.username); return(FALSE); } thisuser.number=i; getuserdat(&scfg, &thisuser); switch(session->req.auth.type) { case AUTHENTICATION_BASIC: if((auth_allowed & (1<<AUTHENTICATION_BASIC))==0) return(FALSE); if(thisuser.pass[0] && stricmp(thisuser.pass,session->req.auth.password)) { if(session->last_user_num!=0) { if(session->last_user_num>0) http_logoff(session,session->socket,__LINE__); session->user.number=0; http_logon(session,NULL); } if(!http_checkuser(session)) return(FALSE); /* Should go to the hack log? */ if(scfg.sys_misc&SM_ECHO_PW) lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'" ,session->socket,session->req.auth.username,session->req.auth.password,thisuser.pass); else lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s" ,session->socket,session->req.auth.username); #ifdef _WIN32 if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE)) PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME); #endif return(FALSE); } break; case AUTHENTICATION_DIGEST: { unsigned char digest[MD5_DIGEST_SIZE]; char ha1[MD5_DIGEST_SIZE*2+1]; char ha1l[MD5_DIGEST_SIZE*2+1]; char ha1u[MD5_DIGEST_SIZE*2+1]; char ha2[MD5_DIGEST_SIZE*2+1]; char *pass; char *p; time32_t nonce_time; time32_t now; MD5 ctx; if((auth_allowed & (1<<AUTHENTICATION_DIGEST))==0) return(FALSE); if(session->req.auth.qop_value==QOP_UNKNOWN) return(FALSE); if(session->req.auth.algorithm==ALGORITHM_UNKNOWN) return(FALSE); /* Validate rules from RFC-2617 */ if(session->req.auth.qop_value==QOP_AUTH || session->req.auth.qop_value==QOP_AUTH_INT) { if(session->req.auth.cnonce==NULL) return(FALSE); if(session->req.auth.nonce_count==NULL) return(FALSE); } else { if(session->req.auth.cnonce!=NULL) return(FALSE); if(session->req.auth.nonce_count!=NULL) return(FALSE); } /* H(A1) */ MD5_open(&ctx); MD5_digest(&ctx, session->req.auth.username, strlen(session->req.auth.username)); MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name), strlen(session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name))); MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, thisuser.pass, strlen(thisuser.pass)); MD5_close(&ctx, digest); MD5_hex(ha1, digest); /* H(A1)l */ pass=strdup(thisuser.pass); strlwr(pass); MD5_open(&ctx); MD5_digest(&ctx, session->req.auth.username, strlen(session->req.auth.username)); MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name), strlen(session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name))); MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, pass, strlen(pass)); MD5_close(&ctx, digest); MD5_hex(ha1l, digest); /* H(A1)u */ strupr(pass); MD5_open(&ctx); MD5_digest(&ctx, session->req.auth.username, strlen(session->req.auth.username)); MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name), strlen(session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name))); MD5_digest(&ctx, ":", 1); MD5_digest(&ctx, thisuser.pass, strlen(thisuser.pass)); MD5_close(&ctx, digest); MD5_hex(ha1u, digest); free(pass); /* H(A2) */ MD5_open(&ctx); MD5_digest(&ctx, methods[session->req.method], strlen(methods[session->req.method])); MD5_digest(&ctx, ":", 1); /* exception here, session->req.auth.digest_uri==NULL */ MD5_digest(&ctx, session->req.auth.digest_uri, strlen(session->req.auth.digest_uri)); /* TODO QOP==AUTH_INT */ if(session->req.auth.qop_value == QOP_AUTH_INT) return(FALSE); MD5_close(&ctx, digest); MD5_hex(ha2, digest); /* Check password as in user.dat */ calculate_digest(session, ha1, ha2, digest); if(thisuser.pass[0]) { // Zero-length password is "special" (any password will work) if(memcmp(digest, session->req.auth.digest, sizeof(digest))) { /* Check against lower-case password */ calculate_digest(session, ha1l, ha2, digest); if(memcmp(digest, session->req.auth.digest, sizeof(digest))) { /* Check against upper-case password */ calculate_digest(session, ha1u, ha2, digest); if(memcmp(digest, session->req.auth.digest, sizeof(digest))) return(FALSE); } } } /* Validate nonce */ p=strchr(session->req.auth.nonce, '@'); if(p==NULL) { session->req.auth.stale=TRUE; return(FALSE); } *p=0; if(strcmp(session->req.auth.nonce, session->client.addr)) { session->req.auth.stale=TRUE; return(FALSE); } *p='@'; p++; nonce_time=strtoul(p, &p, 10); if(*p) { session->req.auth.stale=TRUE; return(FALSE); } now=(time32_t)time(NULL); if(nonce_time > now) { session->req.auth.stale=TRUE; return(FALSE); } if(nonce_time < now-1800) { session->req.auth.stale=TRUE; return(FALSE); } } } if(i != session->last_user_num) { http_logoff(session,session->socket,__LINE__); session->user.number=i; http_logon(session,&thisuser); } if(!http_checkuser(session)) return(FALSE); if(session->req.ld!=NULL) { FREE_AND_NULL(session->req.ld->user); /* FREE()d in http_logging_thread */ session->req.ld->user=strdup(session->req.auth.username); } ar = arstr(NULL,session->req.ars,&scfg); authorized=chk_ar(&scfg,ar,&session->user,&session->client); if(ar!=NULL && ar!=nular) FREE_AND_NULL(ar); if(authorized) { switch(session->req.auth.type) { case AUTHENTICATION_BASIC: add_env(session,"AUTH_TYPE","Basic"); break; case AUTHENTICATION_DIGEST: add_env(session,"AUTH_TYPE","Digest"); break; } /* Should use real name if set to do so somewhere ToDo */ add_env(session,"REMOTE_USER",session->user.alias); return(TRUE); } /* Should go to the hack log? */ lprintf(LOG_WARNING,"%04d !AUTHORIZATION FAILURE for user %s, ARS: %s" ,session->socket,session->req.auth.username,session->req.ars); #ifdef _WIN32 if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE)) PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME); #endif return(FALSE); } static named_string_t** read_ini_list(char* path, char* section, char* desc ,named_string_t** list) { size_t i; FILE* fp; list=iniFreeNamedStringList(list); if((fp=iniOpenFile(path, /* create? */FALSE))!=NULL) { list=iniReadNamedStringList(fp,section); iniCloseFile(fp); COUNT_LIST_ITEMS(list,i); if(i) lprintf(LOG_DEBUG,"Read %u %s from %s section of %s" ,i,desc,section==NULL ? "root":section,path); } return(list); } static int sockreadline(http_session_t * session, char *buf, size_t length) { char ch; int sel; DWORD i; DWORD chucked=0; fd_set rd_set; struct timeval tv; for(i=0;TRUE;) { if(session->socket==INVALID_SOCKET) return(-1); FD_ZERO(&rd_set); FD_SET(session->socket,&rd_set); /* Convert timeout from ms to sec/usec */ tv.tv_sec=startup->max_inactivity; tv.tv_usec=0; sel=select(session->socket+1,&rd_set,NULL,NULL,&tv); switch(sel) { case 1: break; case -1: close_socket(&session->socket); lprintf(LOG_DEBUG,"%04d !ERROR %d selecting socket for read",session->socket,ERROR_VALUE); return(-1); default: /* Timeout */ lprintf(LOG_NOTICE,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity); return(-1); } switch(recv(session->socket, &ch, 1, 0)) { case -1: if(ERROR_VALUE!=EAGAIN) { if(startup->options&WEB_OPT_DEBUG_RX) lprintf(LOG_DEBUG,"%04d !ERROR %d receiving on socket",session->socket,ERROR_VALUE); close_socket(&session->socket); return(-1); } break; case 0: /* Socket has been closed */ close_socket(&session->socket); return(-1); } if(ch=='\n') break; if(i<length) buf[i++]=ch; else chucked++; } /* Terminate at length if longer */ if(i>length) i=length; while(i>0 && buf[i-1]=='\r') i--; buf[i]=0; if(startup->options&WEB_OPT_DEBUG_RX) { lprintf(LOG_DEBUG,"%04d RX: %s",session->socket,buf); if(chucked) lprintf(LOG_DEBUG,"%04d Long header, chucked %d bytes",session->socket,chucked); } return(i); } #if defined(_WIN32) static int pipereadline(HANDLE pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len) #else static int pipereadline(int pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len) #endif { char ch; DWORD i; int ret=0; #ifndef _WIN32 struct timeval tv={0,0}; fd_set read_set; #endif /* Terminate buffers */ if(buf != NULL) buf[0]=0; if(fullbuf != NULL) fullbuf[0]=0; for(i=0;TRUE;) { #if defined(_WIN32) ret=0; ReadFile(pipe, &ch, 1, (DWORD*)&ret, NULL); #else tv.tv_sec=startup->max_cgi_inactivity; tv.tv_usec=0; FD_ZERO(&read_set); FD_SET(pipe, &read_set); if(select(pipe+1, &read_set, NULL, NULL, &tv)<1) return(-1); ret=read(pipe, &ch, 1); #endif if(ret==1) { if(fullbuf != NULL && i < (fullbuf_len-1)) { fullbuf[i]=ch; fullbuf[i+1]=0; } if(ch=='\n') break; if(buf != NULL && i<length) buf[i]=ch; i++; } else return(-1); } /* Terminate at length if longer */ if(i>length) i=length; if(i>0 && buf != NULL && buf[i-1]=='\r') buf[--i]=0; else { if(buf != NULL) buf[i]=0; } return(i); } int recvbufsocket(SOCKET *sock, char *buf, long count) { int rd=0; int i; time_t start; if(count<1) { errno=ERANGE; return(0); } while(rd<count && socket_check(*sock,NULL,NULL,startup->max_inactivity*1000)) { i=recv(*sock,buf+rd,count-rd,0); switch(i) { case -1: if(ERROR_VALUE!=EAGAIN) close_socket(sock); case 0: close_socket(sock); *buf=0; return(0); } rd+=i; start=time(NULL); } if(rd==count) { return(rd); } *buf=0; return(0); } 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_add_queryval(http_session_t * session, char *key, char *value) { JSObject* keyarray; jsval val; jsuint len; int alen; /* Return existing object if it's already been created */ if(JS_GetProperty(session->js_cx,session->js_query,key,&val) && val!=JSVAL_VOID) { keyarray = JSVAL_TO_OBJECT(val); alen=-1; } else { keyarray = JS_NewArrayObject(session->js_cx, 0, NULL); if(!JS_DefineProperty(session->js_cx, session->js_query, key, OBJECT_TO_JSVAL(keyarray) , NULL, NULL, JSPROP_ENUMERATE)) return; alen=0; } if(alen==-1) { if(JS_GetArrayLength(session->js_cx, keyarray, &len)==JS_FALSE) return; alen=len; } lprintf(LOG_DEBUG,"%04d Adding query value %s=%s at pos %d",session->socket,key,value,alen); val=STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx,value)); JS_SetElement(session->js_cx, keyarray, alen, &val); } static void js_add_cookieval(http_session_t * session, char *key, char *value) { JSObject* keyarray; jsval val; jsuint len; int alen; /* Return existing object if it's already been created */ if(JS_GetProperty(session->js_cx,session->js_cookie,key,&val) && val!=JSVAL_VOID) { keyarray = JSVAL_TO_OBJECT(val); alen=-1; } else { keyarray = JS_NewArrayObject(session->js_cx, 0, NULL); if(!JS_DefineProperty(session->js_cx, session->js_cookie, key, OBJECT_TO_JSVAL(keyarray) , NULL, NULL, JSPROP_ENUMERATE)) return; alen=0; } if(alen==-1) { if(JS_GetArrayLength(session->js_cx, keyarray, &len)==JS_FALSE) return; alen=len; } lprintf(LOG_DEBUG,"%04d Adding cookie value %s=%s at pos %d",session->socket,key,value,alen); val=STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx,value)); JS_SetElement(session->js_cx, keyarray, alen, &val); } static void js_add_request_prop(http_session_t * session, char *key, char *value) { JSString* js_str; if(session->js_cx==NULL || session->js_request==NULL) return; if(key==NULL || value==NULL) return; if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL) return; JS_DefineProperty(session->js_cx, session->js_request, 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; char *lckey; if((lckey=(char *)alloca(strlen(key)+1))==NULL) return; strcpy(lckey,key); strlwr(lckey); if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL) { return; } JS_DefineProperty(session->js_cx, session->js_header, lckey, STRING_TO_JSVAL(js_str) ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY); } #if 0 static void js_parse_multipart(http_session_t * session, char *p) { size_t key_len; size_t value_len; char *lp; char *key; char *value; if(p == NULL) return; lp=p; while((key_len=strcspn(lp,"="))!=0) { key=lp; lp+=key_len; if(*lp) { *lp=0; lp++; } value_len=strcspn(lp,"&"); value=lp; lp+=value_len; if(*lp) { *lp=0; lp++; } unescape(value); unescape(key); js_add_queryval(session, key, value); } } #endif static void js_parse_query(http_session_t * session, char *p) { size_t key_len; size_t value_len; char *lp; char *key; char *value; if(p == NULL) return; lp=p; while((key_len=strcspn(lp,"="))!=0) { key=lp; lp+=key_len; if(*lp) { *lp=0; lp++; } value_len=strcspn(lp,"&"); value=lp; lp+=value_len; if(*lp) { *lp=0; lp++; } unescape(value); unescape(key); js_add_queryval(session, key, value); } } static char *get_token_value(char **p) { char *pos=*p; char *start; char *out; BOOL escaped=FALSE; start=pos; out=start; if(*pos=='"') { for(pos++; *pos; pos++) { if(escaped && *pos) *(out++)=*pos; else if(*pos=='"') { pos++; break; } else if(*pos=='\\') escaped=TRUE; else *(out++)=*pos; } } else { for(; *pos; pos++) { if(iscntrl(*pos)) break; switch(*pos) { case 0: case '(': case ')': case '<': case '>': case '@': case ',': case ';': case ':': case '\\': case '"': case '/': case '[': case ']': case '?': case '=': case '{': case '}': case ' ': case '\t': goto end_of_text; } *(out++)=*pos; } } end_of_text: while(*pos==',' || isspace(*pos)) pos++; *out=0; *p=pos; return(start); } static int hexval(unsigned char ch) { ch-='0'; if(ch<10) return(ch); ch-=7; if(ch<16 && ch>9) return(ch); if(ch>41) { ch-=32; if(ch<16 && ch>9) return(ch); } return(0); } static BOOL parse_headers(http_session_t * session) { char *head_line; char *value; char *tvalue; char *last; char *p; int i; size_t idx; size_t content_len=0; char env_name[128]; for(idx=0;session->req.headers[idx]!=NULL;idx++) { /* TODO: strdup() is possibly too slow here... */ head_line=strdup(session->req.headers[idx]); 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_AUTH: if((p=strtok_r(value," ",&last))!=NULL) { if(stricmp(p, "Basic")==0) { p=strtok_r(NULL," ",&last); if(p==NULL) break; while(*p && *p<' ') p++; b64_decode(p,strlen(p),p,strlen(p)); p=strtok_r(p,":",&last); if(p) { if(strlen(p) >= sizeof(session->req.auth.username)) break; SAFECOPY(session->req.auth.username, p); p=strtok_r(NULL,":",&last); if(p) { if(strlen(p) >= sizeof(session->req.auth.password)) break; SAFECOPY(session->req.auth.password, p); session->req.auth.type=AUTHENTICATION_BASIC; } } } else if(stricmp(p, "Digest")==0) { p=strtok_r(NULL, "", &last); /* Defaults */ session->req.auth.algorithm=ALGORITHM_MD5; session->req.auth.type=AUTHENTICATION_DIGEST; /* Parse out values one at a time and store */ while(*p) { while(isspace(*p)) p++; if(strnicmp(p,"username=",9)==0) { p+=9; tvalue=get_token_value(&p); if(strlen(tvalue) >= sizeof(session->req.auth.username)) break; SAFECOPY(session->req.auth.username, tvalue); } else if(strnicmp(p,"realm=",6)==0) { p+=6; session->req.auth.realm=strdup(get_token_value(&p)); } else if(strnicmp(p,"nonce=",6)==0) { p+=6; session->req.auth.nonce=strdup(get_token_value(&p)); } else if(strnicmp(p,"uri=",4)==0) { p+=4; session->req.auth.digest_uri=strdup(get_token_value(&p)); } else if(strnicmp(p,"response=",9)==0) { p+=9; tvalue=get_token_value(&p); if(strlen(tvalue)==32) { for(i=0; i<16; i++) { session->req.auth.digest[i]=hexval(tvalue[i*2])<<4 | hexval(tvalue[i*2+1]); } } } else if(strnicmp(p,"algorithm=",10)==0) { p+=10; tvalue=get_token_value(&p); if(stricmp(tvalue,"MD5")==0) { session->req.auth.algorithm=ALGORITHM_MD5; } else { session->req.auth.algorithm=ALGORITHM_UNKNOWN; } } else if(strnicmp(p,"cnonce=",7)==0) { p+=7; session->req.auth.cnonce=strdup(get_token_value(&p)); } else if(strnicmp(p,"qop=",4)==0) { p+=4; tvalue=get_token_value(&p); if(stricmp(tvalue,"auth")==0) { session->req.auth.qop_value=QOP_AUTH; } else if (stricmp(tvalue,"auth-int")==0) { session->req.auth.qop_value=QOP_AUTH_INT; } else { session->req.auth.qop_value=QOP_UNKNOWN; } } else if(strnicmp(p,"nc=",3)==0) { p+=3; session->req.auth.nonce_count=strdup(get_token_value(&p)); } else { while(*p && *p != '=') p++; if(*p == '=') get_token_value(&p); } } if(session->req.auth.digest_uri==NULL) session->req.auth.digest_uri=strdup(session->req.request_line); /* Validate that we have the required values... */ switch(session->req.auth.qop_value) { case QOP_NONE: if(session->req.auth.realm==NULL || session->req.auth.nonce==NULL || session->req.auth.digest_uri==NULL) send_error(session,"400 Bad Request"); break; case QOP_AUTH: case QOP_AUTH_INT: if(session->req.auth.realm==NULL || session->req.auth.nonce==NULL || session->req.auth.nonce_count==NULL || session->req.auth.cnonce==NULL || session->req.auth.digest_uri==NULL) send_error(session,"400 Bad Request"); break; default: send_error(session,"400 Bad Request"); break; } } } break; case HEAD_LENGTH: add_env(session,"CONTENT_LENGTH",value); content_len=strtol(value,NULL,10); 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; } break; case HEAD_REFERER: if(session->req.ld!=NULL) { FREE_AND_NULL(session->req.ld->referrer); /* FREE()d in http_logging_thread() */ session->req.ld->referrer=strdup(value); } break; case HEAD_AGENT: if(session->req.ld!=NULL) { FREE_AND_NULL(session->req.ld->agent); /* FREE()d in http_logging_thread() */ session->req.ld->agent=strdup(value); } break; case HEAD_TRANSFER_ENCODING: if(!stricmp(value,"chunked")) session->req.read_chunked=TRUE; else send_error(session,"501 Not Implemented"); break; case HEAD_RANGE: if(!stricmp(value,"bytes=")) { send_error(session,error_416); break; } value+=6; if(strchr(value,',')!=NULL) { /* We don't do multiple ranges yet - TODO */ send_error(session,error_416); break; } /* Check for offset from end. */ if(*value=='-') { session->req.range_start=strtol(value,NULL,10); session->req.range_end=-1; break; } if((p=strtok_r(value,"-",&last))!=NULL) { session->req.range_start=strtol(p,NULL,10); if((p=strtok_r(NULL,"-",&last))!=NULL) session->req.range_end=strtol(p,NULL,10); else session->req.range_end=-1; } else { send_error(session,error_416); break; } break; case HEAD_IFRANGE: session->req.if_range=decode_date(value); break; case HEAD_TYPE: add_env(session,"CONTENT_TYPE",value); break; default: break; } sprintf(env_name,"HTTP_%s",head_line); add_env(session,env_name,value); } free(head_line); } if(content_len) session->req.post_len = content_len; add_env(session,"SERVER_NAME",session->req.host[0] ? session->req.host : startup->host_name ); return TRUE; } static BOOL parse_js_headers(http_session_t * session) { char *head_line; char *value; char *last; char *p; int i; size_t idx; for(idx=0;session->req.headers[idx]!=NULL;idx++) { head_line=session->req.headers[idx]; if((strtok_r(head_line,":",&last))!=NULL && (value=strtok_r(NULL,"",&last))!=NULL) { i=get_header_type(head_line); while(*value && *value<=' ') value++; js_add_header(session,head_line,value); switch(i) { case HEAD_TYPE: if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) { /* * We need to parse out the files based on RFC1867 * * And example reponse looks like this: * Content-type: multipart/form-data, boundary=AaB03x * * --AaB03x * content-disposition: form-data; name="field1" * * Joe Blow * --AaB03x * content-disposition: form-data; name="pics" * Content-type: multipart/mixed, boundary=BbC04y * * --BbC04y * Content-disposition: attachment; filename="file1.txt" * * Content-Type: text/plain * * ... contents of file1.txt ... * --BbC04y * Content-disposition: attachment; filename="file2.gif" * Content-type: image/gif * Content-Transfer-Encoding: binary * * ...contents of file2.gif... * --BbC04y-- * --AaB03x-- */ } break; case HEAD_COOKIE: if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) { char *key; char *val; p=value; while((key=strtok_r(p,"=",&last))!=NULL) { while(isspace(*key)) key++; p=NULL; if((val=strtok_r(p,";\t\n\v\f\r ",&last))!=NULL) { /* Whitespace */ js_add_cookieval(session,key,val); } } } break; 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) { int i=0; char drive[4]; char cgidrive[4]; char dir[MAX_PATH+1]; char cgidir[MAX_PATH+1]; 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_DEBUG,"%04d Setting up JavaScript support", session->socket); if(!js_setup(session)) { lprintf(LOG_ERR,"%04d !ERROR setting up JavaScript support", session->socket); send_error(session,error_500); return(IS_STATIC); } return(i); } 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); return(IS_CGI); } } _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); } } return(IS_STATIC); } static char *get_request(http_session_t * session, char *req_line) { char* p; char* query; char* retval; char* last; int offset; SKIP_WHITESPACE(req_line); 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)) { /* Remove http:// from start of physical_path */ memmove(session->req.physical_path, session->req.physical_path+http_scheme_len, strlen(session->req.physical_path+http_scheme_len)+1); /* Set HOST value... ignore HOST header */ SAFECOPY(session->req.host,session->req.physical_path); /* Remove path if present (everything after the first /) */ strtok_r(session->req.host,"/",&last); /* Set vhost value to host value */ SAFECOPY(session->req.vhost,session->req.host); /* Remove port specification from vhost (if present) */ strtok_r(session->req.vhost,":",&last); /* Sets p to point to the first character after the first slash */ p=strchr(session->req.physical_path, '/'); /* * If we have a slash, make it the first char in the string. * otherwise, set path to "/" */ if(p==NULL) { strcpy(session->req.physical_path, "/"); } else { 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 */ ); } } if(query!=NULL) SAFECOPY(session->req.query_str,query); return(retval); } 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); } } if(req_line!=NULL && *req_line>=' ') send_error(session,"501 Not Implemented"); return(NULL); } static BOOL get_request_headers(http_session_t * session) { char head_line[MAX_REQUEST_LINE+1]; char next_char; char *value; char *last; int i; while(sockreadline(session,head_line,sizeof(head_line)-1)>0) { /* Multi-line headers */ while((i=recv(session->socket,&next_char,1,MSG_PEEK))>0 && (next_char=='\t' || next_char==' ')) { if(i==-1 && ERROR_VALUE != EAGAIN) 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); } break; default: break; } } } if(!(session->req.vhost[0])) 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); if(isdir(str)) 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) return(FALSE); return(isabspath(session->req.physical_path)); } static BOOL get_req(http_session_t * session, char *request_line) { char req_line[MAX_REQUEST_LINE+1]; char * p; int is_redir=0; int len; 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) return(FALSE); if(req_line[0]) lprintf(LOG_INFO,"%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); } else { lprintf(LOG_DEBUG,"%04d Handling Internal Redirect to: %s",session->socket,request_line); SAFECOPY(req_line,request_line); is_redir=1; } if(req_line[0]) { 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); return(FALSE); } 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); if(session->req.query_str[0]) 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"); 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 *rp_slash; char *vp_slash; char rpath[MAX_PATH+1]; char vpath[MAX_PATH+1]; char epath[MAX_PATH+1]; char str[MAX_PATH+1]; struct stat sb; int i; char *end; epath[0]=0; epath[1]=0; 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(session->req.path_info_index) { 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)) { 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); } } else { if(isdir(rpath)) 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); } } } } return(FALSE); } static BOOL check_request(http_session_t * session) { char path[MAX_PATH+1]; char curdir[MAX_PATH+1]; char str[MAX_PATH+1]; char last_ch; char* last_slash; char* p; FILE* file; int i; struct stat sb; int send404=0; char filename[MAX_PATH+1]; char *spec; str_list_t specs; BOOL recheck_dynamic=FALSE; if(session->req.finished) return(FALSE); SAFECOPY(path,session->req.physical_path); if(startup->options&WEB_OPT_DEBUG_TX) lprintf(LOG_DEBUG,"%04d Path is: %s",session->socket,path); if(isdir(path)) { last_ch=*lastchar(path); if(!IS_PATH_DELIM(last_ch)) { 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)) { session->req.send_location=MOVED_PERM; strcat(session->req.virtual_path,"/"); } last_slash=find_last_slash(path); if(last_slash==NULL) { send_error(session,error_500); return(FALSE); } last_slash++; 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]); if(startup->options&WEB_OPT_DEBUG_TX) lprintf(LOG_DEBUG,"%04d Checking for %s",session->socket,path); if(!stat(path,&sb)) 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))) { session->req.keep_alive=FALSE; 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); return(FALSE); } /* Set default ARS to a 0-length string */ session->req.ars[0]=0; /* Walk up from root_dir checking for access.ars and webctrl.ini */ SAFECOPY(curdir,path); last_slash=curdir+strlen(root_dir)-1; /* Loop while there's more /s in path*/ p=last_slash; while((last_slash=find_first_slash(p+1))!=NULL) { p=last_slash; /* Terminate the path after the slash */ *(last_slash+1)=0; 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) { 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); if(!stat(str,&sb)) { /* NEVER serve up a webctrl.ini file */ if(!strcmp(path,str)) { send_error(session,"403 Forbidden"); return(FALSE); } /* Read webctrl.ini file */ if((file=fopen(str,"r"))!=NULL) { /* 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); 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, "DigestRealm", scfg.sys_name,str)==str) { FREE_AND_NULL(session->req.digest_realm); /* FREE()d in close_request() */ session->req.digest_realm=strdup(str); } if(iniReadString(file, NULL, "ErrorDirectory", error_dir,str)==str) { prep_dir(root_dir, str, sizeof(str)); 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)); FREE_AND_NULL(session->req.cgi_dir); /* FREE()d in close_request() */ session->req.cgi_dir=strdup(str); recheck_dynamic=TRUE; } if(iniReadString(file, NULL, "Authentication", default_auth_list,str)==str) { FREE_AND_NULL(session->req.auth_list); /* FREE()d in close_request() */ session->req.auth_list=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, "DigestRealm", scfg.sys_name,str)==str) { FREE_AND_NULL(session->req.digest_realm); /* FREE()d in close_request() */ session->req.digest_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); recheck_dynamic=TRUE; } if(iniReadString(file, spec, "Authentication", default_auth_list,str)==str) { FREE_AND_NULL(session->req.auth_list); /* FREE()d in close_request() */ session->req.auth_list=strdup(str); } session->req.path_info_index=iniReadBool(file, spec, "PathInfoIndex", FALSE); } free(spec); } iniFreeStringList(specs); fclose(file); if(session->req.path_info_index) recheck_dynamic=TRUE; } else { /* 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); } if(recheck_dynamic) { 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]) send404=TRUE; if(!check_ars(session)) { unsigned *auth_list; unsigned auth_list_len; /* No authentication provided */ strcpy(str,"401 Unauthorized"); auth_list=parseEnumList(session->req.auth_list?session->req.auth_list:default_auth_list, ",", auth_type_names, &auth_list_len); for(i=0; ((unsigned)i)<auth_list_len; i++) { p=strchr(str,0); switch(auth_list[i]) { case AUTHENTICATION_BASIC: snprintf(p,sizeof(str)-(p-str),"%s%s: Basic realm=\"%s\"" ,newline,get_header(HEAD_WWWAUTH),session->req.realm?session->req.realm:scfg.sys_name); str[sizeof(str)-1]=0; break; case AUTHENTICATION_DIGEST: snprintf(p,sizeof(str)-(p-str),"%s%s: Digest realm=\"%s\", nonce=\"%s@%u\", qop=\"auth\"%s" ,newline,get_header(HEAD_WWWAUTH),session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name),session->client.addr,time(NULL),session->req.auth.stale?", stale=true":""); str[sizeof(str)-1]=0; break; } } if(auth_list) free(auth_list); 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) 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); } return(TRUE); } 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]; size_t i; str_list_t env_list; str_list_t add_list; /* Return value */ if((env_list=strListInit())==NULL) return(NULL); strListAppendList(&env_list, session->req.cgi_env); /* FREE()d in this block */ if((add_list=iniGetSectionList(cgi_env,NULL))!=NULL) { for(i=0; add_list[i]!=NULL; i++) { if((deflt=getenv(add_list[i]))==NULL) deflt=iniGetString(cgi_env,add_list[i],"default",NULL,defltbuf); if(iniGetString(cgi_env,add_list[i],"value",deflt,value)==NULL) continue; iniGetString(cgi_env,add_list[i],"append","",append); iniGetString(cgi_env,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); } iniFreeStringList(add_list); } return(env_list); } static BOOL exec_cgi(http_session_t *session) { #ifdef __unix__ char cmdline[MAX_PATH+256]; /* ToDo: Damn, that's WAY too many variables */ int i=0; int status=0; pid_t child=0; int out_pipe[2]; int err_pipe[2]; struct timeval tv={0,0}; fd_set read_set; fd_set write_set; int high_fd=0; char buf[1024]; 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; char *last; BOOL done_wait=FALSE; BOOL got_valid_headers=FALSE; time_t start; char cgipath[MAX_PATH+1]; char *p; BOOL orig_keep=FALSE; str_list_t tmpbuf; BOOL no_chunked=FALSE; BOOL set_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; /* Set up I/O pipes */ if(pipe(out_pipe)!=0) { lprintf(LOG_ERR,"%04d Can't create out_pipe",session->socket); return(FALSE); } if(pipe(err_pipe)!=0) { lprintf(LOG_ERR,"%04d Can't create err_pipe",session->socket); return(FALSE); } if((child=fork())==0) { str_list_t env_list; /* Do a full suid thing. */ if(startup->setuid!=NULL) startup->setuid(TRUE); env_list=get_cgi_env(session); /* Set up STDIO */ 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 */ if((p=get_cgi_handler(cmdline))!=NULL) { char* shell=os_cmdshell(); lprintf(LOG_INFO,"%04d Using handler %s to execute %s",session->socket,p,cmdline); execle(shell,shell,"-c",p,cmdline,NULL,env_list); } else { execle(cmdline,cmdline,NULL,env_list); } lprintf(LOG_ERR,"%04d !FAILED! execle() (%d)",session->socket,errno); exit(EXIT_FAILURE); /* Should never happen */ } 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 */ 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; /* FREE()d following this block */ 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) { int snt=0; start=time(NULL); if(session->req.method!=HTTP_HEAD) { snt=writebuf(session,buf,i); if(session->req.ld!=NULL) { session->req.ld->size+=snt; } } } else done_reading=TRUE; } else { /* This is the tricky part */ i=pipereadline(out_pipe[0],buf,sizeof(buf), fbuf, sizeof(fbuf)); if(i==-1) { done_reading=TRUE; got_valid_headers=FALSE; } else start=time(NULL); if(!done_parsing_headers && *buf) { if(tmpbuf != NULL) strListPush(&tmpbuf, fbuf); SAFECOPY(header,buf); directive=strtok_r(header,":",&last); if(directive != NULL) { 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) SAFECOPY(cgi_status,error_302); } else { SAFECOPY(session->req.virtual_path,value); session->req.send_location=MOVED_TEMP; 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); no_chunked=TRUE; break; case HEAD_TYPE: got_valid_headers=TRUE; strListPush(&session->req.dynamic_heads,buf); break; case HEAD_TRANSFER_ENCODING: no_chunked=TRUE; break; default: 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; set_chunked=TRUE; } 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 */ char content_type[MAX_REQUEST_LINE+1]; int snt; lprintf(LOG_DEBUG,"%04d Recieved invalid CGI headers, sending result as plain-text",session->socket); /* 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); /* 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)); if(session->req.ld!=NULL && snt>0) { session->req.ld->size+=snt; } } got_valid_headers=TRUE; } done_parsing_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); start=time(NULL); } } 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)); done_reading=TRUE; start=0; } } } 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 */ tv.tv_sec=1; tv.tv_usec=0; FD_ZERO(&read_set); FD_SET(err_pipe[0],&read_set); FD_SET(out_pipe[0],&read_set); while(select(high_fd+1,&read_set,NULL,NULL,&tv)>0) { if(FD_ISSET(err_pipe[0],&read_set)) { i=read(err_pipe[0],buf,sizeof(buf)-1); if(i!=-1 && i!=0) { buf[i]=0; lprintf(LOG_ERR,"%04d CGI Error: %s",session->socket,buf); start=time(NULL); } } if(FD_ISSET(out_pipe[0],&read_set)) { i=read(out_pipe[0],buf,sizeof(buf)); if(i!=-1 && i!=0) { int snt=0; start=time(NULL); if(session->req.method!=HTTP_HEAD) { snt=writebuf(session,buf,i); if(session->req.ld!=NULL) { session->req.ld->size+=snt; } } } } if(i==0 || i==-1) break; tv.tv_sec=1; tv.tv_usec=0; FD_ZERO(&read_set); FD_SET(err_pipe[0],&read_set); FD_SET(out_pipe[0],&read_set); } close(out_pipe[0]); /* close read-end of pipe */ close(err_pipe[0]); /* close read-end of pipe */ if(!got_valid_headers) { 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)); return(FALSE); } return(TRUE); #else /* Win32 exec_cgi() */ /* These are (more or less) copied from the Unix version */ char* p; char *last; 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; BOOL no_chunked=FALSE; int set_chunked=FALSE; /* Win32-specific */ char* env_block; char startup_dir[MAX_PATH+1]; int wr; BOOL rd; HANDLE rdpipe=INVALID_HANDLE_VALUE; HANDLE wrpipe=INVALID_HANDLE_VALUE; HANDLE rdoutpipe; HANDLE wrinpipe; DWORD waiting; DWORD msglen; DWORD retval; BOOL success; BOOL process_terminated=FALSE; PROCESS_INFORMATION process_info; SECURITY_ATTRIBUTES sa; STARTUPINFO startup_info={0}; str_list_t env_list; startup_info.cb=sizeof(startup_info); startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; startup_info.wShowWindow=SW_HIDE; SAFECOPY(startup_dir,session->req.physical_path); if((p=strrchr(startup_dir,'/'))!=NULL || (p=strrchr(startup_dir,'\\'))!=NULL) *p=0; else SAFECOPY(startup_dir,session->req.cgi_dir?session->req.cgi_dir:cgi_dir); lprintf(LOG_DEBUG,"%04d CGI startup dir: %s", session->socket, startup_dir); if((p=get_cgi_handler(session->req.physical_path))!=NULL) SAFEPRINTF2(cmdline,"%s %s",p,session->req.physical_path); else 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; 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(WaitForSingleObject(process_info.hProcess,0)==WAIT_OBJECT_0) process_terminated=TRUE; /* handle remaining data in pipe before breaking */ if((time(NULL)-start) >= startup->max_cgi_inactivity) { lprintf(LOG_WARNING,"%04d CGI Process %s timed out after %u seconds of inactivity" ,session->socket,getfname(cmdline),startup->max_cgi_inactivity); break; } /* Check socket for received POST Data */ if(!socket_check(session->socket, &rd, NULL, /* timeout: */0)) { lprintf(LOG_WARNING,"%04d CGI Socket disconnected", session->socket); break; } if(rd) { /* Send received POST Data to stdin of CGI process */ if((i=recv(session->socket, buf, sizeof(buf), 0)) > 0) { lprintf(LOG_DEBUG,"%04d CGI Received %d bytes of POST data" ,session->socket, i); WriteFile(wrpipe, buf, i, &wr, /* Overlapped: */NULL); } } 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) { if(process_terminated) break; Sleep(1); continue; } /* 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; } } else { /* This is the tricky part */ buf[0]=0; i=pipereadline(rdpipe,buf,sizeof(buf),NULL,0); if(i<0) { lprintf(LOG_WARNING,"%04d CGI pipereadline returned %d",session->socket,i); got_valid_headers=FALSE; break; } lprintf(LOG_DEBUG,"%04d CGI header line: %s" ,session->socket, buf); SAFECOPY(header,buf); if(strchr(header,':')!=NULL) { if((directive=strtok_r(header,":",&last))!=NULL) value=strtok_r(NULL,"",&last); else value=""; 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_TEMP; 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); no_chunked=TRUE; break; case HEAD_TYPE: got_valid_headers=TRUE; SAFECOPY(content_type,buf); break; case HEAD_TRANSFER_ENCODING: no_chunked=TRUE; break; default: strListPush(&session->req.dynamic_heads,buf); } continue; } 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; if(!no_chunked && session->http_ver>=HTTP_1_1) { session->req.keep_alive=orig_keep; set_chunked=TRUE; } strListPush(&session->req.dynamic_heads,content_type); send_headers(session,cgi_status,set_chunked); } if(msglen) { lprintf(LOG_DEBUG,"%04d Sending %d bytes: %.*s" ,session->socket,msglen,msglen,buf); wr=writebuf(session,buf,msglen); /* log actual bytes sent */ if(session->req.ld!=NULL && wr>0) session->req.ld->size+=wr; } } if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE) lprintf(LOG_ERR,"%04d !ERROR GetExitCodeProcess(%s) returned %d" ,session->socket,getfname(cmdline),GetLastError()); if(retval==STILL_ACTIVE) { 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); if(!got_valid_headers) lprintf(LOG_WARNING,"%04d !CGI Process %s did not generate valid headers" ,session->socket,getfname(cmdline)); if(!done_parsing_headers) lprintf(LOG_WARNING,"%04d !CGI Process %s did not send data header termination" ,session->socket,getfname(cmdline)); return(TRUE); #endif } /********************/ /* 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) { /* JSObject* cookie; */ 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); } 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); /* 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); } 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); } else session->js_header = JS_DefineObject(cx, session->js_request, "header", NULL , NULL, JSPROP_ENUMERATE|JSPROP_READONLY); /* Return existing object if it's already been created */ if(JS_GetProperty(cx,session->js_request,"cookie",&val) && val!=JSVAL_VOID) { session->js_cookie = JSVAL_TO_OBJECT(val); JS_ClearScope(cx,session->js_cookie); } else session->js_cookie = JS_DefineObject(cx, session->js_request, "cookie", 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; int log_level; 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) SAFEPRINTF(file," %s",report->filename); else file[0]=0; if(report->lineno) SAFEPRINTF(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"; log_level=LOG_WARNING; } else { log_level=LOG_ERR; warning=""; } lprintf(log_level,"%04d !JavaScript %s%s%s: %s, Request: %s" ,session->socket,warning,file,line,message, session->req.request_line); if(session->req.fp!=NULL) fprintf(session->req.fp,"!JavaScript %s%s%s: %s",warning,file,line,message); } static void js_writebuf(http_session_t *session, const char *buf, size_t buflen) { if(session->req.sent_headers) { if(session->req.method!=HTTP_HEAD && session->req.method!=HTTP_OPTIONS) writebuf(session,buf,buflen); } else fwrite(buf,1,buflen,session->req.fp); } static JSBool js_writefunc(JSContext *cx, uintN argc, jsval *arglist, BOOL writeln) { jsval *argv=JS_ARGV(cx, arglist); uintN i; JSString* str=NULL; http_session_t* session; jsrefcount rc; char *cstr; size_t len; 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)) { if(session->http_ver>=HTTP_1_1 && session->req.keep_alive) { rc=JS_SUSPENDREQUEST(cx); if(!ssjs_send_headers(session,TRUE)) { JS_RESUMEREQUEST(cx, rc); return(JS_FALSE); } JS_RESUMEREQUEST(cx, rc); } else { /* "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; rc=JS_SUSPENDREQUEST(cx); if(!ssjs_send_headers(session,FALSE)) { JS_RESUMEREQUEST(cx, rc); return(JS_FALSE); } JS_RESUMEREQUEST(cx, rc); } } } session->req.prev_write=TRUE; for(i=0; i<argc; i++) { if((str=JS_ValueToString(cx, argv[i]))==NULL) continue; len=JS_GetStringLength(str); JSSTRING_TO_STRING(cx, str, cstr, NULL); rc=JS_SUSPENDREQUEST(cx); js_writebuf(session, cstr, len); if(writeln) js_writebuf(session, newline, 2); JS_RESUMEREQUEST(cx, rc); } if(str==NULL) JS_SET_RVAL(cx,arglist,JSVAL_VOID); else JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str)); return(JS_TRUE); } static JSBool js_write(JSContext *cx, uintN argc, jsval *arglist) { js_writefunc(cx, argc, arglist, FALSE); return(JS_TRUE); } static JSBool js_writeln(JSContext *cx, uintN argc, jsval *arglist) { js_writefunc(cx, argc, arglist,TRUE); return(JS_TRUE); } static JSBool js_set_cookie(JSContext *cx, uintN argc, jsval *arglist) { jsval *argv=JS_ARGV(cx, arglist); char header_buf[8192]; char *header; char *p; int32 i; JSBool b; struct tm tm; http_session_t* session; time_t tt; JS_SET_RVAL(cx, arglist, JSVAL_VOID); if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return(JS_FALSE); if(argc<2) return(JS_FALSE); header=header_buf; JSVALUE_TO_STRING(cx, argv[0], p, NULL); if(!p) return(JS_FALSE); header+=sprintf(header,"Set-Cookie: %s=",p); JSVALUE_TO_STRING(cx, argv[1], p, NULL); if(!p) return(JS_FALSE); header+=sprintf(header,"%s",p); if(argc>2) { if(!JS_ValueToInt32(cx,argv[2],&i)) return JS_FALSE; tt=i; if(i && gmtime_r(&tt,&tm)!=NULL) header += strftime(header,50,"; expires=%a, %d-%b-%Y %H:%M:%S GMT",&tm); } if(argc>3) { JSVALUE_TO_STRING(cx, argv[3], p, NULL); if(p!=NULL && *p) header += sprintf(header,"; domain=%s",p); } if(argc>4) { JSVALUE_TO_STRING(cx, argv[4], p, NULL); if(p!=NULL && *p) header += sprintf(header,"; path=%s",p); } if(argc>5) { JS_ValueToBoolean(cx, argv[5], &b); if(b) header += sprintf(header,"; secure"); } strListPush(&session->req.dynamic_heads,header_buf); return(JS_TRUE); } static JSBool js_log(JSContext *cx, uintN argc, jsval *arglist) { jsval *argv=JS_ARGV(cx, arglist); char str[512]; uintN i=0; int32 level=LOG_INFO; http_session_t* session; jsrefcount rc; char *val; JS_SET_RVAL(cx, arglist, JSVAL_VOID); if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return(JS_FALSE); if(startup==NULL || startup->lputs==NULL) return(JS_FALSE); if(argc > 1 && JSVAL_IS_NUMBER(argv[i])) { if(!JS_ValueToInt32(cx,argv[i++],&level)) return JS_FALSE; } str[0]=0; for(;i<argc && strlen(str)<(sizeof(str)/2);i++) { JSVALUE_TO_STRING(cx, argv[i], val, NULL); if(val==NULL) return(JS_FALSE); strncat(str,val,sizeof(str)/2); strcat(str," "); } rc=JS_SUSPENDREQUEST(cx); lprintf(level,"%04d %s",session->socket,str); JS_RESUMEREQUEST(cx, rc); JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str))); return(JS_TRUE); } static JSBool js_login(JSContext *cx, uintN argc, jsval *arglist) { jsval *argv=JS_ARGV(cx, arglist); char* p; JSBool inc_logons=JS_FALSE; user_t user; http_session_t* session; jsrefcount rc; JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(JS_FALSE)); if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return(JS_FALSE); /* User name */ JSVALUE_TO_STRING(cx, argv[0], p, NULL); if(p==NULL) return(JS_FALSE); rc=JS_SUSPENDREQUEST(cx); 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); JS_RESUMEREQUEST(cx, rc); return(JS_TRUE); } if(user.misc&(DELETED|INACTIVE)) { lprintf(LOG_WARNING,"%04d !DELETED OR INACTIVE USER #%d: %s" ,session->socket,user.number,p); JS_RESUMEREQUEST(cx, rc); return(JS_TRUE); } JS_RESUMEREQUEST(cx, rc); /* Password */ if(user.pass[0]) { JSVALUE_TO_STRING(cx, argv[1], p, NULL); if(p==NULL) return(JS_FALSE); if(stricmp(user.pass,p)) { /* Wrong password */ rc=JS_SUSPENDREQUEST(cx); lprintf(LOG_WARNING,"%04d !INVALID PASSWORD ATTEMPT FOR USER: %s" ,session->socket,user.alias); JS_RESUMEREQUEST(cx, rc); return(JS_TRUE); } } if(argc>2) JS_ValueToBoolean(cx,argv[2],&inc_logons); rc=JS_SUSPENDREQUEST(cx); if(inc_logons) { user.logons++; user.ltoday++; } http_logon(session, &user); JS_RESUMEREQUEST(cx, rc); /* user-specific objects */ if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user, &session->client ,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); } JS_SET_RVAL(cx, arglist,BOOLEAN_TO_JSVAL(JS_TRUE)); return(JS_TRUE); } #if 0 static char *find_next_pair(char *buffer, size_t buflen, char find) { char *p; char *search; char *end; size_t buflen2; char chars[5]="@%^<"; end=buffer+buflen; search=buffer; buflen2=buflen; for(;search<end;) { p=memchr(search, chars[i], buflen2); /* Can't even find one... there's definatly no pair */ if(p==NULL) return(NULL); if(*(p+1)==find) return(p); /* Next search pos is at the char after the match */ search=p+1; buflen2=end-search; } } static void js_write_escaped(JSContext *cx, JSObject *obj, char *pos, size_t len, char *name_end, char *repeat_section) { char *name=pos+2; } enum { T_AT ,T_PERCENT ,T_CARET ,T_LT }; static int js_write_template_part(JSContext *cx, JSObject *obj, char *template, size_t len, char *repeat_section) { size_t len2; char *pos; char *end; char *p; char *p2; char *send_end; int no_more[4]; char *next[4]; int i,j; char chars[5]="@%^<"; end=template+len; pos=template; memset(&next,0,sizeof(next)); memset(&no_more,0,sizeof(no_more)); while(pos<end) { send_end=NULL; /* Find next seperator */ for(i=0; i<4; i++) { if(!no_more[i]) { if(next[i] < pos) next[i]=NULL; if(next[i] == NULL) { if((next[i]=find_next_pair(pos, len, chars[i]))==NULL) { no_more[i]=TRUE; continue; } } if(!send_end || next[i] < send_end) send_end=next[i]; } } if(send_end==NULL) { /* Nothing else matched... we're good now! */ js_writebuf(session, pos, len); pos=end; len=0; continue; } if(send_end > pos) { i=send_end-pos; js_writebuf(session, pos, i); pos+=i; len-=i; } /* * At this point, pos points to a matched introducer. * If it's not a repeat section, we can just output it here. */ if(*pos != '<') { /* * If there is no corresponding terminator to this introdcer, * force it to be output unchanged. */ if((p=find_next_pair(pos, len, *pos))==NULL) { no_more[strchr(chars,*pos)-char]=TRUE; continue; } js_write_escaped(cx, obj, pos, len, p, repeat_section); continue; } /* * Pos is the start of a repeat section now... this is where things * start to get tricky. Set up RepeatObj object, then call self * once for each repeat. */ } } static JSBool js_write_template(JSContext *cx, uintN argc, jsval *arglist) { JSObject *obj=JS_THIS_OBJECT(cx, arglist); jsval *argv=JS_ARGV(cx, arglist); JSString* js_str; char *filename; char *template; FILE *tfile; size_t len; http_session_t* session; JS_SET_RVAL(cx, arglist, JSVAL_VOID); if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return(JS_FALSE); if(session->req.fp==NULL) return(JS_FALSE); JSVALUE_TO_STRING(cx, argv[0], filename, NULL); if(filename==NULL) return(JS_FALSE); if(!fexist(filename)) { JS_ReportError(cx, "Template file %s does not exist.", filename); return(JS_FALSE); } len=flength(filename); if((tfile=fopen(filename,"r"))==NULL) { JS_ReportError(cx, "Unable to open template %s for read.", filename); return(JS_FALSE); } if((template=(char *)alloca(len))==NULL) { JS_ReportError(cx, "Unable to allocate %u bytes for template.", len); return(JS_FALSE); } if(fread(template, 1, len, tfile) != len) { fclose(tfile); JS_ReportError(cx, "Unable to read %u bytes from template %s.", len, filename); return(JS_FALSE); } fclose(tfile); if((!session->req.prev_write) && (!session->req.sent_headers)) { if(session->http_ver>=HTTP_1_1 && session->req.keep_alive) { if(!ssjs_send_headers(session,TRUE)) return(JS_FALSE); } else { /* "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,FALSE)) return(JS_FALSE); } } } session->req.prev_write=TRUE; js_write_template_part(cx, obj, template, len, NULL); return(JS_TRUE); } #endif 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 */ {"set_cookie", js_set_cookie, 2}, /* Set a cookie */ {0} }; static JSBool js_OperationCallback(JSContext *cx) { JSBool ret; http_session_t* session; JS_SetOperationCallback(cx, NULL); if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) { JS_SetOperationCallback(cx, js_OperationCallback); return(JS_FALSE); } ret=js_CommonOperationCallback(cx,&session->js_callback); JS_SetOperationCallback(cx, js_OperationCallback); return ret; } static JSContext* js_initcx(http_session_t *session) { JSContext* js_cx; lprintf(LOG_DEBUG,"%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) return(NULL); JS_BEGINREQUEST(js_cx); lprintf(LOG_DEBUG,"%04d JavaScript: Context created",session->socket); JS_SetErrorReporter(js_cx, js_ErrorReporter); JS_SetOperationCallback(js_cx, js_OperationCallback); lprintf(LOG_DEBUG,"%04d JavaScript: Creating Global Objects and Classes",session->socket); if(!js_CreateCommonObjects(js_cx, &scfg, NULL ,NULL /* global */ ,uptime /* system */ ,startup->host_name /* system */ ,SOCKLIB_DESC /* system */ ,&session->js_callback /* js */ ,&startup->js /* js */ ,&session->client /* client */ ,session->socket /* client */ ,&js_server_props /* server */ ,&session->js_glob ) || !JS_DefineFunctions(js_cx, session->js_glob, js_global_functions)) { JS_RemoveObjectRoot(js_cx, &session->js_glob); JS_ENDREQUEST(js_cx); JS_DestroyContext(js_cx); return(NULL); } return(js_cx); } static BOOL js_setup(http_session_t* session) { JSObject* argv; #ifndef ONE_JS_RUNTIME if(session->js_runtime == NULL) { lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes" ,session->socket,startup->js.max_bytes); if((session->js_runtime=jsrt_GetNew(startup->js.max_bytes, 5000, __FILE__, __LINE__))==NULL) { lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",session->socket); return(FALSE); } } #endif if(session->js_cx==NULL) { /* Context not yet created, create it now */ /* js_initcx() begins a context */ if(((session->js_cx=js_initcx(session))==NULL)) { lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript context",session->socket); return(FALSE); } 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); JS_DefineProperty(session->js_cx, session->js_glob, "web_root_dir", STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx, root_dir)) ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE); JS_DefineProperty(session->js_cx, session->js_glob, "web_error_dir", STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx, session->req.error_dir?session->req.error_dir:error_dir)) ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE); } else JS_BEGINREQUEST(session->js_cx); lprintf(LOG_DEBUG,"%04d JavaScript: Initializing HttpRequest object",session->socket); if(js_CreateHttpRequestObject(session->js_cx, session->js_glob, session)==NULL) { lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript HttpRequest object",session->socket); JS_ENDREQUEST(session->js_cx); return(FALSE); } lprintf(LOG_DEBUG,"%04d JavaScript: Initializing HttpReply object",session->socket); if(js_CreateHttpReplyObject(session->js_cx, session->js_glob, session)==NULL) { lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript HttpReply object",session->socket); JS_ENDREQUEST(session->js_cx); return(FALSE); } JS_SetContextPrivate(session->js_cx, session); JS_ENDREQUEST(session->js_cx); return(TRUE); } static BOOL ssjs_send_headers(http_session_t* session,int chunked) { jsval val; JSObject* reply; JSIdArray* heads; JSObject* headers; int i; char str[MAX_REQUEST_LINE+1]; char *p,*p2; JS_BEGINREQUEST(session->js_cx); JS_GetProperty(session->js_cx,session->js_glob,"http_reply",&val); reply = JSVAL_TO_OBJECT(val); JS_GetProperty(session->js_cx,reply,"status",&val); JSVALUE_TO_STRING(session->js_cx, val, p, NULL); SAFECOPY(session->req.status,p); JS_GetProperty(session->js_cx,reply,"header",&val); headers = JSVAL_TO_OBJECT(val); heads=JS_Enumerate(session->js_cx,headers); if(heads != NULL) { for(i=0;i<heads->length;i++) { JS_IdToValue(session->js_cx,heads->vector[i],&val); JSVALUE_TO_STRING(session->js_cx, val, p, NULL); JS_GetProperty(session->js_cx,headers,p,&val); JSVALUE_TO_STRING(session->js_cx, val, p2, NULL); safe_snprintf(str,sizeof(str),"%s: %s",p,p2); strListPush(&session->req.dynamic_heads,str); } JS_ClearScope(session->js_cx, headers); } JS_ENDREQUEST(session->js_cx); return(send_headers(session,session->req.status,chunked)); } static BOOL exec_ssjs(http_session_t* session, char* script) { JSObject* js_script; jsval rval; char path[MAX_PATH+1]; BOOL retval=TRUE; long double start; /* External JavaScript handler? */ if(script == session->req.physical_path && session->req.xjs_handler[0]) script = session->req.xjs_handler; sprintf(path,"%sSBBS_SSJS.%u.%u.html",temp_dir,getpid(),session->socket); if((session->req.fp=fopen(path,"wb"))==NULL) { lprintf(LOG_ERR,"%04d !ERROR %d opening/creating %s", session->socket, errno, path); return(FALSE); } if(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]) { if(!(startup->options&WEB_OPT_DEBUG_SSJS)) remove(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]); free(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]); } /* FREE()d in close_request() */ session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]=strdup(path); JS_BEGINREQUEST(session->js_cx); js_add_request_prop(session,"real_path",session->req.physical_path); js_add_request_prop(session,"virtual_path",session->req.virtual_path); js_add_request_prop(session,"ars",session->req.ars); js_add_request_prop(session,"request_string",session->req.request_line); js_add_request_prop(session,"host",session->req.host); js_add_request_prop(session,"vhost",session->req.vhost); js_add_request_prop(session,"http_ver",http_vers[session->http_ver]); js_add_request_prop(session,"remote_ip",session->host_ip); js_add_request_prop(session,"remote_host",session->host_name); if(session->req.query_str && session->req.query_str[0]) { js_add_request_prop(session,"query_string",session->req.query_str); js_parse_query(session,session->req.query_str); } if(session->req.post_data && session->req.post_data[0]) { if(session->req.post_len <= MAX_POST_LEN) { js_add_request_prop(session,"post_data",session->req.post_data); js_parse_query(session,session->req.post_data); } } parse_js_headers(session); do { /* RUN SCRIPT */ JS_ClearPendingException(session->js_cx); session->js_callback.counter=0; lprintf(LOG_DEBUG,"%04d JavaScript: Compiling script: %s",session->socket,script); if((js_script=JS_CompileFile(session->js_cx, session->js_glob ,script))==NULL) { lprintf(LOG_ERR,"%04d !JavaScript FAILED to compile script (%s)" ,session->socket,script); JS_RemoveObjectRoot(session->js_cx, &session->js_glob); JS_ENDREQUEST(session->js_cx); return(FALSE); } lprintf(LOG_DEBUG,"%04d JavaScript: Executing script: %s",session->socket,script); start=xp_timer(); js_PrepareToExecute(session->js_cx, session->js_glob, script, /* startup_dir */NULL); JS_ExecuteScript(session->js_cx, session->js_glob, js_script, &rval); js_EvalOnExit(session->js_cx, session->js_glob, &session->js_callback); JS_RemoveObjectRoot(session->js_cx, &session->js_glob); lprintf(LOG_DEBUG,"%04d JavaScript: Done executing script: %s (%.2Lf seconds)" ,session->socket,script,xp_timer()-start); } while(0); SAFECOPY(session->req.physical_path, path); if(session->req.fp!=NULL) { fclose(session->req.fp); session->req.fp=NULL; } /* Read http_reply object */ if(!session->req.sent_headers) { retval=ssjs_send_headers(session,FALSE); } /* Free up temporary resources here */ session->req.dynamic=IS_SSJS; JS_ENDREQUEST(session->js_cx); return(retval); } static void respond(http_session_t * session) { BOOL send_file=TRUE; if(session->req.method==HTTP_OPTIONS) { send_headers(session,session->req.status,FALSE); } else { if(session->req.dynamic==IS_CGI) { if(!exec_cgi(session)) { send_error(session,error_500); return; } session->req.finished=TRUE; return; } if(session->req.dynamic==IS_SSJS) { /* Server-Side JavaScript */ if(!exec_ssjs(session,session->req.physical_path)) { send_error(session,error_500); return; } sprintf(session->req.physical_path ,"%sSBBS_SSJS.%u.%u.html",temp_dir,getpid(),session->socket); } else { session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.')); send_file=send_headers(session,session->req.status,FALSE); } } if(session->req.method==HTTP_HEAD || session->req.method==HTTP_OPTIONS) send_file=FALSE; if(send_file) { int snt=0; lprintf(LOG_INFO,"%04d Sending file: %s (%"PRIuOFF" bytes)" ,session->socket, session->req.physical_path, flength(session->req.physical_path)); snt=sock_sendfile(session,session->req.physical_path,session->req.range_start,session->req.range_end); if(session->req.ld!=NULL) { if(snt<0) snt=0; session->req.ld->size=snt; } if(snt>0) lprintf(LOG_INFO,"%04d Sent file: %s (%d bytes)" ,session->socket, session->req.physical_path, snt); } session->req.finished=TRUE; } BOOL post_to_file(http_session_t *session, FILE*fp, size_t ch_len) { char buf[20*1024]; size_t k; int bytes_read; for(k=0; k<ch_len;) { bytes_read=recvbufsocket(&session->socket,buf,(ch_len-k)>sizeof(buf)?sizeof(buf):(ch_len-k)); if(!bytes_read) { send_error(session,error_500); fclose(fp); return(FALSE); } if(fwrite(buf, bytes_read, 1, fp)!=1) { send_error(session,error_500); fclose(fp); return(FALSE); } k+=bytes_read; session->req.post_len+=bytes_read; } return TRUE; } FILE *open_post_file(http_session_t *session) { char path[MAX_PATH+1]; FILE *fp; // Create temporary file for post data. sprintf(path,"%sSBBS_POST.%u.%u.html",temp_dir,getpid(),session->socket); if((fp=fopen(path,"wb"))==NULL) { lprintf(LOG_ERR,"%04d !ERROR %d opening/creating %s", session->socket, errno, path); return fp; } if(session->req.cleanup_file[CLEANUP_POST_DATA]) { remove(session->req.cleanup_file[CLEANUP_POST_DATA]); free(session->req.cleanup_file[CLEANUP_POST_DATA]); } /* remove()d in close_request() */ session->req.cleanup_file[CLEANUP_POST_DATA]=strdup(path); if(fwrite(session->req.post_data, session->req.post_len, 1, fp)!=1) { lprintf(LOG_ERR,"%04d !ERROR writeing to %s", session->socket, path); fclose(fp); return(FALSE); } FREE_AND_NULL(session->req.post_data); return fp; } int read_post_data(http_session_t * session) { unsigned i=0; FILE *fp=NULL; if(session->req.dynamic!=IS_CGI && (session->req.post_len || session->req.read_chunked)) { if(session->req.read_chunked) { char *p; size_t ch_len=0; int bytes_read=0; char ch_lstr[12]; session->req.post_len=0; while(1) { /* Read chunk length */ if(sockreadline(session,ch_lstr,sizeof(ch_lstr)-1)>0) { ch_len=strtol(ch_lstr,NULL,16); } else { send_error(session,error_500); if(fp) fclose(fp); return(FALSE); } if(ch_len==0) break; /* Check size */ i += ch_len; if(i > MAX_POST_LEN) { if(i > SIZE_MAX) { send_error(session,"413 Request entity too large"); if(fp) fclose(fp); return(FALSE); } if(fp==NULL) { fp=open_post_file(session); if(fp==NULL) return(FALSE); } if(!post_to_file(session, fp, ch_len)) return(FALSE); } else { /* realloc() to new size */ /* FREE()d in close_request */ p=realloc(session->req.post_data, i); if(p==NULL) { lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,session->req.post_len); send_error(session,"413 Request entity too large"); if(fp) fclose(fp); return(FALSE); } session->req.post_data=p; /* read new data */ bytes_read=recvbufsocket(&session->socket,session->req.post_data+session->req.post_len,ch_len); if(!bytes_read) { send_error(session,error_500); if(fp) fclose(fp); return(FALSE); } session->req.post_len+=bytes_read; /* Read chunk terminator */ if(sockreadline(session,ch_lstr,sizeof(ch_lstr)-1)>0) send_error(session,error_500); } } if(fp) { fclose(fp); FREE_AND_NULL(session->req.post_data); session->req.post_map=xpmap(session->req.cleanup_file[CLEANUP_POST_DATA], XPMAP_READ); if(!session->req.post_map) return(FALSE); session->req.post_data=session->req.post_map->addr; } /* Read more headers! */ if(!get_request_headers(session)) return(FALSE); if(!parse_headers(session)) return(FALSE); } else { i = session->req.post_len; FREE_AND_NULL(session->req.post_data); if(i > MAX_POST_LEN) { fp=open_post_file(session); if(fp==NULL) return(FALSE); if(!post_to_file(session, fp, i)) return(FALSE); fclose(fp); session->req.post_map=xpmap(session->req.cleanup_file[CLEANUP_POST_DATA], XPMAP_READ); if(!session->req.post_map) return(FALSE); session->req.post_data=session->req.post_map->addr; } else { /* FREE()d in close_request() */ if(i < (MAX_POST_LEN+1) && (session->req.post_data=malloc(i+1)) != NULL) session->req.post_len=recvbufsocket(&session->socket,session->req.post_data,i); else { lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,i); send_error(session,"413 Request entity too large"); return(FALSE); } } } if(session->req.post_len != i) lprintf(LOG_DEBUG,"%04d !ERROR Browser said they sent %d bytes, but I got %d",session->socket,i,session->req.post_len); if(session->req.post_len > i) session->req.post_len = i; session->req.post_data[session->req.post_len]=0; } return(TRUE); } void http_output_thread(void *arg) { http_session_t *session=(http_session_t *)arg; RingBuf *obuf; char buf[OUTBUF_LEN+12]; /* *MUST* be large enough to hold the buffer, the size of the buffer in hex, and four extra bytes. */ char *bufdata; int failed=0; int len; unsigned avail; int chunked; int i; unsigned mss=OUTBUF_LEN; SetThreadName("HTTP Output"); obuf=&(session->outbuf); /* Destroyed at end of function */ if((i=pthread_mutex_init(&session->outbuf_write,NULL))!=0) { lprintf(LOG_DEBUG,"Error %d initializing outbuf mutex",i); close_socket(&session->socket); return; } session->outbuf_write_initialized=1; #ifdef TCP_MAXSEG /* * Auto-tune the highwater mark to be the negotiated MSS for the * socket (when possible) */ if(!obuf->highwater_mark) { socklen_t sl; sl=sizeof(i); if(!getsockopt(session->socket, IPPROTO_TCP, TCP_MAXSEG, &i, &sl)) { /* Check for sanity... */ if(i>100) { obuf->highwater_mark=i-12; lprintf(LOG_DEBUG,"%04d Autotuning outbuf highwater mark to %d based on MSS" ,session->socket,i); mss=obuf->highwater_mark; if(mss>OUTBUF_LEN) { mss=OUTBUF_LEN; lprintf(LOG_DEBUG,"%04d MSS (%d) is higher than OUTBUF_LEN (%d)" ,session->socket,i,OUTBUF_LEN); } } } } #endif thread_up(TRUE /* setuid */); /* * Do *not* exit on terminate_server... wait for session thread * to close the socket and set it to INVALID_SOCKET */ while(session->socket!=INVALID_SOCKET) { /* Wait for something to output in the RingBuffer */ if((avail=RingBufFull(obuf))==0) { /* empty */ if(sem_trywait_block(&obuf->sem,1000)) continue; /* Check for spurious sem post... */ if((avail=RingBufFull(obuf))==0) continue; } else sem_trywait(&obuf->sem); /* Wait for full buffer or drain timeout */ if(obuf->highwater_mark) { if(avail<obuf->highwater_mark) { sem_trywait_block(&obuf->highwater_sem,startup->outbuf_drain_timeout); /* We (potentially) blocked, so get fill level again */ avail=RingBufFull(obuf); } else sem_trywait(&obuf->highwater_sem); } /* * At this point, there's something to send and, * if the highwater mark is set, the timeout has * passed or we've hit highwater. Read ring buffer * into linear buffer. */ len=avail; if(avail>mss) len=(avail=mss); /* * Read the current value of write_chunked... since we wait until the * ring buffer is empty before fiddling with it. */ chunked=session->req.write_chunked; bufdata=buf; if(chunked) { i=sprintf(buf, "%X\r\n", avail); bufdata+=i; len+=i; } pthread_mutex_lock(&session->outbuf_write); RingBufRead(obuf, bufdata, avail); if(chunked) { bufdata+=avail; *(bufdata++)='\r'; *(bufdata++)='\n'; len+=2; } if(!failed) sock_sendbuf(&session->socket, buf, len, &failed); pthread_mutex_unlock(&session->outbuf_write); } thread_down(); /* Ensure outbuf isn't currently being drained */ pthread_mutex_lock(&session->outbuf_write); session->outbuf_write_initialized=0; pthread_mutex_unlock(&session->outbuf_write); pthread_mutex_destroy(&session->outbuf_write); sem_post(&session->output_thread_terminated); } void http_session_thread(void* arg) { char* host_name; HOSTENT* host; SOCKET socket; char redir_req[MAX_REQUEST_LINE+1]; char *redirp; http_session_t session; int loop_count; BOOL init_error; int32_t clients_remain; SetThreadName("HTTP Session"); pthread_mutex_lock(&((http_session_t*)arg)->struct_filled); pthread_mutex_unlock(&((http_session_t*)arg)->struct_filled); pthread_mutex_destroy(&((http_session_t*)arg)->struct_filled); session=*(http_session_t*)arg; /* copies arg BEFORE it's freed */ FREE_AND_NULL(arg); socket=session.socket; if(socket==INVALID_SOCKET) { session_threads--; return; } lprintf(LOG_DEBUG,"%04d Session thread started", session.socket); if(startup->index_file_name==NULL || startup->cgi_ext==NULL) lprintf(LOG_DEBUG,"%04d !!! DANGER WILL ROBINSON, DANGER !!!", session.socket); #ifdef _WIN32 if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME); #endif thread_up(TRUE /* setuid */); session.finished=FALSE; /* Start up the output buffer */ /* FREE()d in this block (RingBufDispose before all returns) */ if(RingBufInit(&(session.outbuf), OUTBUF_LEN)) { lprintf(LOG_ERR,"%04d Canot create output ringbuffer!", session.socket); close_socket(&session.socket); thread_down(); session_threads--; return; } /* Destroyed in this block (before all returns) */ sem_init(&session.output_thread_terminated,0,0); _beginthread(http_output_thread, 0, &session); sbbs_srand(); /* Seed random number generator */ 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=session.host_ip; SAFECOPY(session.host_name,host_name); if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) { lprintf(LOG_INFO,"%04d Hostname: %s", session.socket, session.host_name); #if 0 /* gethostbyaddr() is apparently not (always) thread-safe and getnameinfo() doesn't return alias information */ for(i=0;host!=NULL && host->h_aliases!=NULL && host->h_aliases[i]!=NULL;i++) lprintf(LOG_INFO,"%04d HostAlias: %s", session.socket, host->h_aliases[i]); #endif if(trashcan(&scfg,session.host_name,"host")) { lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", session.socket, session.host_name); close_socket(&session.socket); sem_wait(&session.output_thread_terminated); sem_destroy(&session.output_thread_terminated); RingBufDispose(&session.outbuf); thread_down(); session_threads--; return; } } /* host_ip wasn't defined in http_session_thread */ if(trashcan(&scfg,session.host_ip,"ip")) { lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip); close_socket(&session.socket); sem_wait(&session.output_thread_terminated); sem_destroy(&session.output_thread_terminated); RingBufDispose(&session.outbuf); thread_down(); session_threads--; return; } protected_int32_adjust(&active_clients, 1); update_clients(); SAFECOPY(session.username,unknown); SAFECOPY(session.client.addr,session.host_ip); SAFECOPY(session.client.host,session.host_name); session.client.port=ntohs(session.addr.sin_port); session.client.time=time32(NULL); session.client.protocol="HTTP"; session.client.user=session.username; session.client.size=sizeof(session.client); client_on(session.socket, &session.client, /* update existing client record? */FALSE); session.last_user_num=-1; session.last_js_user_num=-1; session.logon_time=0; session.subscan=(subscan_t*)alloca(sizeof(subscan_t)*scfg.total_subs); while(!session.finished) { init_error=FALSE; memset(&(session.req), 0, sizeof(session.req)); redirp=NULL; loop_count=0; if(startup->options&WEB_OPT_HTTP_LOGGING) { /* FREE()d in http_logging_thread... passed there by close_request() */ if((session.req.ld=(struct log_data*)malloc(sizeof(struct log_data)))==NULL) lprintf(LOG_ERR,"%04d Cannot allocate memory for log data!",session.socket); } if(session.req.ld!=NULL) { memset(session.req.ld,0,sizeof(struct log_data)); /* FREE()d in http_logging_thread */ session.req.ld->hostname=strdup(session.host_name); } while((redirp==NULL || session.req.send_location >= MOVED_TEMP) && !session.finished && !session.req.finished && session.socket!=INVALID_SOCKET) { SAFECOPY(session.req.status,"200 OK"); session.req.send_location=NO_LOCATION; if(session.req.headers==NULL) { /* FREE()d in close_request() */ if((session.req.headers=strListInit())==NULL) { lprintf(LOG_ERR,"%04d !ERROR allocating memory for header list",session.socket); init_error=TRUE; } } if(session.req.cgi_env==NULL) { /* FREE()d in close_request() */ if((session.req.cgi_env=strListInit())==NULL) { lprintf(LOG_ERR,"%04d !ERROR allocating memory for CGI environment list",session.socket); init_error=TRUE; } } if(session.req.dynamic_heads==NULL) { /* FREE()d in close_request() */ if((session.req.dynamic_heads=strListInit())==NULL) { lprintf(LOG_ERR,"%04d !ERROR allocating memory for dynamic header list",session.socket); init_error=TRUE; } } if(get_req(&session,redirp)) { if(init_error) { send_error(&session, error_500); } /* At this point, if redirp is non-NULL then the headers have already been parsed */ if((session.http_ver<HTTP_1_0)||redirp!=NULL||parse_headers(&session)) { if(check_request(&session)) { if(session.req.send_location < MOVED_TEMP || session.req.virtual_path[0]!='/' || loop_count++ >= MAX_REDIR_LOOPS) { if(read_post_data(&session)) respond(&session); } else { safe_snprintf(redir_req,sizeof(redir_req),"%s %s%s%s",methods[session.req.method] ,session.req.virtual_path,session.http_ver<HTTP_1_0?"":" ",http_vers[session.http_ver]); lprintf(LOG_DEBUG,"%04d Internal Redirect to: %s",socket,redir_req); redirp=redir_req; } } } } else { session.req.keep_alive=FALSE; break; } } close_request(&session); } http_logoff(&session,socket,__LINE__); if(session.js_cx!=NULL) { lprintf(LOG_DEBUG,"%04d JavaScript: Destroying context",socket); JS_BEGINREQUEST(session.js_cx); JS_RemoveObjectRoot(session.js_cx, &session.js_glob); JS_ENDREQUEST(session.js_cx); JS_DestroyContext(session.js_cx); /* Free Context */ session.js_cx=NULL; } #ifndef ONE_JS_RUNTIME if(session.js_runtime!=NULL) { lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",socket); jsrt_Release(session.js_runtime); session.js_runtime=NULL; } #endif #ifdef _WIN32 if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE)) PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME); #endif close_socket(&session.socket); sem_wait(&session.output_thread_terminated); sem_destroy(&session.output_thread_terminated); RingBufDispose(&session.outbuf); clients_remain=protected_int32_adjust(&active_clients, -1); update_clients(); client_off(socket); session_threads--; thread_down(); if(startup->index_file_name==NULL || startup->cgi_ext==NULL) lprintf(LOG_DEBUG,"%04d !!! ALL YOUR BASE ARE BELONG TO US !!!", socket); lprintf(LOG_INFO,"%04d Session thread terminated (%u clients, %u threads remain, %lu served)" ,socket, clients_remain, thread_count, served); } void DLLCALL web_terminate(void) { lprintf(LOG_INFO,"%04d Web Server terminate",server_socket); terminate_server=TRUE; } static void cleanup(int code) { while(session_threads) { lprintf(LOG_INFO,"#### Web Server waiting on %d active session threads",session_threads); SLEEP(1000); } free_cfg(&scfg); listFree(&log_list); mime_types=iniFreeNamedStringList(mime_types); cgi_handlers=iniFreeNamedStringList(cgi_handlers); xjs_handlers=iniFreeNamedStringList(xjs_handlers); cgi_env=iniFreeStringList(cgi_env); semfile_list_free(&recycle_semfiles); semfile_list_free(&shutdown_semfiles); if(server_socket!=INVALID_SOCKET) { close_socket(&server_socket); } if(active_clients.value) lprintf(LOG_WARNING,"#### Web Server terminating with %ld active clients", active_clients.value); else protected_int32_destroy(active_clients); update_clients(); #ifdef _WINSOCKAPI_ if(WSAInitialized && WSACleanup()!=0) lprintf(LOG_ERR,"0000 !WSACleanup ERROR %d",ERROR_VALUE); #endif thread_down(); status("Down"); if(terminate_server || code) lprintf(LOG_INFO,"#### Web Server thread terminated (%lu clients served)", served); if(thread_count) lprintf(LOG_WARNING,"#### !Web Server threads (%u) remain after termination", thread_count); if(startup!=NULL && startup->terminated!=NULL) startup->terminated(startup->cbdata,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 http_logging_thread(void* arg) { char base[MAX_PATH+1]; char filename[MAX_PATH+1]; char newfilename[MAX_PATH+1]; FILE* logfile=NULL; http_logging_thread_running=TRUE; terminate_http_logging_thread=FALSE; SAFECOPY(base,arg); if(!base[0]) SAFEPRINTF(base,"%slogs/http-",scfg.logs_dir); SetThreadName("HTTP Logging"); filename[0]=0; newfilename[0]=0; thread_up(TRUE /* setuid */); lprintf(LOG_INFO,"%04d HTTP logging thread started", server_socket); for(;;) { struct log_data *ld; char timestr[128]; char sizestr[100]; if(!listSemTryWait(&log_list)) { if(logfile!=NULL) fflush(logfile); listSemWait(&log_list); } ld=listShiftNode(&log_list); /* * Because the sem is posted when terminate_http_logging_thread is set, this will * ensure that all pending log entries are written to disk */ if(ld==NULL) { if(terminate_http_logging_thread) break; lprintf(LOG_ERR,"%04d HTTP logging thread received NULL linked list log entry" ,server_socket); continue; } SAFECOPY(newfilename,base); if(startup->options&WEB_OPT_VIRTUAL_HOSTS && ld->vhost!=NULL) { strcat(newfilename,ld->vhost); if(ld->vhost[0]) strcat(newfilename,"-"); } strftime(strchr(newfilename,0),15,"%Y-%m-%d.log",&ld->completed); if(logfile==NULL || strcmp(newfilename,filename)) { if(logfile!=NULL) fclose(logfile); SAFECOPY(filename,newfilename); logfile=fopen(filename,"ab"); if(logfile) lprintf(LOG_INFO,"%04d HTTP logfile is now: %s",server_socket,filename); } if(logfile!=NULL) { if(ld->status) { sprintf(sizestr,"%d",ld->size); strftime(timestr,sizeof(timestr),"%d/%b/%Y:%H:%M:%S %z",&ld->completed); /* * In case of a termination, do no block for a lock... just discard * the output. */ while(lock(fileno(logfile),0,1) && !terminate_http_logging_thread) { SLEEP(10); } fprintf(logfile,"%s %s %s [%s] \"%s\" %d %s \"%s\" \"%s\"\n" ,ld->hostname?(ld->hostname[0]?ld->hostname:"-"):"-" ,ld->ident?(ld->ident[0]?ld->ident:"-"):"-" ,ld->user?(ld->user[0]?ld->user:"-"):"-" ,timestr ,ld->request?(ld->request[0]?ld->request:"-"):"-" ,ld->status ,ld->size?sizestr:"-" ,ld->referrer?(ld->referrer[0]?ld->referrer:"-"):"-" ,ld->agent?(ld->agent[0]?ld->agent:"-"):"-"); unlock(fileno(logfile),0,1); } } else { lprintf(LOG_ERR,"%04d HTTP server failed to open logfile %s (%d)!",server_socket,filename,errno); } FREE_AND_NULL(ld->hostname); FREE_AND_NULL(ld->ident); FREE_AND_NULL(ld->user); FREE_AND_NULL(ld->request); FREE_AND_NULL(ld->referrer); FREE_AND_NULL(ld->agent); FREE_AND_NULL(ld->vhost); FREE_AND_NULL(ld); } if(logfile!=NULL) { fclose(logfile); logfile=NULL; } thread_down(); lprintf(LOG_INFO,"%04d HTTP logging thread terminated",server_socket); http_logging_thread_running=FALSE; } void DLLCALL web_server(void* arg) { int i; int result; time_t start; WORD host_port; char host_ip[32]; char path[MAX_PATH+1]; char logstr[256]; char mime_types_ini[MAX_PATH+1]; char web_handler_ini[MAX_PATH+1]; 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; FILE* fp; char* p; char compiler[32]; http_session_t * session=NULL; struct timeval tv; #ifdef ONE_JS_RUNTIME JSRuntime* js_runtime; #endif #ifdef SO_ACCEPTFILTER struct accept_filter_arg afa; #endif startup=(web_startup_t*)arg; SetThreadName("Web Server"); web_ver(); /* get CVS revision */ if(startup==NULL) { sbbs_beep(100,500); fprintf(stderr, "No startup structure passed!\n"); return; } 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; } #ifdef _THREAD_SUID_BROKEN if(thread_suid_broken) startup->seteuid(TRUE); #endif /* Setup intelligent defaults */ if(startup->port==0) startup->port=IPPORT_HTTP; if(startup->root_dir[0]==0) SAFECOPY(startup->root_dir,WEB_DEFAULT_ROOT_DIR); if(startup->error_dir[0]==0) SAFECOPY(startup->error_dir,WEB_DEFAULT_ERROR_DIR); if(startup->default_auth_list[0]==0) SAFECOPY(startup->default_auth_list,WEB_DEFAULT_AUTH_LIST); if(startup->cgi_dir[0]==0) SAFECOPY(startup->cgi_dir,WEB_DEFAULT_CGI_DIR); if(startup->default_cgi_content[0]==0) SAFECOPY(startup->default_cgi_content,WEB_DEFAULT_CGI_CONTENT); if(startup->max_inactivity==0) startup->max_inactivity=120; /* seconds */ if(startup->max_cgi_inactivity==0) startup->max_cgi_inactivity=120; /* seconds */ if(startup->sem_chk_freq==0) startup->sem_chk_freq=2; /* seconds */ if(startup->js.max_bytes==0) startup->js.max_bytes=JAVASCRIPT_MAX_BYTES; if(startup->js.cx_stack==0) startup->js.cx_stack=JAVASCRIPT_CONTEXT_STACK; if(startup->ssjs_ext[0]==0) SAFECOPY(startup->ssjs_ext,".ssjs"); if(startup->js_ext[0]==0) SAFECOPY(startup->js_ext,".bbs"); ZERO_VAR(js_server_props); SAFEPRINTF2(js_server_props.version,"%s %s",server_name,revision); js_server_props.version_detail=web_ver(); js_server_props.clients=&active_clients.value; js_server_props.options=&startup->options; js_server_props.interface_addr=&startup->interface_addr; uptime=0; served=0; startup->recycle_now=FALSE; startup->shutdown_now=FALSE; terminate_server=FALSE; do { thread_up(FALSE /* setuid */); status("Initializing"); /* Copy html directories */ SAFECOPY(root_dir,startup->root_dir); SAFECOPY(error_dir,startup->error_dir); SAFECOPY(default_auth_list,startup->default_auth_list); SAFECOPY(cgi_dir,startup->cgi_dir); if(startup->temp_dir[0]) SAFECOPY(temp_dir,startup->temp_dir); else SAFECOPY(temp_dir,"../temp"); /* Change to absolute path */ prep_dir(startup->ctrl_dir, root_dir, sizeof(root_dir)); prep_dir(startup->ctrl_dir, temp_dir, sizeof(temp_dir)); prep_dir(root_dir, error_dir, sizeof(error_dir)); prep_dir(root_dir, cgi_dir, sizeof(cgi_dir)); /* Trim off trailing slash/backslash */ if(IS_PATH_DELIM(*(p=lastchar(root_dir)))) *p=0; memset(&scfg, 0, sizeof(scfg)); lprintf(LOG_INFO,"%s Revision %s%s" ,server_name ,revision #ifdef _DEBUG ," Debug" #else ,"" #endif ); DESCRIBE_COMPILER(compiler); lprintf(LOG_INFO,"Compiled %s %s with %s", __DATE__, __TIME__, compiler); if(!winsock_startup()) { cleanup(1); return; } t=time(NULL); lprintf(LOG_INFO,"Initializing on %.24s with options: %lx" ,ctime_r(&t,logstr),startup->options); if(chdir(startup->ctrl_dir)!=0) lprintf(LOG_ERR,"!ERROR %d changing directory to: %s", errno, startup->ctrl_dir); /* Initial configuration and load from CNF files */ SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir); lprintf(LOG_INFO,"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(LOG_CRIT,"!ERROR %s",logstr); lprintf(LOG_CRIT,"!FAILED to load configuration files"); cleanup(1); return; } lprintf(LOG_DEBUG,"Temporary file directory: %s", temp_dir); MKDIR(temp_dir); if(!isdir(temp_dir)) { lprintf(LOG_CRIT,"!Invalid temp directory: %s", temp_dir); cleanup(1); return; } lprintf(LOG_DEBUG,"Root directory: %s", root_dir); lprintf(LOG_DEBUG,"Error directory: %s", error_dir); lprintf(LOG_DEBUG,"CGI directory: %s", cgi_dir); iniFileName(mime_types_ini,sizeof(mime_types_ini),scfg.ctrl_dir,"mime_types.ini"); mime_types=read_ini_list(mime_types_ini,NULL /* root section */,"MIME types" ,mime_types); iniFileName(web_handler_ini,sizeof(web_handler_ini),scfg.ctrl_dir,"web_handler.ini"); if((cgi_handlers=read_ini_list(web_handler_ini,"CGI."PLATFORM_DESC,"CGI content handlers" ,cgi_handlers))==NULL) cgi_handlers=read_ini_list(web_handler_ini,"CGI","CGI content handlers" ,cgi_handlers); xjs_handlers=read_ini_list(web_handler_ini,"JavaScript","JavaScript content handlers" ,xjs_handlers); /* Don't do this for *each* CGI request, just once here during [re]init */ iniFileName(cgi_env_ini,sizeof(cgi_env_ini),scfg.ctrl_dir,"cgi_env.ini"); if((fp=iniOpenFile(cgi_env_ini,/* create? */FALSE)) != NULL) { cgi_env = iniReadFile(fp); iniCloseFile(fp); } if(startup->host_name[0]==0) SAFECOPY(startup->host_name,scfg.sys_inetaddr); if(uptime==0) uptime=time(NULL); /* this must be done *after* setting the timezone */ protected_int32_init(&active_clients,0); update_clients(); /* open a socket and wait for a client */ server_socket = open_socket(SOCK_STREAM); if(server_socket == INVALID_SOCKET) { lprintf(LOG_CRIT,"!ERROR %d creating HTTP socket", ERROR_VALUE); cleanup(1); return; } /* * i=1; * if(setsockopt(server_socket, IPPROTO_TCP, TCP_NOPUSH, &i, sizeof(i))) * lprintf("Cannot set TCP_NOPUSH socket option"); */ #ifdef SO_ACCEPTFILTER memset(&afa, 0, sizeof(afa)); strcpy(afa.af_name, "httpready"); setsockopt(server_socket, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)); #endif lprintf(LOG_DEBUG,"%04d Web Server socket 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->port < IPPORT_RESERVED) { if(startup->seteuid!=NULL) startup->seteuid(FALSE); } result = retry_bind(server_socket,(struct sockaddr *)&server_addr,sizeof(server_addr) ,startup->bind_retry_count,startup->bind_retry_delay,"Web Server",lprintf); if(startup->port < IPPORT_RESERVED) { if(startup->seteuid!=NULL) startup->seteuid(TRUE); } if(result != 0) { lprintf(LOG_CRIT,"%s",BIND_FAILURE_HELP); cleanup(1); return; } result = listen(server_socket, 64); if(result != 0) { lprintf(LOG_CRIT,"%04d !ERROR %d (%d) listening on socket" ,server_socket, result, ERROR_VALUE); cleanup(1); return; } lprintf(LOG_INFO,"%04d Web Server listening on port %u" ,server_socket, startup->port); status("Listening"); listInit(&log_list,/* flags */ LINK_LIST_MUTEX|LINK_LIST_SEMAPHORE); if(startup->options&WEB_OPT_HTTP_LOGGING) { /********************/ /* Start log thread */ /********************/ _beginthread(http_logging_thread, 0, startup->logfile_base); } #ifdef ONE_JS_RUNTIME if(js_runtime == NULL) { lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes" ,server_socket,startup->js.max_bytes); if((js_runtime=jsrt_GetNew(startup->js.max_bytes, 0, __FILE__, __LINE__))==NULL) { lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",server_socket); /* Sleep 15 seconds then try again */ /* ToDo: Something better should be used here. */ SLEEP(15000); continue; } } #endif /* Setup recycle/shutdown semaphore file lists */ shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown","web"); recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle","web"); SAFEPRINTF(path,"%swebsrvr.rec",scfg.ctrl_dir); /* legacy */ semfile_list_add(&recycle_semfiles,path); semfile_list_add(&recycle_semfiles,mime_types_ini); semfile_list_add(&recycle_semfiles,web_handler_ini); semfile_list_add(&recycle_semfiles,cgi_env_ini); if(!initialized) { initialized=time(NULL); semfile_list_check(&initialized,recycle_semfiles); semfile_list_check(&initialized,shutdown_semfiles); } /* signal caller that we've started up successfully */ if(startup->started!=NULL) startup->started(startup->cbdata); lprintf(LOG_INFO,"%04d Web Server thread started", server_socket); while(server_socket!=INVALID_SOCKET && !terminate_server) { /* check for re-cycle/shutdown semaphores */ if(active_clients.value==0) { if(!(startup->options&BBS_OPT_NO_RECYCLE)) { if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) { lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected" ,server_socket,p); if(session!=NULL) { pthread_mutex_unlock(&session->struct_filled); session=NULL; } break; } #if 0 /* unused */ if(startup->recycle_sem!=NULL && sem_trywait(&startup->recycle_sem)==0) startup->recycle_now=TRUE; #endif if(startup->recycle_now==TRUE) { lprintf(LOG_INFO,"%04d Recycle semaphore signaled",server_socket); startup->recycle_now=FALSE; if(session!=NULL) { pthread_mutex_unlock(&session->struct_filled); session=NULL; } break; } } if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL && lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected" ,server_socket,p)) || (startup->shutdown_now==TRUE && lprintf(LOG_INFO,"%04d Shutdown semaphore signaled" ,server_socket))) { startup->shutdown_now=FALSE; terminate_server=TRUE; if(session!=NULL) { pthread_mutex_unlock(&session->struct_filled); session=NULL; } break; } } /* Startup next session thread */ if(session==NULL) { /* FREE()d at the start of the session thread */ if((session=malloc(sizeof(http_session_t)))==NULL) { lprintf(LOG_CRIT,"%04d !ERROR allocating %u bytes of memory for http_session_t" ,server_socket, sizeof(http_session_t)); mswait(3000); continue; } memset(session, 0, sizeof(http_session_t)); session->socket=INVALID_SOCKET; /* Destroyed in http_session_thread */ pthread_mutex_init(&session->struct_filled,NULL); pthread_mutex_lock(&session->struct_filled); session_threads++; _beginthread(http_session_thread, 0, session); } /* now wait for connection */ FD_ZERO(&socket_set); FD_SET(server_socket,&socket_set); high_socket_set=server_socket+1; tv.tv_sec=startup->sem_chk_freq; tv.tv_usec=0; if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) { if(i==0) continue; if(ERROR_VALUE==EINTR) lprintf(LOG_DEBUG,"Web Server listening interrupted"); else if(ERROR_VALUE == ENOTSOCK) lprintf(LOG_INFO,"Web Server socket closed"); else lprintf(LOG_WARNING,"!ERROR %d selecting socket",ERROR_VALUE); continue; } if(server_socket==INVALID_SOCKET) { /* terminated */ pthread_mutex_unlock(&session->struct_filled); session=NULL; 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(LOG_NOTICE,"!NO SOCKETS set by select"); continue; } if(client_socket == INVALID_SOCKET) { lprintf(LOG_WARNING,"!ERROR %d accepting connection", ERROR_VALUE); #ifdef _WIN32 if(WSAGetLastError()==WSAENOBUFS) { /* recycle (re-init WinSock) on this error */ pthread_mutex_unlock(&session->struct_filled); session=NULL; break; } #endif continue; } if(startup->socket_open!=NULL) startup->socket_open(startup->cbdata,TRUE); SAFECOPY(host_ip,inet_ntoa(client_addr.sin_addr)); if(trashcan(&scfg,host_ip,"ip-silent")) { close_socket(&client_socket); continue; } if(startup->max_clients && active_clients.value>=startup->max_clients) { lprintf(LOG_WARNING,"%04d !MAXIMUM CLIENTS (%d) reached, access denied" ,client_socket, startup->max_clients); mswait(3000); close_socket(&client_socket); continue; } host_port=ntohs(client_addr.sin_port); lprintf(LOG_INFO,"%04d HTTP connection accepted from: %s port %u" ,client_socket ,host_ip, host_port); SAFECOPY(session->host_ip,host_ip); session->addr=client_addr; session->socket=client_socket; session->js_callback.auto_terminate=TRUE; session->js_callback.terminated=&terminate_server; session->js_callback.limit=startup->js.time_limit; session->js_callback.gc_interval=startup->js.gc_interval; session->js_callback.yield_interval=startup->js.yield_interval; #ifdef ONE_JS_RUNTIME session->js_runtime=js_runtime; #endif pthread_mutex_unlock(&session->struct_filled); session=NULL; served++; } if(session) { pthread_mutex_unlock(&session->struct_filled); session=NULL; } /* Wait for active clients to terminate */ if(active_clients.value) { lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..." ,server_socket, active_clients.value); start=time(NULL); while(active_clients.value) { if(time(NULL)-start>startup->max_inactivity) { lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d active clients" ,server_socket, active_clients.value); break; } mswait(100); } } if(http_logging_thread_running) { terminate_http_logging_thread=TRUE; listSemPost(&log_list); mswait(100); } if(http_logging_thread_running) { lprintf(LOG_DEBUG,"%04d Waiting for HTTP logging thread to terminate..." ,server_socket); start=time(NULL); while(http_logging_thread_running) { if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for HTTP logging thread to " "terminate", server_socket); break; } mswait(100); } } #ifdef ONE_JS_RUNTIME if(js_runtime!=NULL) { lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",server_socket); jsrt_Release(js_runtime); js_runtime=NULL; } #endif cleanup(0); if(!terminate_server) { lprintf(LOG_INFO,"Recycling server..."); mswait(2000); if(startup->recycle!=NULL) startup->recycle(startup->cbdata); } } while(!terminate_server); }