--- sbbs/src/sbbs3/websrvr.c 2018/04/24 16:41:23 1.1 +++ sbbs/src/sbbs3/websrvr.c 2018/04/24 16:42:51 1.1.1.2 @@ -2,13 +2,13 @@ /* Synchronet Web Server */ -/* $Id: websrvr.c,v 1.1 2018/04/24 16:41:23 root Exp $ */ +/* $Id: websrvr.c,v 1.1.1.2 2018/04/24 16:42:51 root Exp $ */ /**************************************************************************** * @format.tab-size 4 (Plain Text/Source Code File Header) * * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * * * - * Copyright 2006 Rob Swindell - http://www.synchro.net/copyright.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 * @@ -38,8 +38,6 @@ /* * General notes: (ToDo stuff) * - * Should support RFC2617 Digest auth. - * * Support the ident protocol... the standard log format supports it. * * Add in support to pass connections through to a different webserver... @@ -47,6 +45,8 @@ * This would allow people to run apache and Synchronet as the same site. */ +//#define ONE_JS_RUNTIME + /* Headers for CGI stuff */ #if defined(__unix__) #include /* waitpid() */ @@ -60,11 +60,15 @@ #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" static const char* server_name="Synchronet Web Server"; static const char* newline="\r\n"; @@ -92,27 +96,29 @@ enum { }; static scfg_t scfg; -static BOOL scfg_reloaded=TRUE; -static BOOL http_logging_thread_running=FALSE; -static ulong active_clients=0; -static ulong sockets=0; -static BOOL terminate_server=FALSE; -static BOOL terminate_http_logging_thread=FALSE; -static uint thread_count=0; -static SOCKET server_socket=INVALID_SOCKET; +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 volatile SOCKET server_socket=INVALID_SOCKET; +static volatile SOCKET server_socket6=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 time_t uptime=0; -static DWORD served=0; +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 int session_threads=0; +static str_list_t cgi_env; +static volatile ulong session_threads=0; static named_string_t** mime_types; static named_string_t** cgi_handlers; @@ -133,6 +139,47 @@ struct log_data { 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]; @@ -141,7 +188,7 @@ typedef struct { time_t if_modified_since; BOOL keep_alive; char ars[256]; - char auth[128]; /* UserID:Password */ + 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; @@ -175,15 +222,19 @@ typedef struct { BOOL sent_headers; BOOL prev_write; - /* webconfig.ini overrides */ + /* 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 */ @@ -429,18 +480,30 @@ time_gm(struct tm *tm) return (t < 0 ? (time_t) -1 : t); } -static int lprintf(int level, char *fmt, ...) +static int lprintf(int level, const char *fmt, ...) { va_list argptr; char sbuf[1024]; - if(startup==NULL || startup->lputs==NULL) - return(0); - 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)); } @@ -485,6 +548,10 @@ static int sock_sendbuf(SOCKET *sock, co 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) @@ -521,12 +588,12 @@ static BOOL winsock_startup(void) int status; /* Status Code */ if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) { - lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus); + lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus); WSAInitialized=TRUE; return (TRUE); } - lprintf(LOG_ERR,"!WinSock startup ERROR %d", status); + lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status); return (FALSE); } @@ -546,7 +613,7 @@ static void status(char* str) static void update_clients(void) { if(startup!=NULL && startup->clients!=NULL) - startup->clients(startup->cbdata,active_clients); + startup->clients(startup->cbdata,active_clients.value); } static void client_on(SOCKET sock, client_t* client, BOOL update) @@ -834,7 +901,17 @@ static void close_request(http_session_t 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. */ @@ -846,7 +923,9 @@ static void close_request(http_session_t 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); @@ -907,23 +986,18 @@ static const char* get_mime_type(char *e return(unknown_mime_type); } -static BOOL get_cgi_handler(char* cmdline, size_t maxlen) +static char* get_cgi_handler(const char* fname) { - char fname[MAX_PATH+1]; char* ext; size_t i; - if(cgi_handlers==NULL || (ext=getfext(cmdline))==NULL) - return(FALSE); - + 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) { - SAFECOPY(fname,cmdline); - safe_snprintf(cmdline,maxlen,"%s %s",cgi_handlers[i]->value,fname); - return(TRUE); - } + if(stricmp(cgi_handlers[i]->name, ext+1)==0) + return(cgi_handlers[i]->value); } - return(FALSE); + return(NULL); } static BOOL get_xjs_handler(char* ext, http_session_t* session) @@ -963,6 +1037,7 @@ static void safecat(char *dst, const cha 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; @@ -1017,8 +1092,14 @@ static BOOL send_headers(http_session_t send_file=FALSE; } + stat_code=atoi(status_line); if(session->req.ld!=NULL) - session->req.ld->status=atoi(status_line); + 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); @@ -1311,7 +1392,8 @@ void http_logoff(http_session_t* session ,socket,session->user.number, line); SAFECOPY(session->username,unknown); - logoutuserdat(&scfg, &session->user, time(NULL), session->logon_time); + 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; } @@ -1321,40 +1403,80 @@ BOOL http_checkuser(http_session_t * ses if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) { if(session->last_js_user_num==session->user.number) return(TRUE); - lprintf(LOG_INFO,"%04d JavaScript: Initializing User Objects",session->socket); + 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 + 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, NULL + 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) { - char *username; - char *password; - char *last; uchar *ar; BOOL authorized; - char auth_req[MAX_REQUEST_LINE+1]; 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)req.auth[0]==0) { + /* 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) @@ -1373,19 +1495,9 @@ static BOOL check_ars(http_session_t * s /* No auth required, allow */ return(TRUE); } - SAFECOPY(auth_req,session->req.auth); - username=strtok_r(auth_req,":",&last); - if(username) - password=strtok_r(NULL,":",&last); - else { - username=""; - password=""; - } /* Require a password */ - if(password==NULL) - password=""; - i=matchuser(&scfg, username, FALSE); + i=matchuser(&scfg, session->req.auth.username, FALSE); if(i==0) { if(session->last_user_num!=0) { if(session->last_user_num>0) @@ -1395,37 +1507,166 @@ static BOOL check_ars(http_session_t * s } if(!http_checkuser(session)) return(FALSE); - if(scfg.sys_misc&SM_ECHO_PW) + if(scfg.sys_misc&SM_ECHO_PW && session->req.auth.type==AUTHENTICATION_BASIC) lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s" - ,session->socket,username,password); + ,session->socket,session->req.auth.username,session->req.auth.password); else lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s" - ,session->socket,username); + ,session->socket,session->req.auth.username); return(FALSE); } thisuser.number=i; getuserdat(&scfg, &thisuser); - if(thisuser.pass[0] && stricmp(thisuser.pass,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,username,password,thisuser.pass); - else - lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s" - ,session->socket,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); + switch(session->req.auth.type) { + case AUTHENTICATION_BASIC: + if((auth_allowed & (1<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<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) { @@ -1439,16 +1680,23 @@ static BOOL check_ars(http_session_t * s if(session->req.ld!=NULL) { FREE_AND_NULL(session->req.ld->user); /* FREE()d in http_logging_thread */ - session->req.ld->user=strdup(username); + session->req.ld->user=strdup(session->req.auth.username); } ar = arstr(NULL,session->req.ars,&scfg); - authorized=chk_ar(&scfg,ar,&session->user); + authorized=chk_ar(&scfg,ar,&session->user,&session->client); if(ar!=NULL && ar!=nular) FREE_AND_NULL(ar); if(authorized) { - add_env(session,"AUTH_TYPE","Basic"); + 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); @@ -1457,7 +1705,7 @@ static BOOL check_ars(http_session_t * s /* Should go to the hack log? */ lprintf(LOG_WARNING,"%04d !AUTHORIZATION FAILURE for user %s, ARS: %s" - ,session->socket,username,session->req.ars); + ,session->socket,session->req.auth.username,session->req.ars); #ifdef _WIN32 if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE)) @@ -1467,25 +1715,22 @@ static BOOL check_ars(http_session_t * s return(FALSE); } -static named_string_t** read_ini_list(char* fname, char* section, char* desc +static named_string_t** read_ini_list(char* path, char* section, char* desc ,named_string_t** list) { - char path[MAX_PATH+1]; size_t i; FILE* fp; list=iniFreeNamedStringList(list); - iniFileName(path,sizeof(path),scfg.ctrl_dir,fname); - 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",i,desc,path); + lprintf(LOG_DEBUG,"Read %u %s from %s section of %s" + ,i,desc,section==NULL ? "root":section,path); } - return(list); } @@ -1516,7 +1761,7 @@ static int sockreadline(http_session_t * return(-1); default: /* Timeout */ - lprintf(LOG_WARNING,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity); + lprintf(LOG_NOTICE,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity); return(-1); } @@ -1548,10 +1793,10 @@ static int sockreadline(http_session_t * if(i>length) i=length; - if(i>0 && buf[i-1]=='\r') - buf[--i]=0; - else - buf[i]=0; + 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); @@ -1842,10 +2087,88 @@ static void js_parse_query(http_session_ } } +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; @@ -1854,60 +2177,139 @@ static BOOL parse_headers(http_session_t char env_name[128]; for(idx=0;session->req.headers[idx]!=NULL;idx++) { - head_line=session->req.headers[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++; - if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) - js_add_header(session,head_line,value); switch(i) { case HEAD_AUTH: - if(strtok_r(value," ",&last)) { - p=strtok_r(NULL," ",&last); - if(p==NULL) - break; - while(*p && *p<' ') p++; - b64_decode(session->req.auth,sizeof(session->req.auth),p,strlen(p)); + 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_TYPE: - add_env(session,"CONTENT_TYPE",value); - 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_IFMODIFIED: session->req.if_modified_since=decode_date(value); break; @@ -1970,6 +2372,72 @@ static BOOL parse_headers(http_session_t 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; @@ -1977,6 +2445,8 @@ static BOOL parse_headers(http_session_t 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); @@ -1987,13 +2457,8 @@ static BOOL parse_headers(http_session_t default: break; } - sprintf(env_name,"HTTP_%s",head_line); - add_env(session,env_name,value); } } - 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; } @@ -2033,13 +2498,12 @@ static int is_dynamic_req(http_session_t else if(stricmp(ext,startup->js_ext)==0) i=IS_JS; if(!(startup->options&BBS_OPT_NO_JAVASCRIPT) && i) { - lprintf(LOG_INFO,"%04d Setting up JavaScript support", session->socket); + lprintf(LOG_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); } @@ -2083,26 +2547,40 @@ static char *get_request(http_session_t /* 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+http_scheme_len); + 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 */ + + /* Remove port specification from vhost (if present) */ strtok_r(session->req.vhost,":",&last); - if(strtok_r(session->req.physical_path,"/",&last)) - p=strtok_r(NULL,"/",&last); - else - p=NULL; + + /* 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) { - /* Do not allow host values larger than 128 bytes */ - session->req.host[0]=0; - p=session->req.physical_path+http_scheme_len; - } - offset=p-session->req.physical_path; - memmove(session->req.physical_path - ,session->req.physical_path+offset - ,strlen(session->req.physical_path+offset)+1 /* move '\0' terminator too */ - ); + 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); @@ -2193,12 +2671,10 @@ static BOOL get_fullpath(http_session_t } else safe_snprintf(str,sizeof(str),"%s%s",root_dir,session->req.physical_path); - if(FULLPATH(session->req.physical_path,str,sizeof(session->req.physical_path))==NULL) { - send_error(session,error_500); + if(FULLPATH(session->req.physical_path,str,sizeof(session->req.physical_path))==NULL) return(FALSE); - } - return(TRUE); + return(isabspath(session->req.physical_path)); } static BOOL get_req(http_session_t * session, char *request_line) @@ -2221,7 +2697,7 @@ static BOOL get_req(http_session_t * ses if(len<0) return(FALSE); if(req_line[0]) - lprintf(LOG_DEBUG,"%04d Request: %s",session->socket,req_line); + 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); @@ -2316,7 +2792,6 @@ static BOOL check_extra_path(http_sessio struct stat sb; int i; char *end; - int use_epath=0; epath[0]=0; epath[1]=0; @@ -2336,8 +2811,7 @@ static BOOL check_extra_path(http_sessio /* Check if this contains an index */ end=strchr(rpath,0); - if(use_epath || session->req.path_info_index || strchr(epath+1,'/')!=NULL) { - use_epath=1; + if(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; @@ -2460,7 +2934,7 @@ static BOOL check_request(http_session_t /* Set default ARS to a 0-length string */ session->req.ars[0]=0; - /* Walk up from root_dir checking for access.ars and webconfig.ini */ + /* 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*/ @@ -2499,7 +2973,7 @@ static BOOL check_request(http_session_t send_error(session,"403 Forbidden"); return(FALSE); } - /* Read webctrl.ars file */ + /* Read webctrl.ini file */ if((file=fopen(str,"r"))!=NULL) { /* FREE()d in this block */ specs=iniReadSectionList(file,NULL); @@ -2511,6 +2985,11 @@ static BOOL check_request(http_session_t /* 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); @@ -2524,6 +3003,11 @@ static BOOL check_request(http_session_t 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) { @@ -2535,6 +3019,11 @@ static BOOL check_request(http_session_t /* 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)); @@ -2548,6 +3037,11 @@ static BOOL check_request(http_session_t 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); @@ -2578,9 +3072,29 @@ static BOOL check_request(http_session_t send404=TRUE; if(!check_ars(session)) { + unsigned *auth_list; + unsigned auth_list_len; + /* No authentication provided */ - sprintf(str,"401 Unauthorized%s%s: Basic realm=\"%s\"" - ,newline,get_header(HEAD_WWWAUTH),session->req.realm?session->req.realm:scfg.sys_name); + 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)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); } @@ -2642,7 +3156,6 @@ static str_list_t get_cgi_env(http_sessi char append[INI_MAX_VALUE_LEN+1]; char prepend[INI_MAX_VALUE_LEN+1]; char env_str[(INI_MAX_VALUE_LEN*4)+2]; - FILE* fp; size_t i; str_list_t env_list; str_list_t add_list; @@ -2653,21 +3166,16 @@ static str_list_t get_cgi_env(http_sessi strListAppendList(&env_list, session->req.cgi_env); - strListPush(&env_list,"REDIRECT_STATUS=200"); /* Kludge for php-cgi */ - - if((fp=iniOpenFile(cgi_env_ini,/* create? */FALSE))==NULL) - return(env_list); - /* FREE()d in this block */ - if((add_list=iniReadSectionList(fp,NULL))!=NULL) { + if((add_list=iniGetSectionList(cgi_env,NULL))!=NULL) { for(i=0; add_list[i]!=NULL; i++) { if((deflt=getenv(add_list[i]))==NULL) - deflt=iniReadString(fp,add_list[i],"default",NULL,defltbuf); - if(iniReadString(fp,add_list[i],"value",deflt,value)==NULL) + deflt=iniGetString(cgi_env,add_list[i],"default",NULL,defltbuf); + if(iniGetString(cgi_env,add_list[i],"value",deflt,value)==NULL) continue; - iniReadString(fp,add_list[i],"append","",append); - iniReadString(fp,add_list[i],"prepend","",prepend); + 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); @@ -2675,8 +3183,6 @@ static str_list_t get_cgi_env(http_sessi iniFreeStringList(add_list); } - fclose(fp); - return(env_list); } @@ -2763,10 +3269,10 @@ static BOOL exec_cgi(http_session_t *ses } /* Execute command */ - if(get_cgi_handler(cgipath, sizeof(cgipath))) { + if((p=get_cgi_handler(cmdline))!=NULL) { char* shell=os_cmdshell(); - lprintf(LOG_INFO,"%04d Using handler %s to execute %s",session->socket,cgipath,cmdline); - execle(shell,shell,"-c",cgipath,NULL,env_list); + 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); @@ -3052,6 +3558,7 @@ static BOOL exec_cgi(http_session_t *ses 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; @@ -3070,9 +3577,7 @@ static BOOL exec_cgi(http_session_t *ses startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; startup_info.wShowWindow=SW_HIDE; - SAFECOPY(cmdline,session->req.physical_path); - - SAFECOPY(startup_dir,cmdline); + SAFECOPY(startup_dir,session->req.physical_path); if((p=strrchr(startup_dir,'/'))!=NULL || (p=strrchr(startup_dir,'\\'))!=NULL) *p=0; else @@ -3080,7 +3585,10 @@ static BOOL exec_cgi(http_session_t *ses lprintf(LOG_DEBUG,"%04d CGI startup dir: %s", session->socket, startup_dir); - get_cgi_handler(cmdline, sizeof(cmdline)); + 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); @@ -3155,6 +3663,20 @@ static BOOL exec_cgi(http_session_t *ses 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 */ @@ -3385,6 +3907,7 @@ js_ErrorReporter(JSContext *cx, const ch char file[MAX_PATH+1]; char* warning; http_session_t* session; + int log_level; if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return; @@ -3397,12 +3920,12 @@ js_ErrorReporter(JSContext *cx, const ch } if(report->filename) - sprintf(file," %s",report->filename); + SAFEPRINTF(file," %s",report->filename); else file[0]=0; if(report->lineno) - sprintf(line," line %u",report->lineno); + SAFEPRINTF(line," line %u",report->lineno); else line[0]=0; @@ -3411,10 +3934,14 @@ js_ErrorReporter(JSContext *cx, const ch warning="strict warning"; else warning="warning"; - } else + log_level=LOG_WARNING; + } else { + log_level=LOG_ERR; warning=""; + } - lprintf(LOG_ERR,"%04d !JavaScript %s%s%s: %s",session->socket,warning,file,line,message); + 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); } @@ -3435,17 +3962,23 @@ js_writefunc(JSContext *cx, JSObject *ob uintN i; JSString* str=NULL; http_session_t* session; + jsrefcount rc; if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return(JS_FALSE); - if(session->req.fp==NULL) + 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) { - if(!ssjs_send_headers(session,TRUE)) + 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? */ @@ -3456,8 +3989,12 @@ js_writefunc(JSContext *cx, JSObject *ob 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)) + rc=JS_SUSPENDREQUEST(cx); + if(!ssjs_send_headers(session,FALSE)) { + JS_RESUMEREQUEST(cx, rc); return(JS_FALSE); + } + JS_RESUMEREQUEST(cx, rc); } } } @@ -3469,9 +4006,11 @@ js_writefunc(JSContext *cx, JSObject *ob continue; if(JS_GetStringLength(str)<1 && !writeln) continue; + rc=JS_SUSPENDREQUEST(cx); js_writebuf(session,JS_GetStringBytes(str), JS_GetStringLength(str)); if(writeln) js_writebuf(session, newline, 2); + JS_RESUMEREQUEST(cx, rc); } if(str==NULL) @@ -3518,6 +4057,7 @@ js_set_cookie(JSContext *cx, JSObject *o JSBool b; struct tm tm; http_session_t* session; + time_t tt; if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return(JS_FALSE); @@ -3536,7 +4076,8 @@ js_set_cookie(JSContext *cx, JSObject *o header+=sprintf(header,"%s",p); if(argc>2) { JS_ValueToInt32(cx,argv[2],&i); - if(i && gmtime_r((time_t *)&i,&tm)!=NULL) + 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) { @@ -3565,6 +4106,7 @@ js_log(JSContext *cx, JSObject *obj, uin int32 level=LOG_INFO; JSString* js_str; http_session_t* session; + jsrefcount rc; if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL) return(JS_FALSE); @@ -3583,7 +4125,9 @@ js_log(JSContext *cx, JSObject *obj, uin strcat(str," "); } + rc=JS_SUSPENDREQUEST(cx); lprintf(level,"%04d %s",session->socket,str); + JS_RESUMEREQUEST(cx, rc); *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str)); @@ -3598,6 +4142,7 @@ js_login(JSContext *cx, JSObject *obj, u user_t user; JSString* js_str; http_session_t* session; + jsrefcount rc; *rval = BOOLEAN_TO_JSVAL(JS_FALSE); @@ -3611,6 +4156,8 @@ js_login(JSContext *cx, JSObject *obj, u if((p=JS_GetStringBytes(js_str))==NULL) return(JS_FALSE); + rc=JS_SUSPENDREQUEST(cx); + memset(&user,0,sizeof(user)); if(isdigit(*p)) @@ -3621,15 +4168,18 @@ js_login(JSContext *cx, JSObject *obj, u 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]) { if((js_str=JS_ValueToString(cx, argv[1]))==NULL) @@ -3639,8 +4189,10 @@ js_login(JSContext *cx, JSObject *obj, u 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); } } @@ -3648,6 +4200,8 @@ js_login(JSContext *cx, JSObject *obj, u if(argc>2) JS_ValueToBoolean(cx,argv[2],&inc_logons); + rc=JS_SUSPENDREQUEST(cx); + if(inc_logons) { user.logons++; user.ltoday++; @@ -3655,8 +4209,10 @@ js_login(JSContext *cx, JSObject *obj, u http_logon(session, &user); + JS_RESUMEREQUEST(cx, rc); + /* user-specific objects */ - if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user + 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"); @@ -3874,35 +4430,55 @@ js_BranchCallback(JSContext *cx, JSScrip return(js_CommonBranchCallback(cx,&session->js_branch)); } +#ifdef USE_JS_OPERATION_CALLBACK +static JSBool +js_OperationCallback(JSContext *cx) +{ + JSBool ret; + + JS_SetOperationCallback(cx, NULL); + ret=js_BranchCallback(cx, NULL); + JS_SetOperationCallback(cx, js_OperationCallback); + return ret; +} +#endif + static JSContext* js_initcx(http_session_t *session) { JSContext* js_cx; - lprintf(LOG_INFO,"%04d JavaScript: Initializing context (stack: %lu bytes)" + 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_INFO,"%04d JavaScript: Context created",session->socket); + lprintf(LOG_DEBUG,"%04d JavaScript: Context created",session->socket); JS_SetErrorReporter(js_cx, js_ErrorReporter); +#ifdef USE_JS_OPERATION_CALLBACK + JS_SetOperationCallback(js_cx, js_OperationCallback); +#else JS_SetBranchCallback(js_cx, js_BranchCallback); +#endif - lprintf(LOG_INFO,"%04d JavaScript: Creating Global Objects and Classes",session->socket); + lprintf(LOG_DEBUG,"%04d JavaScript: Creating Global Objects and Classes",session->socket); if((session->js_glob=js_CreateCommonObjects(js_cx, &scfg, NULL ,NULL /* global */ ,uptime /* system */ ,startup->host_name /* system */ ,SOCKLIB_DESC /* system */ ,&session->js_branch /* js */ + ,&startup->js /* js */ ,&session->client /* client */ ,session->socket /* client */ ,&js_server_props /* server */ ))==NULL || !JS_DefineFunctions(js_cx, session->js_glob, js_global_functions)) { + JS_ENDREQUEST(js_cx); JS_DestroyContext(js_cx); return(NULL); } @@ -3916,10 +4492,10 @@ static BOOL js_setup(http_session_t* ses #ifndef ONE_JS_RUNTIME if(session->js_runtime == NULL) { - lprintf(LOG_INFO,"%04d JavaScript: Creating runtime: %lu bytes" + lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes" ,session->socket,startup->js.max_bytes); - if((session->js_runtime=JS_NewRuntime(startup->js.max_bytes))==NULL) { + 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); } @@ -3927,6 +4503,7 @@ static BOOL js_setup(http_session_t* ses #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); @@ -3946,20 +4523,25 @@ static BOOL js_setup(http_session_t* ses ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE); } + else + JS_BEGINREQUEST(session->js_cx); - lprintf(LOG_INFO,"%04d JavaScript: Initializing HttpRequest object",session->socket); + 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_INFO,"%04d JavaScript: Initializing HttpReply object",session->socket); + 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); } @@ -3974,6 +4556,7 @@ static BOOL ssjs_send_headers(http_sessi JSString* js_str; char str[MAX_REQUEST_LINE+1]; + 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); @@ -3992,6 +4575,7 @@ static BOOL ssjs_send_headers(http_sessi } JS_ClearScope(session->js_cx, headers); } + JS_ENDREQUEST(session->js_cx); return(send_headers(session,session->req.status,chunked)); } @@ -4019,6 +4603,7 @@ static BOOL exec_ssjs(http_session_t* se /* 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); @@ -4036,6 +4621,7 @@ static BOOL exec_ssjs(http_session_t* se 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 */ @@ -4048,11 +4634,13 @@ static BOOL exec_ssjs(http_session_t* se ,script))==NULL) { lprintf(LOG_ERR,"%04d !JavaScript FAILED to compile script (%s)" ,session->socket,script); + 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_branch); lprintf(LOG_DEBUG,"%04d JavaScript: Done executing script: %s (%.2Lf seconds)" @@ -4076,6 +4664,7 @@ static BOOL exec_ssjs(http_session_t* se if(js_script!=NULL) JS_DestroyScript(session->js_cx, js_script); session->req.dynamic=IS_SSJS; + JS_ENDREQUEST(session->js_cx); return(retval); } @@ -4114,7 +4703,7 @@ static void respond(http_session_t * ses send_file=FALSE; if(send_file) { int snt=0; - lprintf(LOG_INFO,"%04d Sending file: %s (%u bytes)" + 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) { @@ -4174,6 +4763,9 @@ int read_post_data(http_session_t * sess 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); } /* Read more headers! */ if(!get_request_headers(session)) @@ -4216,6 +4808,7 @@ void http_output_thread(void *arg) 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) { @@ -4237,11 +4830,13 @@ void http_output_thread(void *arg) /* Check for sanity... */ if(i>100) { obuf->highwater_mark=i-12; - lprintf(LOG_DEBUG,"Autotuning outbuf highwater mark to %d based on MSS",i); + 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,"MSS (%d) is higher than OUTBUF_LEN (%d)",i,OUTBUF_LEN); + lprintf(LOG_DEBUG,"%04d MSS (%d) is higher than OUTBUF_LEN (%d)" + ,session->socket,i,OUTBUF_LEN); } } } @@ -4323,7 +4918,6 @@ void http_output_thread(void *arg) void http_session_thread(void* arg) { - int i; char* host_name; HOSTENT* host; SOCKET socket; @@ -4332,7 +4926,9 @@ void http_session_thread(void* arg) 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); @@ -4388,12 +4984,15 @@ void http_session_thread(void* arg) SAFECOPY(session.host_name,host_name); if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) { - lprintf(LOG_INFO,"%04d Hostname: %s", session.socket, host_name); + 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]); - if(trashcan(&scfg,host_name,"host")) { - lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", session.socket, host_name); +#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); @@ -4416,7 +5015,7 @@ void http_session_thread(void* arg) return; } - active_clients++; + protected_int32_adjust(&active_clients, 1); update_clients(); SAFECOPY(session.username,unknown); @@ -4518,15 +5117,15 @@ void http_session_thread(void* arg) http_logoff(&session,socket,__LINE__); if(session.js_cx!=NULL) { - lprintf(LOG_INFO,"%04d JavaScript: Destroying context",socket); + lprintf(LOG_DEBUG,"%04d JavaScript: Destroying context",socket); JS_DestroyContext(session.js_cx); /* Free Context */ session.js_cx=NULL; } #ifndef ONE_JS_RUNTIME if(session.js_runtime!=NULL) { - lprintf(LOG_INFO,"%04d JavaScript: Destroying runtime",socket); - JS_DestroyRuntime(session.js_runtime); + lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",socket); + jsrt_Release(session.js_runtime); session.js_runtime=NULL; } #endif @@ -4541,7 +5140,7 @@ void http_session_thread(void* arg) sem_destroy(&session.output_thread_terminated); RingBufDispose(&session.outbuf); - active_clients--; + clients_remain=protected_int32_adjust(&active_clients, -1); update_clients(); client_off(socket); @@ -4552,7 +5151,7 @@ void http_session_thread(void* arg) 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, active_clients, thread_count, served); + ,socket, clients_remain, thread_count, served); } @@ -4577,6 +5176,8 @@ static void cleanup(int code) 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); @@ -4584,6 +5185,11 @@ static void cleanup(int code) 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_ @@ -4594,8 +5200,9 @@ static void cleanup(int code) thread_down(); status("Down"); if(terminate_server || code) - lprintf(LOG_INFO,"#### Web Server thread terminated (%u threads remain, %lu clients served)" - ,thread_count, served); + 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); } @@ -4607,7 +5214,7 @@ const char* DLLCALL web_ver(void) DESCRIBE_COMPILER(compiler); - sscanf("$Revision: 1.1 $", "%*s %s", revision); + sscanf("$Revision: 1.1.1.2 $", "%*s %s", revision); sprintf(ver,"%s %s%s " "Compiled %s %s with %s" @@ -4637,12 +5244,13 @@ void http_logging_thread(void* 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_DEBUG,"%04d http logging thread started", server_socket); + lprintf(LOG_INFO,"%04d HTTP logging thread started", server_socket); for(;;) { struct log_data *ld; @@ -4663,7 +5271,7 @@ void http_logging_thread(void* arg) if(ld==NULL) { if(terminate_http_logging_thread) break; - lprintf(LOG_ERR,"%04d http logging thread received NULL linked list log entry" + lprintf(LOG_ERR,"%04d HTTP logging thread received NULL linked list log entry" ,server_socket); continue; } @@ -4674,12 +5282,13 @@ void http_logging_thread(void* arg) strcat(newfilename,"-"); } strftime(strchr(newfilename,0),15,"%Y-%m-%d.log",&ld->completed); - if(strcmp(newfilename,filename)) { + if(logfile==NULL || strcmp(newfilename,filename)) { if(logfile!=NULL) fclose(logfile); SAFECOPY(filename,newfilename); logfile=fopen(filename,"ab"); - lprintf(LOG_INFO,"%04d http logfile is now: %s",server_socket,filename); + if(logfile) + lprintf(LOG_INFO,"%04d HTTP logfile is now: %s",server_socket,filename); } if(logfile!=NULL) { if(ld->status) { @@ -4706,8 +5315,7 @@ void http_logging_thread(void* arg) } } else { - logfile=fopen(filename,"ab"); - lprintf(LOG_ERR,"%04d http logfile %s was not open!",server_socket,filename); + 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); @@ -4723,7 +5331,7 @@ void http_logging_thread(void* arg) logfile=NULL; } thread_down(); - lprintf(LOG_DEBUG,"%04d http logging thread terminated",server_socket); + lprintf(LOG_INFO,"%04d HTTP logging thread terminated",server_socket); http_logging_thread_running=FALSE; } @@ -4737,6 +5345,8 @@ void DLLCALL web_server(void* arg) 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; @@ -4745,6 +5355,7 @@ void DLLCALL web_server(void* arg) fd_set socket_set; time_t t; time_t initialized=0; + FILE* fp; char* p; char compiler[32]; http_session_t * session=NULL; @@ -4758,6 +5369,7 @@ void DLLCALL web_server(void* arg) startup=(web_startup_t*)arg; + SetThreadName("Web Server"); web_ver(); /* get CVS revision */ if(startup==NULL) { @@ -4783,6 +5395,7 @@ void DLLCALL web_server(void* arg) 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 */ @@ -4796,7 +5409,7 @@ void DLLCALL web_server(void* arg) 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; + js_server_props.clients=&active_clients.value; js_server_props.options=&startup->options; js_server_props.interface_addr=&startup->interface_addr; @@ -4815,6 +5428,7 @@ void DLLCALL web_server(void* arg) /* 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); @@ -4853,7 +5467,7 @@ void DLLCALL web_server(void* arg) t=time(NULL); lprintf(LOG_INFO,"Initializing on %.24s with options: %lx" - ,CTIME_R(&t,logstr),startup->options); + ,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); @@ -4864,17 +5478,16 @@ void DLLCALL web_server(void* arg) scfg.size=sizeof(scfg); SAFECOPY(logstr,UNKNOWN_LOAD_ERROR); if(!load_cfg(&scfg, NULL, TRUE, logstr)) { - lprintf(LOG_ERR,"!ERROR %s",logstr); - lprintf(LOG_ERR,"!FAILED to load configuration files"); + lprintf(LOG_CRIT,"!ERROR %s",logstr); + lprintf(LOG_CRIT,"!FAILED to load configuration files"); cleanup(1); return; } - scfg_reloaded=TRUE; lprintf(LOG_DEBUG,"Temporary file directory: %s", temp_dir); MKDIR(temp_dir); if(!isdir(temp_dir)) { - lprintf(LOG_ERR,"!Invalid temp directory: %s", temp_dir); + lprintf(LOG_CRIT,"!Invalid temp directory: %s", temp_dir); cleanup(1); return; } @@ -4882,29 +5495,31 @@ void DLLCALL web_server(void* arg) lprintf(LOG_DEBUG,"Error directory: %s", error_dir); lprintf(LOG_DEBUG,"CGI directory: %s", cgi_dir); - mime_types=read_ini_list("mime_types.ini",NULL /* root section */,"MIME types" + 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); - 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" + 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(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&BBS_OPT_LOCAL_TIMEZONE)) { - if(putenv("TZ=UTC0")) - lprintf(LOG_WARNING,"!putenv() FAILED"); - tzset(); - } - if(uptime==0) uptime=time(NULL); /* this must be done *after* setting the timezone */ - active_clients=0; + protected_int32_init(&active_clients,0); update_clients(); /* open a socket and wait for a client */ @@ -4912,7 +5527,7 @@ void DLLCALL web_server(void* arg) server_socket = open_socket(SOCK_STREAM); if(server_socket == INVALID_SOCKET) { - lprintf(LOG_ERR,"!ERROR %d creating HTTP socket", ERROR_VALUE); + lprintf(LOG_CRIT,"!ERROR %d creating HTTP socket", ERROR_VALUE); cleanup(1); return; } @@ -4929,7 +5544,7 @@ void DLLCALL web_server(void* arg) setsockopt(server_socket, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)); #endif - lprintf(LOG_INFO,"%04d Web Server socket opened",server_socket); + lprintf(LOG_DEBUG,"%04d Web Server socket opened",server_socket); /*****************************/ /* Listen for incoming calls */ @@ -4940,14 +5555,18 @@ void DLLCALL web_server(void* arg) server_addr.sin_family = AF_INET; server_addr.sin_port = htons(startup->port); - if(startup->seteuid!=NULL) - startup->seteuid(FALSE); + 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->seteuid!=NULL) - startup->seteuid(TRUE); + if(startup->port < IPPORT_RESERVED) { + if(startup->seteuid!=NULL) + startup->seteuid(TRUE); + } if(result != 0) { - lprintf(LOG_NOTICE,"%s",BIND_FAILURE_HELP); + lprintf(LOG_CRIT,"%s",BIND_FAILURE_HELP); cleanup(1); return; } @@ -4955,17 +5574,15 @@ void DLLCALL web_server(void* arg) result = listen(server_socket, 64); if(result != 0) { - lprintf(LOG_ERR,"%04d !ERROR %d (%d) listening on socket" + 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 %d" + lprintf(LOG_INFO,"%04d Web Server listening on port %u" ,server_socket, startup->port); status("Listening"); - lprintf(LOG_INFO,"%04d Web Server thread started", server_socket); - listInit(&log_list,/* flags */ LINK_LIST_MUTEX|LINK_LIST_SEMAPHORE); if(startup->options&WEB_OPT_HTTP_LOGGING) { /********************/ @@ -4976,10 +5593,10 @@ void DLLCALL web_server(void* arg) #ifdef ONE_JS_RUNTIME if(js_runtime == NULL) { - lprintf(LOG_INFO,"%04d JavaScript: Creating runtime: %lu bytes" + lprintf(LOG_DEBUG,"%04d JavaScript: Creating runtime: %lu bytes" ,server_socket,startup->js.max_bytes); - if((js_runtime=JS_NewRuntime(startup->js.max_bytes))==NULL) { + 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. */ @@ -4994,6 +5611,9 @@ void DLLCALL web_server(void* arg) 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); @@ -5004,10 +5624,12 @@ void DLLCALL web_server(void* arg) 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==0) { + 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" @@ -5053,7 +5675,7 @@ void DLLCALL web_server(void* arg) /* 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" - ,client_socket, sizeof(http_session_t)); + ,server_socket, sizeof(http_session_t)); mswait(3000); continue; } @@ -5127,7 +5749,7 @@ void DLLCALL web_server(void* arg) continue; } - if(startup->max_clients && active_clients>=startup->max_clients) { + 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); @@ -5164,14 +5786,14 @@ void DLLCALL web_server(void* arg) } /* Wait for active clients to terminate */ - if(active_clients) { + if(active_clients.value) { lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..." - ,server_socket, active_clients); + ,server_socket, active_clients.value); start=time(NULL); - while(active_clients) { + 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); + ,server_socket, active_clients.value); break; } mswait(100); @@ -5199,8 +5821,8 @@ void DLLCALL web_server(void* arg) #ifdef ONE_JS_RUNTIME if(js_runtime!=NULL) { - lprintf(LOG_INFO,"%04d JavaScript: Destroying runtime",server_socket); - JS_DestroyRuntime(js_runtime); + lprintf(LOG_DEBUG,"%04d JavaScript: Destroying runtime",server_socket); + jsrt_Release(js_runtime); js_runtime=NULL; } #endif