|
|
1.1 root 1: /* websrvr.c */
2:
3: /* Synchronet Web Server */
4:
5: /* $Id: websrvr.c,v 1.458 2006/12/29 09:21:35 rswindell Exp $ */
6:
7: /****************************************************************************
8: * @format.tab-size 4 (Plain Text/Source Code File Header) *
9: * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
10: * *
11: * Copyright 2006 Rob Swindell - http://www.synchro.net/copyright.html *
12: * *
13: * This program is free software; you can redistribute it and/or *
14: * modify it under the terms of the GNU General Public License *
15: * as published by the Free Software Foundation; either version 2 *
16: * of the License, or (at your option) any later version. *
17: * See the GNU General Public License for more details: gpl.txt or *
18: * http://www.fsf.org/copyleft/gpl.html *
19: * *
20: * Anonymous FTP access to the most recent released source is available at *
21: * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net *
22: * *
23: * Anonymous CVS access to the development source and modification history *
24: * is available at cvs.synchro.net:/cvsroot/sbbs, example: *
25: * cvs -d :pserver:[email protected]:/cvsroot/sbbs login *
26: * (just hit return, no password is necessary) *
27: * cvs -d :pserver:[email protected]:/cvsroot/sbbs checkout src *
28: * *
29: * For Synchronet coding style and modification guidelines, see *
30: * http://www.synchro.net/source.html *
31: * *
32: * You are encouraged to submit any modifications (preferably in Unix diff *
33: * format) via e-mail to [email protected] *
34: * *
35: * Note: If this box doesn't appear square, then you need to fix your tabs. *
36: ****************************************************************************/
37:
38: /*
39: * General notes: (ToDo stuff)
40: *
41: * Should support RFC2617 Digest auth.
42: *
43: * Support the ident protocol... the standard log format supports it.
44: *
45: * Add in support to pass connections through to a different webserver...
46: * probobly in access.ars... with like a simplified mod_rewrite.
47: * This would allow people to run apache and Synchronet as the same site.
48: */
49:
50: /* Headers for CGI stuff */
51: #if defined(__unix__)
52: #include <sys/wait.h> /* waitpid() */
53: #include <sys/types.h>
54: #include <signal.h> /* kill() */
55: #endif
56:
57: #ifndef JAVASCRIPT
58: #define JAVASCRIPT
59: #endif
60:
61: #undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
62: #include "sbbs.h"
63: #include "sockwrap.h" /* sendfilesocket() */
64: #include "threadwrap.h"
65: #include "semwrap.h"
66: #include "websrvr.h"
67: #include "base64.h"
68:
69: static const char* server_name="Synchronet Web Server";
70: static const char* newline="\r\n";
71: static const char* http_scheme="http://";
72: static const size_t http_scheme_len=7;
73: static const char* error_301="301 Moved Permanently";
74: static const char* error_302="302 Moved Temporarily";
75: static const char* error_404="404 Not Found";
76: static const char* error_416="416 Requested Range Not Satisfiable";
77: static const char* error_500="500 Internal Server Error";
78: static const char* unknown="<unknown>";
79:
80: #define TIMEOUT_THREAD_WAIT 60 /* Seconds */
81: #define MAX_REQUEST_LINE 1024 /* NOT including terminator */
82: #define MAX_HEADERS_SIZE 16384 /* Maximum total size of all headers
83: (Including terminator )*/
84: #define MAX_REDIR_LOOPS 20 /* Max. times to follow internal redirects for a single request */
85: #define MAX_POST_LEN 1048576 /* Max size of body for POSTS */
86: #define OUTBUF_LEN 20480 /* Size of output thread ring buffer */
87:
88: enum {
89: CLEANUP_SSJS_TMP_FILE
90: ,CLEANUP_POST_DATA
91: ,MAX_CLEANUPS
92: };
93:
94: static scfg_t scfg;
95: static BOOL scfg_reloaded=TRUE;
96: static BOOL http_logging_thread_running=FALSE;
97: static ulong active_clients=0;
98: static ulong sockets=0;
99: static BOOL terminate_server=FALSE;
100: static BOOL terminate_http_logging_thread=FALSE;
101: static uint thread_count=0;
102: static SOCKET server_socket=INVALID_SOCKET;
103: static char revision[16];
104: static char root_dir[MAX_PATH+1];
105: static char error_dir[MAX_PATH+1];
106: static char temp_dir[MAX_PATH+1];
107: static char cgi_dir[MAX_PATH+1];
108: static char cgi_env_ini[MAX_PATH+1];
109: static time_t uptime=0;
110: static DWORD served=0;
111: static web_startup_t* startup=NULL;
112: static js_server_props_t js_server_props;
113: static str_list_t recycle_semfiles;
114: static str_list_t shutdown_semfiles;
115: static int session_threads=0;
116:
117: static named_string_t** mime_types;
118: static named_string_t** cgi_handlers;
119: static named_string_t** xjs_handlers;
120:
121: /* Logging stuff */
122: link_list_t log_list;
123: struct log_data {
124: char *hostname;
125: char *ident;
126: char *user;
127: char *request;
128: char *referrer;
129: char *agent;
130: char *vhost;
131: int status;
132: unsigned int size;
133: struct tm completed;
134: };
135:
136: typedef struct {
137: int method;
138: char virtual_path[MAX_PATH+1];
139: char physical_path[MAX_PATH+1];
140: BOOL expect_go_ahead;
141: time_t if_modified_since;
142: BOOL keep_alive;
143: char ars[256];
144: char auth[128]; /* UserID:Password */
145: char host[128]; /* The requested host. (as used for self-referencing URLs) */
146: char vhost[128]; /* The requested host. (virtual host) */
147: int send_location;
148: const char* mime_type;
149: str_list_t headers;
150: char status[MAX_REQUEST_LINE+1];
151: char * post_data;
152: size_t post_len;
153: int dynamic;
154: char xjs_handler[MAX_PATH+1];
155: struct log_data *ld;
156: char request_line[MAX_REQUEST_LINE+1];
157: BOOL finished; /* Done processing request. */
158: BOOL read_chunked;
159: BOOL write_chunked;
160: long range_start;
161: long range_end;
162: BOOL accept_ranges;
163: time_t if_range;
164: BOOL path_info_index;
165:
166: /* CGI parameters */
167: char query_str[MAX_REQUEST_LINE+1];
168: char extra_path_info[MAX_REQUEST_LINE+1];
169: str_list_t cgi_env;
170: str_list_t dynamic_heads;
171:
172: /* Dynamically (sever-side JS) generated HTML parameters */
173: FILE* fp;
174: char *cleanup_file[MAX_CLEANUPS];
175: BOOL sent_headers;
176: BOOL prev_write;
177:
178: /* webconfig.ini overrides */
179: char *error_dir;
180: char *cgi_dir;
181: char *realm;
182: } http_request_t;
183:
184: typedef struct {
185: SOCKET socket;
186: SOCKADDR_IN addr;
187: http_request_t req;
188: char host_ip[64];
189: char host_name[128]; /* Resolved remote host */
190: int http_ver; /* HTTP version. 0 = HTTP/0.9, 1=HTTP/1.0, 2=HTTP/1.1 */
191: BOOL finished; /* Do not accept any more imput from client */
192: user_t user;
193: int last_user_num;
194: time_t logon_time;
195: char username[LEN_NAME+1];
196: int last_js_user_num;
197:
198: /* JavaScript parameters */
199: JSRuntime* js_runtime;
200: JSContext* js_cx;
201: JSObject* js_glob;
202: JSObject* js_query;
203: JSObject* js_header;
204: JSObject* js_cookie;
205: JSObject* js_request;
206: js_branch_t js_branch;
207: subscan_t *subscan;
208:
209: /* Ring Buffer Stuff */
210: RingBuf outbuf;
211: sem_t output_thread_terminated;
212: int outbuf_write_initialized;
213: pthread_mutex_t outbuf_write;
214:
215: /* Client info */
216: client_t client;
217:
218: /* Synchronization stuff */
219: pthread_mutex_t struct_filled;
220: } http_session_t;
221:
222: enum {
223: HTTP_0_9
224: ,HTTP_1_0
225: ,HTTP_1_1
226: };
227: static char* http_vers[] = {
228: ""
229: ,"HTTP/1.0"
230: ,"HTTP/1.1"
231: ,NULL /* terminator */
232: };
233:
234: enum {
235: HTTP_HEAD
236: ,HTTP_GET
237: ,HTTP_POST
238: ,HTTP_OPTIONS
239: };
240:
241: static char* methods[] = {
242: "HEAD"
243: ,"GET"
244: ,"POST"
245: ,"OPTIONS"
246: ,NULL /* terminator */
247: };
248:
249: enum {
250: IS_STATIC
251: ,IS_CGI
252: ,IS_JS
253: ,IS_SSJS
254: };
255:
256: enum {
257: HEAD_DATE
258: ,HEAD_HOST
259: ,HEAD_IFMODIFIED
260: ,HEAD_LENGTH
261: ,HEAD_TYPE
262: ,HEAD_AUTH
263: ,HEAD_CONNECTION
264: ,HEAD_WWWAUTH
265: ,HEAD_STATUS
266: ,HEAD_ALLOW
267: ,HEAD_EXPIRES
268: ,HEAD_LASTMODIFIED
269: ,HEAD_LOCATION
270: ,HEAD_PRAGMA
271: ,HEAD_SERVER
272: ,HEAD_REFERER
273: ,HEAD_AGENT
274: ,HEAD_TRANSFER_ENCODING
275: ,HEAD_ACCEPT_RANGES
276: ,HEAD_CONTENT_RANGE
277: ,HEAD_RANGE
278: ,HEAD_IFRANGE
279: ,HEAD_COOKIE
280: };
281:
282: static struct {
283: int id;
284: char* text;
285: } headers[] = {
286: { HEAD_DATE, "Date" },
287: { HEAD_HOST, "Host" },
288: { HEAD_IFMODIFIED, "If-Modified-Since" },
289: { HEAD_LENGTH, "Content-Length" },
290: { HEAD_TYPE, "Content-Type" },
291: { HEAD_AUTH, "Authorization" },
292: { HEAD_CONNECTION, "Connection" },
293: { HEAD_WWWAUTH, "WWW-Authenticate" },
294: { HEAD_STATUS, "Status" },
295: { HEAD_ALLOW, "Allow" },
296: { HEAD_EXPIRES, "Expires" },
297: { HEAD_LASTMODIFIED, "Last-Modified" },
298: { HEAD_LOCATION, "Location" },
299: { HEAD_PRAGMA, "Pragma" },
300: { HEAD_SERVER, "Server" },
301: { HEAD_REFERER, "Referer" },
302: { HEAD_AGENT, "User-Agent" },
303: { HEAD_TRANSFER_ENCODING, "Transfer-Encoding" },
304: { HEAD_ACCEPT_RANGES, "Accept-Ranges" },
305: { HEAD_CONTENT_RANGE, "Content-Range" },
306: { HEAD_RANGE, "Range" },
307: { HEAD_IFRANGE, "If-Range" },
308: { HEAD_COOKIE, "Cookie" },
309: { -1, NULL /* terminator */ },
310: };
311:
312: /* Everything MOVED_TEMP and everything after is a magical internal redirect */
313: enum {
314: NO_LOCATION
315: ,MOVED_PERM
316: ,MOVED_TEMP
317: ,MOVED_STAT
318: };
319:
320: static char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
321: static char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
322:
323: static void respond(http_session_t * session);
324: static BOOL js_setup(http_session_t* session);
325: static char *find_last_slash(char *str);
326: static BOOL check_extra_path(http_session_t * session);
327: static BOOL exec_ssjs(http_session_t* session, char* script);
328: static BOOL ssjs_send_headers(http_session_t* session, int chunked);
329:
330: static time_t
331: sub_mkgmt(struct tm *tm)
332: {
333: int y, nleapdays;
334: time_t t;
335: /* days before the month */
336: static const unsigned short moff[12] = {
337: 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
338: };
339:
340: /*
341: * XXX: This code assumes the given time to be normalized.
342: * Normalizing here is impossible in case the given time is a leap
343: * second but the local time library is ignorant of leap seconds.
344: */
345:
346: /* minimal sanity checking not to access outside of the array */
347: if ((unsigned) tm->tm_mon >= 12)
348: return (time_t) -1;
349: if (tm->tm_year < 1970 - 1900)
350: return (time_t) -1;
351:
352: y = tm->tm_year + 1900 - (tm->tm_mon < 2);
353: nleapdays = y / 4 - y / 100 + y / 400 -
354: ((1970-1) / 4 - (1970-1) / 100 + (1970-1) / 400);
355: t = ((((time_t) (tm->tm_year - (1970 - 1900)) * 365 +
356: moff[tm->tm_mon] + tm->tm_mday - 1 + nleapdays) * 24 +
357: tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;
358:
359: return (t < 0 ? (time_t) -1 : t);
360: }
361:
362: time_t
363: time_gm(struct tm *tm)
364: {
365: time_t t, t2;
366: struct tm *tm2;
367: int sec;
368:
369: /* Do the first guess. */
370: if ((t = sub_mkgmt(tm)) == (time_t) -1)
371: return (time_t) -1;
372:
373: /* save value in case *tm is overwritten by gmtime() */
374: sec = tm->tm_sec;
375:
376: tm2 = gmtime(&t);
377: if ((t2 = sub_mkgmt(tm2)) == (time_t) -1)
378: return (time_t) -1;
379:
380: if (t2 < t || tm2->tm_sec != sec) {
381: /*
382: * Adjust for leap seconds.
383: *
384: * real time_t time
385: * |
386: * tm
387: * / ... (a) first sub_mkgmt() conversion
388: * t
389: * |
390: * tm2
391: * / ... (b) second sub_mkgmt() conversion
392: * t2
393: * --->time
394: */
395: /*
396: * Do the second guess, assuming (a) and (b) are almost equal.
397: */
398: t += t - t2;
399: tm2 = gmtime(&t);
400:
401: /*
402: * Either (a) or (b), may include one or two extra
403: * leap seconds. Try t, t + 2, t - 2, t + 1, and t - 1.
404: */
405: if (tm2->tm_sec == sec
406: || (t += 2, tm2 = gmtime(&t), tm2->tm_sec == sec)
407: || (t -= 4, tm2 = gmtime(&t), tm2->tm_sec == sec)
408: || (t += 3, tm2 = gmtime(&t), tm2->tm_sec == sec)
409: || (t -= 2, tm2 = gmtime(&t), tm2->tm_sec == sec))
410: ; /* found */
411: else {
412: /*
413: * Not found.
414: */
415: if (sec >= 60)
416: /*
417: * The given time is a leap second
418: * (sec 60 or 61), but the time library
419: * is ignorant of the leap second.
420: */
421: ; /* treat sec 60 as 59,
422: sec 61 as 0 of the next minute */
423: else
424: /* The given time may not be normalized. */
425: t++; /* restore t */
426: }
427: }
428:
429: return (t < 0 ? (time_t) -1 : t);
430: }
431:
432: static int lprintf(int level, char *fmt, ...)
433: {
434: va_list argptr;
435: char sbuf[1024];
436:
437: if(startup==NULL || startup->lputs==NULL)
438: return(0);
439:
440: va_start(argptr,fmt);
441: vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
442: sbuf[sizeof(sbuf)-1]=0;
443: va_end(argptr);
444: return(startup->lputs(startup->cbdata,level,sbuf));
445: }
446:
447: static int writebuf(http_session_t *session, const char *buf, size_t len)
448: {
449: size_t sent=0;
450: size_t avail;
451:
452: while(sent < len) {
453: avail=RingBufFree(&session->outbuf);
454: if(!avail) {
455: SLEEP(1);
456: continue;
457: }
458: if(avail > len-sent)
459: avail=len-sent;
460: sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
461: }
462: return(sent);
463: }
464:
465: static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
466: {
467: size_t sent=0;
468: int result;
469: int sel;
470: fd_set wr_set;
471: struct timeval tv;
472:
473: while(sent<len && *sock!=INVALID_SOCKET) {
474: FD_ZERO(&wr_set);
475: FD_SET(*sock,&wr_set);
476: /* Convert timeout from ms to sec/usec */
477: tv.tv_sec=startup->max_inactivity;
478: tv.tv_usec=0;
479: sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
480: switch(sel) {
481: case 1:
482: result=sendsocket(*sock,buf+sent,len-sent);
483: if(result==SOCKET_ERROR) {
484: if(ERROR_VALUE==ECONNRESET)
485: lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",*sock);
486: else if(ERROR_VALUE==ECONNABORTED)
487: lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",*sock);
488: else
489: lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE);
490: if(failed)
491: *failed=TRUE;
492: return(sent);
493: }
494: break;
495: case 0:
496: lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
497: if(failed)
498: *failed=TRUE;
499: return(sent);
500: case -1:
501: lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
502: if(failed)
503: *failed=TRUE;
504: return(sent);
505: }
506: sent+=result;
507: }
508: if(failed && sent<len)
509: *failed=TRUE;
510: return(sent);
511: }
512:
513: #ifdef _WINSOCKAPI_
514:
515: static WSADATA WSAData;
516: #define SOCKLIB_DESC WSAData.szDescription
517: static BOOL WSAInitialized=FALSE;
518:
519: static BOOL winsock_startup(void)
520: {
521: int status; /* Status Code */
522:
523: if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
524: lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
525: WSAInitialized=TRUE;
526: return (TRUE);
527: }
528:
529: lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
530: return (FALSE);
531: }
532:
533: #else /* No WINSOCK */
534:
535: #define winsock_startup() (TRUE)
536: #define SOCKLIB_DESC NULL
537:
538: #endif
539:
540: static void status(char* str)
541: {
542: if(startup!=NULL && startup->status!=NULL)
543: startup->status(startup->cbdata,str);
544: }
545:
546: static void update_clients(void)
547: {
548: if(startup!=NULL && startup->clients!=NULL)
549: startup->clients(startup->cbdata,active_clients);
550: }
551:
552: static void client_on(SOCKET sock, client_t* client, BOOL update)
553: {
554: if(startup!=NULL && startup->client_on!=NULL)
555: startup->client_on(startup->cbdata,TRUE,sock,client,update);
556: }
557:
558: static void client_off(SOCKET sock)
559: {
560: if(startup!=NULL && startup->client_on!=NULL)
561: startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
562: }
563:
564: static void thread_up(BOOL setuid)
565: {
566: thread_count++;
567: if(startup!=NULL && startup->thread_up!=NULL)
568: startup->thread_up(startup->cbdata,TRUE, setuid);
569: }
570:
571: static void thread_down(void)
572: {
573: if(thread_count>0)
574: thread_count--;
575: if(startup!=NULL && startup->thread_up!=NULL)
576: startup->thread_up(startup->cbdata,FALSE, FALSE);
577: }
578:
579: /*********************************************************************/
580: /* Adds an environment variable to the sessions cgi_env linked list */
581: /*********************************************************************/
582: static void add_env(http_session_t *session, const char *name,const char *value) {
583: char newname[129];
584: char *p;
585:
586: if(name==NULL || value==NULL) {
587: lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
588: return;
589: }
590: SAFECOPY(newname,name);
591:
592: for(p=newname;*p;p++) {
593: *p=toupper(*p);
594: if(*p=='-')
595: *p='_';
596: }
597: p=(char *)alloca(strlen(name)+strlen(value)+2);
598: if(p==NULL) {
599: lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
600: return;
601: }
602: #if 0 /* this is way too verbose for every request */
603: lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
604: #endif
605: sprintf(p,"%s=%s",newname,value);
606: strListPush(&session->req.cgi_env,p);
607: }
608:
609: /***************************************/
610: /* Initializes default CGI envirnoment */
611: /***************************************/
612: static void init_enviro(http_session_t *session) {
613: char str[128];
614:
615: add_env(session,"SERVER_SOFTWARE",VERSION_NOTICE);
616: sprintf(str,"%d",startup->port);
617: add_env(session,"SERVER_PORT",str);
618: add_env(session,"GATEWAY_INTERFACE","CGI/1.1");
619: if(!strcmp(session->host_name,session->host_ip))
620: add_env(session,"REMOTE_HOST",session->host_name);
621: add_env(session,"REMOTE_ADDR",session->host_ip);
622: add_env(session,"REQUEST_URI",session->req.request_line);
623: }
624:
625: /*
626: * Sends string str to socket sock... returns number of bytes written, or 0 on an error
627: * Can not close the socket since it can not set it to INVALID_SOCKET
628: */
629: static int bufprint(http_session_t *session, const char *str)
630: {
631: int len;
632:
633: len=strlen(str);
634: return(writebuf(session,str,len));
635: }
636:
637: /**********************************************************/
638: /* Converts a month name/abbr to the 0-based month number */
639: /* ToDo: This probobly exists somewhere else already */
640: /**********************************************************/
641: static int getmonth(char *mon)
642: {
643: int i;
644: for(i=0;i<12;i++)
645: if(!stricmp(mon,months[i]))
646: return(i);
647:
648: return 0;
649: }
650:
651: /*******************************************************************/
652: /* Converts a date string in any of the common formats to a time_t */
653: /*******************************************************************/
654: static time_t decode_date(char *date)
655: {
656: struct tm ti;
657: char *token;
658: char *last;
659: time_t t;
660:
661: ti.tm_sec=0; /* seconds (0 - 60) */
662: ti.tm_min=0; /* minutes (0 - 59) */
663: ti.tm_hour=0; /* hours (0 - 23) */
664: ti.tm_mday=1; /* day of month (1 - 31) */
665: ti.tm_mon=0; /* month of year (0 - 11) */
666: ti.tm_year=0; /* year - 1900 */
667: ti.tm_isdst=0; /* is summer time in effect? */
668:
669: token=strtok_r(date,",",&last);
670: if(token==NULL)
671: return(0);
672: /* This probobly only needs to be 9, but the extra one is for luck. */
673: if(strlen(date)>15) {
674: /* asctime() */
675: /* Toss away week day */
676: token=strtok_r(date," ",&last);
677: if(token==NULL)
678: return(0);
679: token=strtok_r(NULL," ",&last);
680: if(token==NULL)
681: return(0);
682: ti.tm_mon=getmonth(token);
683: token=strtok_r(NULL," ",&last);
684: if(token==NULL)
685: return(0);
686: ti.tm_mday=atoi(token);
687: token=strtok_r(NULL,":",&last);
688: if(token==NULL)
689: return(0);
690: ti.tm_hour=atoi(token);
691: token=strtok_r(NULL,":",&last);
692: if(token==NULL)
693: return(0);
694: ti.tm_min=atoi(token);
695: token=strtok_r(NULL," ",&last);
696: if(token==NULL)
697: return(0);
698: ti.tm_sec=atoi(token);
699: token=strtok_r(NULL,"",&last);
700: if(token==NULL)
701: return(0);
702: ti.tm_year=atoi(token)-1900;
703: }
704: else {
705: /* RFC 1123 or RFC 850 */
706: token=strtok_r(NULL," -",&last);
707: if(token==NULL)
708: return(0);
709: ti.tm_mday=atoi(token);
710: token=strtok_r(NULL," -",&last);
711: if(token==NULL)
712: return(0);
713: ti.tm_mon=getmonth(token);
714: token=strtok_r(NULL," ",&last);
715: if(token==NULL)
716: return(0);
717: ti.tm_year=atoi(token);
718: token=strtok_r(NULL,":",&last);
719: if(token==NULL)
720: return(0);
721: ti.tm_hour=atoi(token);
722: token=strtok_r(NULL,":",&last);
723: if(token==NULL)
724: return(0);
725: ti.tm_min=atoi(token);
726: token=strtok_r(NULL," ",&last);
727: if(token==NULL)
728: return(0);
729: ti.tm_sec=atoi(token);
730: if(ti.tm_year>1900)
731: ti.tm_year -= 1900;
732: }
733:
734: t=time_gm(&ti);
735: return(t);
736: }
737:
738: static SOCKET open_socket(int type)
739: {
740: char error[256];
741: SOCKET sock;
742:
743: sock=socket(AF_INET, type, IPPROTO_IP);
744: if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL)
745: startup->socket_open(startup->cbdata,TRUE);
746: if(sock!=INVALID_SOCKET) {
747: if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
748: lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
749:
750: sockets++;
751: }
752: return(sock);
753: }
754:
755: static int close_socket(SOCKET *sock)
756: {
757: int result;
758:
759: if(sock==NULL || *sock==INVALID_SOCKET)
760: return(-1);
761:
762: /* required to ensure all data is send when SO_LINGER is off (Not functional on Win32) */
763: shutdown(*sock,SHUT_RDWR);
764: result=closesocket(*sock);
765: *sock=INVALID_SOCKET;
766: if(startup!=NULL && startup->socket_open!=NULL) {
767: startup->socket_open(startup->cbdata,FALSE);
768: }
769: sockets--;
770: if(result!=0) {
771: if(ERROR_VALUE!=ENOTSOCK)
772: lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE);
773: }
774:
775: return(result);
776: }
777:
778: /* Waits for the outbuf to drain */
779: static void drain_outbuf(http_session_t * session)
780: {
781: if(session->socket==INVALID_SOCKET)
782: return;
783: /* Force the output thread to go NOW */
784: sem_post(&(session->outbuf.highwater_sem));
785: /* ToDo: This should probobly timeout eventually... */
786: while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET)
787: SLEEP(1);
788: /* Lock the mutex to ensure data has been sent */
789: while(session->socket!=INVALID_SOCKET && !session->outbuf_write_initialized)
790: SLEEP(1);
791: if(session->socket==INVALID_SOCKET)
792: return;
793: pthread_mutex_lock(&session->outbuf_write); /* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */
794: pthread_mutex_unlock(&session->outbuf_write);
795: }
796:
797: /**************************************************/
798: /* End of a single request... */
799: /* This is called at the end of EVERY request */
800: /* Log the request */
801: /* Free request-specific data ie: dynamic stuff */
802: /* Close socket unless it's being kept alive */
803: /* If the socket is closed, the session is done */
804: /**************************************************/
805: static void close_request(http_session_t * session)
806: {
807: time_t now;
808: int i;
809:
810: if(session->req.write_chunked) {
811: drain_outbuf(session);
812: session->req.write_chunked=0;
813: writebuf(session,"0\r\n",3);
814: if(session->req.dynamic==IS_SSJS)
815: ssjs_send_headers(session,FALSE);
816: else
817: /* Non-ssjs isn't capable of generating headers during execution */
818: writebuf(session, newline, 2);
819: }
820:
821: /* Force the output thread to go NOW */
822: sem_post(&(session->outbuf.highwater_sem));
823:
824: if(session->req.ld!=NULL) {
825: now=time(NULL);
826: localtime_r(&now,&session->req.ld->completed);
827: listPushNode(&log_list,session->req.ld);
828: session->req.ld=NULL;
829: }
830:
831: strListFree(&session->req.headers);
832: strListFree(&session->req.dynamic_heads);
833: strListFree(&session->req.cgi_env);
834: FREE_AND_NULL(session->req.post_data);
835: FREE_AND_NULL(session->req.error_dir);
836: FREE_AND_NULL(session->req.cgi_dir);
837: FREE_AND_NULL(session->req.realm);
838: /*
839: * This causes all active http_session_threads to terminate.
840: */
841: if((!session->req.keep_alive) || terminate_server) {
842: drain_outbuf(session);
843: close_socket(&session->socket);
844: }
845: if(session->socket==INVALID_SOCKET)
846: session->finished=TRUE;
847:
848: if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
849: JS_GC(session->js_cx);
850: }
851: if(session->subscan!=NULL)
852: putmsgptrs(&scfg, session->user.number, session->subscan);
853:
854: if(session->req.fp!=NULL)
855: fclose(session->req.fp);
856:
857: for(i=0;i<MAX_CLEANUPS;i++) {
858: if(session->req.cleanup_file[i]!=NULL) {
859: if(!(startup->options&WEB_OPT_DEBUG_SSJS))
860: remove(session->req.cleanup_file[i]);
861: free(session->req.cleanup_file[i]);
862: }
863: }
864:
865: memset(&session->req,0,sizeof(session->req));
866: }
867:
868: static int get_header_type(char *header)
869: {
870: int i;
871: for(i=0; headers[i].text!=NULL; i++) {
872: if(!stricmp(header,headers[i].text)) {
873: return(headers[i].id);
874: }
875: }
876: return(-1);
877: }
878:
879: /* Opposite of get_header_type() */
880: static char *get_header(int id)
881: {
882: int i;
883: if(headers[id].id==id)
884: return(headers[id].text);
885:
886: for(i=0;headers[i].text!=NULL;i++) {
887: if(headers[i].id==id) {
888: return(headers[i].text);
889: }
890: }
891: return(NULL);
892: }
893:
894: static const char* unknown_mime_type="application/octet-stream";
895:
896: static const char* get_mime_type(char *ext)
897: {
898: uint i;
899:
900: if(ext==NULL || mime_types==NULL)
901: return(unknown_mime_type);
902:
903: for(i=0;mime_types[i]!=NULL;i++)
904: if(stricmp(ext+1,mime_types[i]->name)==0)
905: return(mime_types[i]->value);
906:
907: return(unknown_mime_type);
908: }
909:
910: static BOOL get_cgi_handler(char* cmdline, size_t maxlen)
911: {
912: char fname[MAX_PATH+1];
913: char* ext;
914: size_t i;
915:
916: if(cgi_handlers==NULL || (ext=getfext(cmdline))==NULL)
917: return(FALSE);
918:
919: for(i=0;cgi_handlers[i]!=NULL;i++) {
920: if(stricmp(cgi_handlers[i]->name, ext+1)==0) {
921: SAFECOPY(fname,cmdline);
922: safe_snprintf(cmdline,maxlen,"%s %s",cgi_handlers[i]->value,fname);
923: return(TRUE);
924: }
925: }
926: return(FALSE);
927: }
928:
929: static BOOL get_xjs_handler(char* ext, http_session_t* session)
930: {
931: size_t i;
932:
933: if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
934: return(FALSE);
935:
936: for(i=0;xjs_handlers[i]!=NULL;i++) {
937: if(stricmp(xjs_handlers[i]->name, ext+1)==0) {
938: if(getfname(xjs_handlers[i]->value)==xjs_handlers[i]->value) /* no path specified */
939: SAFEPRINTF2(session->req.xjs_handler,"%s%s",scfg.exec_dir,xjs_handlers[i]->value);
940: else
941: SAFECOPY(session->req.xjs_handler,xjs_handlers[i]->value);
942: return(TRUE);
943: }
944: }
945: return(FALSE);
946: }
947:
948: /* This function appends append plus a newline IF the final dst string would have a length less than maxlen */
949: static void safecat(char *dst, const char *append, size_t maxlen) {
950: size_t dstlen,appendlen;
951: dstlen=strlen(dst);
952: appendlen=strlen(append);
953: if(dstlen+appendlen+2 < maxlen) {
954: strcat(dst,append);
955: strcat(dst,newline);
956: }
957: }
958:
959: /*************************************************/
960: /* Sends headers for the reply. */
961: /* HTTP/0.9 doesn't use headers, so just returns */
962: /*************************************************/
963: static BOOL send_headers(http_session_t *session, const char *status, int chunked)
964: {
965: int ret;
966: BOOL send_file=TRUE;
967: time_t ti;
968: size_t idx;
969: const char *status_line;
970: struct stat stats;
971: struct tm tm;
972: char *headers;
973: char header[MAX_REQUEST_LINE+1];
974:
975: if(session->socket==INVALID_SOCKET) {
976: session->req.sent_headers=TRUE;
977: return(FALSE);
978: }
979: lprintf(LOG_DEBUG,"%04d Request resolved to: %s"
980: ,session->socket,session->req.physical_path);
981: if(session->http_ver <= HTTP_0_9) {
982: session->req.sent_headers=TRUE;
983: if(session->req.ld != NULL)
984: session->req.ld->status=atoi(status);
985: return(TRUE);
986: }
987: headers=alloca(MAX_HEADERS_SIZE);
988: if(headers==NULL) {
989: lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
990: return(FALSE);
991: }
992: *headers=0;
993: if(!session->req.sent_headers) {
994: session->req.sent_headers=TRUE;
995: status_line=status;
996: ret=stat(session->req.physical_path,&stats);
997: if(session->req.method==HTTP_OPTIONS)
998: ret=-1;
999: if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
1000: status_line="304 Not Modified";
1001: ret=-1;
1002: send_file=FALSE;
1003: }
1004: if(!ret && session->req.if_range && (stats.st_mtime > session->req.if_range || session->req.dynamic)) {
1005: status_line="200 OK";
1006: session->req.range_start=0;
1007: session->req.range_end=0;
1008: }
1009: if(session->req.send_location==MOVED_PERM) {
1010: status_line=error_301;
1011: ret=-1;
1012: send_file=FALSE;
1013: }
1014: if(session->req.send_location==MOVED_TEMP) {
1015: status_line=error_302;
1016: ret=-1;
1017: send_file=FALSE;
1018: }
1019:
1020: if(session->req.ld!=NULL)
1021: session->req.ld->status=atoi(status_line);
1022:
1023: /* Status-Line */
1024: safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
1025:
1026: lprintf(LOG_DEBUG,"%04d Result: %s",session->socket,header);
1027:
1028: safecat(headers,header,MAX_HEADERS_SIZE);
1029:
1030: /* General Headers */
1031: ti=time(NULL);
1032: if(gmtime_r(&ti,&tm)==NULL)
1033: memset(&tm,0,sizeof(tm));
1034: safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
1035: ,get_header(HEAD_DATE)
1036: ,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
1037: ,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
1038: safecat(headers,header,MAX_HEADERS_SIZE);
1039: if(session->req.keep_alive) {
1040: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
1041: safecat(headers,header,MAX_HEADERS_SIZE);
1042: }
1043: else {
1044: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
1045: safecat(headers,header,MAX_HEADERS_SIZE);
1046: }
1047:
1048: /* Response Headers */
1049: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
1050: safecat(headers,header,MAX_HEADERS_SIZE);
1051:
1052: /* Entity Headers */
1053: if(session->req.dynamic) {
1054: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
1055: safecat(headers,header,MAX_HEADERS_SIZE);
1056: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"none");
1057: safecat(headers,header,MAX_HEADERS_SIZE);
1058: }
1059: else {
1060: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
1061: safecat(headers,header,MAX_HEADERS_SIZE);
1062: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"bytes");
1063: safecat(headers,header,MAX_HEADERS_SIZE);
1064: }
1065:
1066: if(session->req.send_location) {
1067: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
1068: safecat(headers,header,MAX_HEADERS_SIZE);
1069: }
1070:
1071: if(chunked) {
1072: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TRANSFER_ENCODING),"Chunked");
1073: safecat(headers,header,MAX_HEADERS_SIZE);
1074: }
1075:
1076: /* DO NOT send a content-length for chunked */
1077: if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
1078: if(ret) {
1079: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
1080: safecat(headers,header,MAX_HEADERS_SIZE);
1081: }
1082: else {
1083: if((session->req.range_start || session->req.range_end) && atoi(status_line)==206) {
1084: safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),session->req.range_end-session->req.range_start+1);
1085: safecat(headers,header,MAX_HEADERS_SIZE);
1086: }
1087: else {
1088: safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
1089: safecat(headers,header,MAX_HEADERS_SIZE);
1090: }
1091: }
1092: }
1093:
1094: if(!ret && !session->req.dynamic) {
1095: safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
1096: safecat(headers,header,MAX_HEADERS_SIZE);
1097: gmtime_r(&stats.st_mtime,&tm);
1098: safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
1099: ,get_header(HEAD_LASTMODIFIED)
1100: ,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
1101: ,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
1102: safecat(headers,header,MAX_HEADERS_SIZE);
1103: }
1104:
1105: if(session->req.range_start || session->req.range_end) {
1106: switch(atoi(status_line)) {
1107: case 206: /* Partial reply */
1108: 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);
1109: safecat(headers,header,MAX_HEADERS_SIZE);
1110: break;
1111: default:
1112: safe_snprintf(header,sizeof(header),"%s: *",get_header(HEAD_CONTENT_RANGE));
1113: safecat(headers,header,MAX_HEADERS_SIZE);
1114: break;
1115: }
1116: }
1117: }
1118:
1119: if(session->req.dynamic) {
1120: /* Dynamic headers */
1121: /* Set up environment */
1122: for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
1123: safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
1124: /* free() the headers so they don't get sent again if more are sent at the end of the request (chunked) */
1125: strListFreeStrings(session->req.dynamic_heads);
1126: }
1127:
1128: safecat(headers,"",MAX_HEADERS_SIZE);
1129: send_file = (bufprint(session,headers) && send_file);
1130: drain_outbuf(session);
1131: session->req.write_chunked=chunked;
1132: return(send_file);
1133: }
1134:
1135: static int sock_sendfile(http_session_t *session,char *path,unsigned long start, unsigned long end)
1136: {
1137: int file;
1138: int ret=0;
1139: int i;
1140: char buf[2048]; /* Input buffer */
1141: unsigned long remain;
1142:
1143: if(startup->options&WEB_OPT_DEBUG_TX)
1144: lprintf(LOG_DEBUG,"%04d Sending %s",session->socket,path);
1145: if((file=open(path,O_RDONLY|O_BINARY))==-1)
1146: lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",session->socket,errno,path);
1147: else {
1148: if(start || end) {
1149: if(lseek(file, start, SEEK_SET)==-1) {
1150: lprintf(LOG_WARNING,"%04d !ERROR %d seeking to position %lu in %s",session->socket,ERROR_VALUE,start,path);
1151: return(0);
1152: }
1153: remain=end-start+1;
1154: }
1155: else {
1156: remain=-1L;
1157: }
1158: while((i=read(file, buf, remain>sizeof(buf)?sizeof(buf):remain))>0) {
1159: if(writebuf(session,buf,i)!=i) {
1160: lprintf(LOG_WARNING,"%04d !ERROR sending %s",session->socket,path);
1161: return(0);
1162: }
1163: ret+=i;
1164: remain-=i;
1165: }
1166: close(file);
1167: }
1168: return(ret);
1169: }
1170:
1171: /********************************************************/
1172: /* Sends a specified error message, closes the request, */
1173: /* and marks the session to be closed */
1174: /********************************************************/
1175: static void send_error(http_session_t * session, const char* message)
1176: {
1177: char error_code[4];
1178: struct stat sb;
1179: char sbuf[MAX_PATH+1];
1180: char sbuf2[MAX_PATH+1];
1181: BOOL sent_ssjs=FALSE;
1182:
1183: if(session->socket==INVALID_SOCKET)
1184: return;
1185: session->req.if_modified_since=0;
1186: lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1187: session->req.keep_alive=FALSE;
1188: session->req.send_location=NO_LOCATION;
1189: SAFECOPY(error_code,message);
1190: SAFECOPY(session->req.status,message);
1191: if(atoi(error_code)<500) {
1192: /*
1193: * Attempt to run SSJS error pages
1194: * If this fails, do the standard error page instead,
1195: * ie: Don't "upgrade" to a 500 error
1196: */
1197:
1198: if(session->req.error_dir) {
1199: /* We have a custom error directory from webctrl.ini look there first */
1200: sprintf(sbuf,"%s%s%s",session->req.error_dir,error_code,startup->ssjs_ext);
1201: if(stat(sbuf,&sb)) {
1202: /* No custom .ssjs error message... check for custom .html */
1203: sprintf(sbuf2,"%s%s.html",session->req.error_dir,error_code);
1204: if(stat(sbuf2,&sb)) {
1205: /* Nope, no custom .html error either, check for global ssjs one */
1206: sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext);
1207: }
1208: }
1209: }
1210: else
1211: sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext);
1212: if(!stat(sbuf,&sb)) {
1213: lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket);
1214: session->req.dynamic=IS_SSJS;
1215: if(js_setup(session)) {
1216: sent_ssjs=exec_ssjs(session,sbuf);
1217: if(sent_ssjs) {
1218: int snt=0;
1219:
1220: lprintf(LOG_INFO,"%04d Sending generated error page",session->socket);
1221: snt=sock_sendfile(session,session->req.physical_path,0,0);
1222: if(snt<0)
1223: snt=0;
1224: if(session->req.ld!=NULL)
1225: session->req.ld->size=snt;
1226: }
1227: else
1228: session->req.dynamic=IS_STATIC;
1229: }
1230: else
1231: session->req.dynamic=IS_STATIC;
1232: }
1233: }
1234: if(!sent_ssjs) {
1235: if(session->req.error_dir) {
1236: sprintf(session->req.physical_path,"%s%s.html",session->req.error_dir,error_code);
1237: if(stat(session->req.physical_path,&sb))
1238: sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
1239: }
1240: else
1241: sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code,startup->ssjs_ext);
1242: session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
1243: send_headers(session,message,FALSE);
1244: if(!stat(session->req.physical_path,&sb)) {
1245: int snt=0;
1246: snt=sock_sendfile(session,session->req.physical_path,0,0);
1247: if(snt<0)
1248: snt=0;
1249: if(session->req.ld!=NULL)
1250: session->req.ld->size=snt;
1251: }
1252: else {
1253: lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
1254: ,session->socket,session->req.physical_path);
1255: safe_snprintf(sbuf,sizeof(sbuf)
1256: ,"<HTML><HEAD><TITLE>%s Error</TITLE></HEAD>"
1257: "<BODY><H1>%s Error</H1><BR><H3>In addition, "
1258: "I can't seem to find the %s error file</H3><br>"
1259: "please notify <a href=\"mailto:sysop@%s\">"
1260: "%s</a></BODY></HTML>"
1261: ,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
1262: bufprint(session,sbuf);
1263: if(session->req.ld!=NULL)
1264: session->req.ld->size=strlen(sbuf);
1265: }
1266: }
1267: drain_outbuf(session);
1268: session->req.finished=TRUE;
1269: }
1270:
1271: void http_logon(http_session_t * session, user_t *usr)
1272: {
1273: char str[128];
1274:
1275: if(usr==NULL)
1276: getuserdat(&scfg, &session->user);
1277: else
1278: session->user=*usr;
1279:
1280: if(session->user.number==session->last_user_num)
1281: return;
1282:
1283: lprintf(LOG_DEBUG,"%04d HTTP Logon (user #%d)",session->socket,session->user.number);
1284:
1285: if(session->subscan!=NULL)
1286: getmsgptrs(&scfg,session->user.number,session->subscan);
1287:
1288: session->logon_time=time(NULL);
1289: if(session->user.number==0)
1290: SAFECOPY(session->username,unknown);
1291: else {
1292: SAFECOPY(session->username,session->user.alias);
1293: /* Adjust Connect and host */
1294: putuserrec(&scfg,session->user.number,U_MODEM,LEN_MODEM,"HTTP");
1295: putuserrec(&scfg,session->user.number,U_COMP,LEN_COMP,session->host_name);
1296: putuserrec(&scfg,session->user.number,U_NOTE,LEN_NOTE,session->host_ip);
1297: putuserrec(&scfg,session->user.number,U_LOGONTIME,0,ultoa(session->logon_time,str,16));
1298: }
1299: session->client.user=session->username;
1300: client_on(session->socket, &session->client, /* update existing client record? */TRUE);
1301:
1302: session->last_user_num=session->user.number;
1303: }
1304:
1305: void http_logoff(http_session_t* session, SOCKET socket, int line)
1306: {
1307: if(session->last_user_num<=0)
1308: return;
1309:
1310: lprintf(LOG_DEBUG,"%04d HTTP Logoff (user #%d) from line %d"
1311: ,socket,session->user.number, line);
1312:
1313: SAFECOPY(session->username,unknown);
1314: logoutuserdat(&scfg, &session->user, time(NULL), session->logon_time);
1315: memset(&session->user,0,sizeof(session->user));
1316: session->last_user_num=session->user.number;
1317: }
1318:
1319: BOOL http_checkuser(http_session_t * session)
1320: {
1321: if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
1322: if(session->last_js_user_num==session->user.number)
1323: return(TRUE);
1324: lprintf(LOG_INFO,"%04d JavaScript: Initializing User Objects",session->socket);
1325: if(session->user.number>0) {
1326: if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
1327: ,NULL /* ftp index file */, session->subscan /* subscan */)) {
1328: lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
1329: send_error(session,"500 Error initializing JavaScript User Objects");
1330: return(FALSE);
1331: }
1332: }
1333: else {
1334: if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, NULL
1335: ,NULL /* ftp index file */, session->subscan /* subscan */)) {
1336: lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript User Objects",session->socket);
1337: send_error(session,"500 Error initializing JavaScript User Objects");
1338: return(FALSE);
1339: }
1340: }
1341: session->last_js_user_num=session->user.number;
1342: }
1343: return(TRUE);
1344: }
1345:
1346: static BOOL check_ars(http_session_t * session)
1347: {
1348: char *username;
1349: char *password;
1350: char *last;
1351: uchar *ar;
1352: BOOL authorized;
1353: char auth_req[MAX_REQUEST_LINE+1];
1354: int i;
1355: user_t thisuser;
1356:
1357: if(session->req.auth[0]==0) {
1358: /* No authentication information... */
1359: if(session->last_user_num!=0) {
1360: if(session->last_user_num>0)
1361: http_logoff(session,session->socket,__LINE__);
1362: session->user.number=0;
1363: http_logon(session,NULL);
1364: }
1365: if(!http_checkuser(session))
1366: return(FALSE);
1367: if(session->req.ars[0]) {
1368: /* There *IS* an ARS string ie: Auth is required */
1369: if(startup->options&WEB_OPT_DEBUG_RX)
1370: lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket);
1371: return(FALSE);
1372: }
1373: /* No auth required, allow */
1374: return(TRUE);
1375: }
1376: SAFECOPY(auth_req,session->req.auth);
1377:
1378: username=strtok_r(auth_req,":",&last);
1379: if(username)
1380: password=strtok_r(NULL,":",&last);
1381: else {
1382: username="";
1383: password="";
1384: }
1385: /* Require a password */
1386: if(password==NULL)
1387: password="";
1388: i=matchuser(&scfg, username, FALSE);
1389: if(i==0) {
1390: if(session->last_user_num!=0) {
1391: if(session->last_user_num>0)
1392: http_logoff(session,session->socket,__LINE__);
1393: session->user.number=0;
1394: http_logon(session,NULL);
1395: }
1396: if(!http_checkuser(session))
1397: return(FALSE);
1398: if(scfg.sys_misc&SM_ECHO_PW)
1399: lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s"
1400: ,session->socket,username,password);
1401: else
1402: lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s"
1403: ,session->socket,username);
1404: return(FALSE);
1405: }
1406: thisuser.number=i;
1407: getuserdat(&scfg, &thisuser);
1408: if(thisuser.pass[0] && stricmp(thisuser.pass,password)) {
1409: if(session->last_user_num!=0) {
1410: if(session->last_user_num>0)
1411: http_logoff(session,session->socket,__LINE__);
1412: session->user.number=0;
1413: http_logon(session,NULL);
1414: }
1415: if(!http_checkuser(session))
1416: return(FALSE);
1417: /* Should go to the hack log? */
1418: if(scfg.sys_misc&SM_ECHO_PW)
1419: lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
1420: ,session->socket,username,password,thisuser.pass);
1421: else
1422: lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
1423: ,session->socket,username);
1424: #ifdef _WIN32
1425: if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE))
1426: PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
1427: #endif
1428: return(FALSE);
1429: }
1430:
1431: if(i != session->last_user_num) {
1432: http_logoff(session,session->socket,__LINE__);
1433: session->user.number=i;
1434: http_logon(session,&thisuser);
1435: }
1436: if(!http_checkuser(session))
1437: return(FALSE);
1438:
1439: if(session->req.ld!=NULL) {
1440: FREE_AND_NULL(session->req.ld->user);
1441: /* FREE()d in http_logging_thread */
1442: session->req.ld->user=strdup(username);
1443: }
1444:
1445: ar = arstr(NULL,session->req.ars,&scfg);
1446: authorized=chk_ar(&scfg,ar,&session->user);
1447: if(ar!=NULL && ar!=nular)
1448: FREE_AND_NULL(ar);
1449:
1450: if(authorized) {
1451: add_env(session,"AUTH_TYPE","Basic");
1452: /* Should use real name if set to do so somewhere ToDo */
1453: add_env(session,"REMOTE_USER",session->user.alias);
1454:
1455: return(TRUE);
1456: }
1457:
1458: /* Should go to the hack log? */
1459: lprintf(LOG_WARNING,"%04d !AUTHORIZATION FAILURE for user %s, ARS: %s"
1460: ,session->socket,username,session->req.ars);
1461:
1462: #ifdef _WIN32
1463: if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE))
1464: PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
1465: #endif
1466:
1467: return(FALSE);
1468: }
1469:
1470: static named_string_t** read_ini_list(char* fname, char* section, char* desc
1471: ,named_string_t** list)
1472: {
1473: char path[MAX_PATH+1];
1474: size_t i;
1475: FILE* fp;
1476:
1477: list=iniFreeNamedStringList(list);
1478:
1479: iniFileName(path,sizeof(path),scfg.ctrl_dir,fname);
1480:
1481: if((fp=iniOpenFile(path, /* create? */FALSE))!=NULL) {
1482: list=iniReadNamedStringList(fp,section);
1483: iniCloseFile(fp);
1484: COUNT_LIST_ITEMS(list,i);
1485: if(i)
1486: lprintf(LOG_DEBUG,"Read %u %s from %s",i,desc,path);
1487: }
1488:
1489: return(list);
1490: }
1491:
1492: static int sockreadline(http_session_t * session, char *buf, size_t length)
1493: {
1494: char ch;
1495: int sel;
1496: DWORD i;
1497: DWORD chucked=0;
1498: fd_set rd_set;
1499: struct timeval tv;
1500:
1501: for(i=0;TRUE;) {
1502: if(session->socket==INVALID_SOCKET)
1503: return(-1);
1504: FD_ZERO(&rd_set);
1505: FD_SET(session->socket,&rd_set);
1506: /* Convert timeout from ms to sec/usec */
1507: tv.tv_sec=startup->max_inactivity;
1508: tv.tv_usec=0;
1509: sel=select(session->socket+1,&rd_set,NULL,NULL,&tv);
1510: switch(sel) {
1511: case 1:
1512: break;
1513: case -1:
1514: close_socket(&session->socket);
1515: lprintf(LOG_DEBUG,"%04d !ERROR %d selecting socket for read",session->socket,ERROR_VALUE);
1516: return(-1);
1517: default:
1518: /* Timeout */
1519: lprintf(LOG_WARNING,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity);
1520: return(-1);
1521: }
1522:
1523: switch(recv(session->socket, &ch, 1, 0)) {
1524: case -1:
1525: if(ERROR_VALUE!=EAGAIN) {
1526: if(startup->options&WEB_OPT_DEBUG_RX)
1527: lprintf(LOG_DEBUG,"%04d !ERROR %d receiving on socket",session->socket,ERROR_VALUE);
1528: close_socket(&session->socket);
1529: return(-1);
1530: }
1531: break;
1532: case 0:
1533: /* Socket has been closed */
1534: close_socket(&session->socket);
1535: return(-1);
1536: }
1537:
1538: if(ch=='\n')
1539: break;
1540:
1541: if(i<length)
1542: buf[i++]=ch;
1543: else
1544: chucked++;
1545: }
1546:
1547: /* Terminate at length if longer */
1548: if(i>length)
1549: i=length;
1550:
1551: if(i>0 && buf[i-1]=='\r')
1552: buf[--i]=0;
1553: else
1554: buf[i]=0;
1555:
1556: if(startup->options&WEB_OPT_DEBUG_RX) {
1557: lprintf(LOG_DEBUG,"%04d RX: %s",session->socket,buf);
1558: if(chucked)
1559: lprintf(LOG_DEBUG,"%04d Long header, chucked %d bytes",session->socket,chucked);
1560: }
1561: return(i);
1562: }
1563:
1564: #if defined(_WIN32)
1565: static int pipereadline(HANDLE pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len)
1566: #else
1567: static int pipereadline(int pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len)
1568: #endif
1569: {
1570: char ch;
1571: DWORD i;
1572: int ret=0;
1573: #ifndef _WIN32
1574: struct timeval tv={0,0};
1575: fd_set read_set;
1576: #endif
1577:
1578: /* Terminate buffers */
1579: if(buf != NULL)
1580: buf[0]=0;
1581: if(fullbuf != NULL)
1582: fullbuf[0]=0;
1583: for(i=0;TRUE;) {
1584: #if defined(_WIN32)
1585: ret=0;
1586: ReadFile(pipe, &ch, 1, (DWORD*)&ret, NULL);
1587: #else
1588: tv.tv_sec=startup->max_cgi_inactivity;
1589: tv.tv_usec=0;
1590: FD_ZERO(&read_set);
1591: FD_SET(pipe, &read_set);
1592: if(select(pipe+1, &read_set, NULL, NULL, &tv)<1)
1593: return(-1);
1594: ret=read(pipe, &ch, 1);
1595: #endif
1596: if(ret==1) {
1597: if(fullbuf != NULL && i < (fullbuf_len-1)) {
1598: fullbuf[i]=ch;
1599: fullbuf[i+1]=0;
1600: }
1601:
1602: if(ch=='\n')
1603: break;
1604:
1605: if(buf != NULL && i<length)
1606: buf[i]=ch;
1607:
1608: i++;
1609: }
1610: else
1611: return(-1);
1612: }
1613:
1614: /* Terminate at length if longer */
1615: if(i>length)
1616: i=length;
1617:
1618: if(i>0 && buf != NULL && buf[i-1]=='\r')
1619: buf[--i]=0;
1620: else {
1621: if(buf != NULL)
1622: buf[i]=0;
1623: }
1624:
1625: return(i);
1626: }
1627:
1628: int recvbufsocket(SOCKET *sock, char *buf, long count)
1629: {
1630: int rd=0;
1631: int i;
1632: time_t start;
1633:
1634: if(count<1) {
1635: errno=ERANGE;
1636: return(0);
1637: }
1638:
1639: while(rd<count && socket_check(*sock,NULL,NULL,startup->max_inactivity*1000)) {
1640: i=recv(*sock,buf+rd,count-rd,0);
1641: switch(i) {
1642: case -1:
1643: if(ERROR_VALUE!=EAGAIN)
1644: close_socket(sock);
1645: case 0:
1646: close_socket(sock);
1647: *buf=0;
1648: return(0);
1649: }
1650:
1651: rd+=i;
1652: start=time(NULL);
1653: }
1654:
1655: if(rd==count) {
1656: return(rd);
1657: }
1658:
1659: *buf=0;
1660: return(0);
1661: }
1662:
1663: static void unescape(char *p)
1664: {
1665: char * dst;
1666: char code[3];
1667:
1668: dst=p;
1669: for(;*p;p++) {
1670: if(*p=='%' && isxdigit(*(p+1)) && isxdigit(*(p+2))) {
1671: sprintf(code,"%.2s",p+1);
1672: *(dst++)=(char)strtol(code,NULL,16);
1673: p+=2;
1674: }
1675: else {
1676: if(*p=='+') {
1677: *(dst++)=' ';
1678: }
1679: else {
1680: *(dst++)=*p;
1681: }
1682: }
1683: }
1684: *(dst)=0;
1685: }
1686:
1687: static void js_add_queryval(http_session_t * session, char *key, char *value)
1688: {
1689: JSObject* keyarray;
1690: jsval val;
1691: jsuint len;
1692: int alen;
1693:
1694: /* Return existing object if it's already been created */
1695: if(JS_GetProperty(session->js_cx,session->js_query,key,&val) && val!=JSVAL_VOID) {
1696: keyarray = JSVAL_TO_OBJECT(val);
1697: alen=-1;
1698: }
1699: else {
1700: keyarray = JS_NewArrayObject(session->js_cx, 0, NULL);
1701: if(!JS_DefineProperty(session->js_cx, session->js_query, key, OBJECT_TO_JSVAL(keyarray)
1702: , NULL, NULL, JSPROP_ENUMERATE))
1703: return;
1704: alen=0;
1705: }
1706:
1707: if(alen==-1) {
1708: if(JS_GetArrayLength(session->js_cx, keyarray, &len)==JS_FALSE)
1709: return;
1710: alen=len;
1711: }
1712:
1713: lprintf(LOG_DEBUG,"%04d Adding query value %s=%s at pos %d",session->socket,key,value,alen);
1714: val=STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx,value));
1715: JS_SetElement(session->js_cx, keyarray, alen, &val);
1716: }
1717:
1718: static void js_add_cookieval(http_session_t * session, char *key, char *value)
1719: {
1720: JSObject* keyarray;
1721: jsval val;
1722: jsuint len;
1723: int alen;
1724:
1725: /* Return existing object if it's already been created */
1726: if(JS_GetProperty(session->js_cx,session->js_cookie,key,&val) && val!=JSVAL_VOID) {
1727: keyarray = JSVAL_TO_OBJECT(val);
1728: alen=-1;
1729: }
1730: else {
1731: keyarray = JS_NewArrayObject(session->js_cx, 0, NULL);
1732: if(!JS_DefineProperty(session->js_cx, session->js_cookie, key, OBJECT_TO_JSVAL(keyarray)
1733: , NULL, NULL, JSPROP_ENUMERATE))
1734: return;
1735: alen=0;
1736: }
1737:
1738: if(alen==-1) {
1739: if(JS_GetArrayLength(session->js_cx, keyarray, &len)==JS_FALSE)
1740: return;
1741: alen=len;
1742: }
1743:
1744: lprintf(LOG_DEBUG,"%04d Adding cookie value %s=%s at pos %d",session->socket,key,value,alen);
1745: val=STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx,value));
1746: JS_SetElement(session->js_cx, keyarray, alen, &val);
1747: }
1748:
1749: static void js_add_request_prop(http_session_t * session, char *key, char *value)
1750: {
1751: JSString* js_str;
1752:
1753: if(session->js_cx==NULL || session->js_request==NULL)
1754: return;
1755: if(key==NULL || value==NULL)
1756: return;
1757: if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL)
1758: return;
1759: JS_DefineProperty(session->js_cx, session->js_request, key, STRING_TO_JSVAL(js_str)
1760: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
1761: }
1762:
1763: static void js_add_header(http_session_t * session, char *key, char *value)
1764: {
1765: JSString* js_str;
1766: char *lckey;
1767:
1768: if((lckey=(char *)alloca(strlen(key)+1))==NULL)
1769: return;
1770: strcpy(lckey,key);
1771: strlwr(lckey);
1772: if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL) {
1773: return;
1774: }
1775: JS_DefineProperty(session->js_cx, session->js_header, lckey, STRING_TO_JSVAL(js_str)
1776: ,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
1777: }
1778:
1779: #if 0
1780: static void js_parse_multipart(http_session_t * session, char *p) {
1781: size_t key_len;
1782: size_t value_len;
1783: char *lp;
1784: char *key;
1785: char *value;
1786:
1787: if(p == NULL)
1788: return;
1789:
1790: lp=p;
1791:
1792: while((key_len=strcspn(lp,"="))!=0) {
1793: key=lp;
1794: lp+=key_len;
1795: if(*lp) {
1796: *lp=0;
1797: lp++;
1798: }
1799: value_len=strcspn(lp,"&");
1800: value=lp;
1801: lp+=value_len;
1802: if(*lp) {
1803: *lp=0;
1804: lp++;
1805: }
1806: unescape(value);
1807: unescape(key);
1808: js_add_queryval(session, key, value);
1809: }
1810: }
1811: #endif
1812:
1813: static void js_parse_query(http_session_t * session, char *p) {
1814: size_t key_len;
1815: size_t value_len;
1816: char *lp;
1817: char *key;
1818: char *value;
1819:
1820: if(p == NULL)
1821: return;
1822:
1823: lp=p;
1824:
1825: while((key_len=strcspn(lp,"="))!=0) {
1826: key=lp;
1827: lp+=key_len;
1828: if(*lp) {
1829: *lp=0;
1830: lp++;
1831: }
1832: value_len=strcspn(lp,"&");
1833: value=lp;
1834: lp+=value_len;
1835: if(*lp) {
1836: *lp=0;
1837: lp++;
1838: }
1839: unescape(value);
1840: unescape(key);
1841: js_add_queryval(session, key, value);
1842: }
1843: }
1844:
1845: static BOOL parse_headers(http_session_t * session)
1846: {
1847: char *head_line;
1848: char *value;
1849: char *last;
1850: char *p;
1851: int i;
1852: size_t idx;
1853: size_t content_len=0;
1854: char env_name[128];
1855:
1856: for(idx=0;session->req.headers[idx]!=NULL;idx++) {
1857: head_line=session->req.headers[idx];
1858: if((strtok_r(head_line,":",&last))!=NULL && (value=strtok_r(NULL,"",&last))!=NULL) {
1859: i=get_header_type(head_line);
1860: while(*value && *value<=' ') value++;
1861: if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)
1862: js_add_header(session,head_line,value);
1863: switch(i) {
1864: case HEAD_AUTH:
1865: if(strtok_r(value," ",&last)) {
1866: p=strtok_r(NULL," ",&last);
1867: if(p==NULL)
1868: break;
1869: while(*p && *p<' ') p++;
1870: b64_decode(session->req.auth,sizeof(session->req.auth),p,strlen(p));
1871: }
1872: break;
1873: case HEAD_LENGTH:
1874: add_env(session,"CONTENT_LENGTH",value);
1875: content_len=strtol(value,NULL,10);
1876: break;
1877: case HEAD_TYPE:
1878: add_env(session,"CONTENT_TYPE",value);
1879: if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
1880: /*
1881: * We need to parse out the files based on RFC1867
1882: *
1883: * And example reponse looks like this:
1884: * Content-type: multipart/form-data, boundary=AaB03x
1885: *
1886: * --AaB03x
1887: * content-disposition: form-data; name="field1"
1888: *
1889: * Joe Blow
1890: * --AaB03x
1891: * content-disposition: form-data; name="pics"
1892: * Content-type: multipart/mixed, boundary=BbC04y
1893: *
1894: * --BbC04y
1895: * Content-disposition: attachment; filename="file1.txt"
1896: *
1897: * Content-Type: text/plain
1898: *
1899: * ... contents of file1.txt ...
1900: * --BbC04y
1901: * Content-disposition: attachment; filename="file2.gif"
1902: * Content-type: image/gif
1903: * Content-Transfer-Encoding: binary
1904: *
1905: * ...contents of file2.gif...
1906: * --BbC04y--
1907: * --AaB03x--
1908: */
1909: }
1910: break;
1911: case HEAD_IFMODIFIED:
1912: session->req.if_modified_since=decode_date(value);
1913: break;
1914: case HEAD_CONNECTION:
1915: if(!stricmp(value,"Keep-Alive")) {
1916: session->req.keep_alive=TRUE;
1917: }
1918: if(!stricmp(value,"Close")) {
1919: session->req.keep_alive=FALSE;
1920: }
1921: break;
1922: case HEAD_REFERER:
1923: if(session->req.ld!=NULL) {
1924: FREE_AND_NULL(session->req.ld->referrer);
1925: /* FREE()d in http_logging_thread() */
1926: session->req.ld->referrer=strdup(value);
1927: }
1928: break;
1929: case HEAD_AGENT:
1930: if(session->req.ld!=NULL) {
1931: FREE_AND_NULL(session->req.ld->agent);
1932: /* FREE()d in http_logging_thread() */
1933: session->req.ld->agent=strdup(value);
1934: }
1935: break;
1936: case HEAD_TRANSFER_ENCODING:
1937: if(!stricmp(value,"chunked"))
1938: session->req.read_chunked=TRUE;
1939: else
1940: send_error(session,"501 Not Implemented");
1941: break;
1942: case HEAD_RANGE:
1943: if(!stricmp(value,"bytes=")) {
1944: send_error(session,error_416);
1945: break;
1946: }
1947: value+=6;
1948: if(strchr(value,',')!=NULL) { /* We don't do multiple ranges yet - TODO */
1949: send_error(session,error_416);
1950: break;
1951: }
1952: /* Check for offset from end. */
1953: if(*value=='-') {
1954: session->req.range_start=strtol(value,NULL,10);
1955: session->req.range_end=-1;
1956: break;
1957: }
1958: if((p=strtok_r(value,"-",&last))!=NULL) {
1959: session->req.range_start=strtol(p,NULL,10);
1960: if((p=strtok_r(NULL,"-",&last))!=NULL)
1961: session->req.range_end=strtol(p,NULL,10);
1962: else
1963: session->req.range_end=-1;
1964: }
1965: else {
1966: send_error(session,error_416);
1967: break;
1968: }
1969: break;
1970: case HEAD_IFRANGE:
1971: session->req.if_range=decode_date(value);
1972: break;
1973: case HEAD_COOKIE:
1974: if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
1975: char *key;
1976: char *val;
1977:
1978: p=value;
1979: while((key=strtok_r(p,"=",&last))!=NULL) {
1980: p=NULL;
1981: if((val=strtok_r(p,";\t\n\v\f\r ",&last))!=NULL) { /* Whitespace */
1982: js_add_cookieval(session,key,val);
1983: }
1984: }
1985: }
1986: break;
1987: default:
1988: break;
1989: }
1990: sprintf(env_name,"HTTP_%s",head_line);
1991: add_env(session,env_name,value);
1992: }
1993: }
1994: if(content_len)
1995: session->req.post_len = content_len;
1996: add_env(session,"SERVER_NAME",session->req.host[0] ? session->req.host : startup->host_name );
1997: return TRUE;
1998: }
1999:
2000: static int get_version(char *p)
2001: {
2002: int i;
2003: if(p==NULL)
2004: return(0);
2005: while(*p && *p<' ') p++;
2006: if(*p==0)
2007: return(0);
2008: for(i=1;http_vers[i]!=NULL;i++) {
2009: if(!stricmp(p,http_vers[i])) {
2010: return(i);
2011: }
2012: }
2013: return(i-1);
2014: }
2015:
2016: static int is_dynamic_req(http_session_t* session)
2017: {
2018: int i=0;
2019: char drive[4];
2020: char cgidrive[4];
2021: char dir[MAX_PATH+1];
2022: char cgidir[MAX_PATH+1];
2023: char fname[MAX_PATH+1];
2024: char ext[MAX_PATH+1];
2025:
2026: check_extra_path(session);
2027: _splitpath(session->req.physical_path, drive, dir, fname, ext);
2028:
2029: if(stricmp(ext,startup->ssjs_ext)==0)
2030: i=IS_SSJS;
2031: else if(get_xjs_handler(ext,session))
2032: i=IS_SSJS;
2033: else if(stricmp(ext,startup->js_ext)==0)
2034: i=IS_JS;
2035: if(!(startup->options&BBS_OPT_NO_JAVASCRIPT) && i) {
2036: lprintf(LOG_INFO,"%04d Setting up JavaScript support", session->socket);
2037: if(!js_setup(session)) {
2038: lprintf(LOG_ERR,"%04d !ERROR setting up JavaScript support", session->socket);
2039: send_error(session,error_500);
2040: return(IS_STATIC);
2041: }
2042:
2043: return(i);
2044: }
2045:
2046: if(!(startup->options&WEB_OPT_NO_CGI)) {
2047: for(i=0; startup->cgi_ext!=NULL && startup->cgi_ext[i]!=NULL; i++) {
2048: if(stricmp(ext,startup->cgi_ext[i])==0) {
2049: init_enviro(session);
2050: return(IS_CGI);
2051: }
2052: }
2053: _splitpath(session->req.cgi_dir?session->req.cgi_dir:cgi_dir, cgidrive, cgidir, fname, ext);
2054: if(stricmp(dir,cgidir)==0 && stricmp(drive,cgidrive)==0) {
2055: init_enviro(session);
2056: return(IS_CGI);
2057: }
2058: }
2059:
2060: return(IS_STATIC);
2061: }
2062:
2063: static char *get_request(http_session_t * session, char *req_line)
2064: {
2065: char* p;
2066: char* query;
2067: char* retval;
2068: char* last;
2069: int offset;
2070:
2071: SKIP_WHITESPACE(req_line);
2072: SAFECOPY(session->req.virtual_path,req_line);
2073: if(strtok_r(session->req.virtual_path," \t",&last))
2074: retval=strtok_r(NULL," \t",&last);
2075: else
2076: retval=NULL;
2077: SAFECOPY(session->req.request_line,session->req.virtual_path);
2078: if(strtok_r(session->req.virtual_path,"?",&last))
2079: query=strtok_r(NULL,"",&last);
2080: else
2081: query=NULL;
2082:
2083: /* Must initialize physical_path before calling is_dynamic_req() */
2084: SAFECOPY(session->req.physical_path,session->req.virtual_path);
2085: unescape(session->req.physical_path);
2086: if(!strnicmp(session->req.physical_path,http_scheme,http_scheme_len)) {
2087: /* Set HOST value... ignore HOST header */
2088: SAFECOPY(session->req.host,session->req.physical_path+http_scheme_len);
2089: SAFECOPY(session->req.vhost,session->req.host);
2090: /* Remove port specification */
2091: strtok_r(session->req.vhost,":",&last);
2092: if(strtok_r(session->req.physical_path,"/",&last))
2093: p=strtok_r(NULL,"/",&last);
2094: else
2095: p=NULL;
2096: if(p==NULL) {
2097: /* Do not allow host values larger than 128 bytes */
2098: session->req.host[0]=0;
2099: p=session->req.physical_path+http_scheme_len;
2100: }
2101: offset=p-session->req.physical_path;
2102: memmove(session->req.physical_path
2103: ,session->req.physical_path+offset
2104: ,strlen(session->req.physical_path+offset)+1 /* move '\0' terminator too */
2105: );
2106: }
2107: if(query!=NULL)
2108: SAFECOPY(session->req.query_str,query);
2109:
2110: return(retval);
2111: }
2112:
2113: static char *get_method(http_session_t * session, char *req_line)
2114: {
2115: int i;
2116:
2117: for(i=0;methods[i]!=NULL;i++) {
2118: if(!strnicmp(req_line,methods[i],strlen(methods[i]))) {
2119: session->req.method=i;
2120: if(strlen(req_line)<strlen(methods[i])+2) {
2121: send_error(session,"400 Bad Request");
2122: return(NULL);
2123: }
2124: return(req_line+strlen(methods[i])+1);
2125: }
2126: }
2127: if(req_line!=NULL && *req_line>=' ')
2128: send_error(session,"501 Not Implemented");
2129: return(NULL);
2130: }
2131:
2132: static BOOL get_request_headers(http_session_t * session)
2133: {
2134: char head_line[MAX_REQUEST_LINE+1];
2135: char next_char;
2136: char *value;
2137: char *last;
2138: int i;
2139:
2140: while(sockreadline(session,head_line,sizeof(head_line)-1)>0) {
2141: /* Multi-line headers */
2142: while((i=recv(session->socket,&next_char,1,MSG_PEEK))>0
2143: && (next_char=='\t' || next_char==' ')) {
2144: if(i==-1 && ERROR_VALUE != EAGAIN)
2145: close_socket(&session->socket);
2146: i=strlen(head_line);
2147: if(i>sizeof(head_line)-1) {
2148: lprintf(LOG_ERR,"%04d !ERROR long multi-line header. The web server is broken!", session->socket);
2149: i=sizeof(head_line)/2;
2150: break;
2151: }
2152: sockreadline(session,head_line+i,sizeof(head_line)-i-1);
2153: }
2154: strListPush(&session->req.headers,head_line);
2155:
2156: if((strtok_r(head_line,":",&last))!=NULL && (value=strtok_r(NULL,"",&last))!=NULL) {
2157: i=get_header_type(head_line);
2158: while(*value && *value<=' ') value++;
2159: switch(i) {
2160: case HEAD_HOST:
2161: if(session->req.host[0]==0) {
2162: SAFECOPY(session->req.host,value);
2163: SAFECOPY(session->req.vhost,value);
2164: /* Remove port part of host (Win32 doesn't allow : in dir names) */
2165: /* Either an existing : will be replaced with a null, or nothing */
2166: /* Will happen... the return value is not relevent here */
2167: strtok_r(session->req.vhost,":",&last);
2168: }
2169: break;
2170: default:
2171: break;
2172: }
2173: }
2174: }
2175:
2176: if(!(session->req.vhost[0]))
2177: SAFECOPY(session->req.vhost, startup->host_name);
2178: if(!(session->req.host[0]))
2179: SAFECOPY(session->req.host, startup->host_name);
2180: return TRUE;
2181: }
2182:
2183: static BOOL get_fullpath(http_session_t * session)
2184: {
2185: char str[MAX_PATH+1];
2186:
2187: if(session->req.vhost[0] && startup->options&WEB_OPT_VIRTUAL_HOSTS) {
2188: safe_snprintf(str,sizeof(str),"%s/%s",root_dir,session->req.vhost);
2189: if(isdir(str))
2190: safe_snprintf(str,sizeof(str),"%s/%s%s",root_dir,session->req.vhost,session->req.physical_path);
2191: else
2192: safe_snprintf(str,sizeof(str),"%s%s",root_dir,session->req.physical_path);
2193: } else
2194: safe_snprintf(str,sizeof(str),"%s%s",root_dir,session->req.physical_path);
2195:
2196: if(FULLPATH(session->req.physical_path,str,sizeof(session->req.physical_path))==NULL) {
2197: send_error(session,error_500);
2198: return(FALSE);
2199: }
2200:
2201: return(TRUE);
2202: }
2203:
2204: static BOOL get_req(http_session_t * session, char *request_line)
2205: {
2206: char req_line[MAX_REQUEST_LINE+1];
2207: char * p;
2208: int is_redir=0;
2209: int len;
2210:
2211: req_line[0]=0;
2212: if(request_line == NULL) {
2213: /* Eat leaing blank lines... as apache does...
2214: * "This is a legacy issue. The CERN webserver required POST data to have an extra
2215: * CRLF following it. Thus many clients send an extra CRLF that is not included in the
2216: * Content-Length of the request. Apache works around this problem by eating any empty
2217: * lines which appear before a request."
2218: * http://httpd.apache.org/docs/misc/known_client_problems.html
2219: */
2220: while((len=sockreadline(session,req_line,sizeof(req_line)-1))==0);
2221: if(len<0)
2222: return(FALSE);
2223: if(req_line[0])
2224: lprintf(LOG_DEBUG,"%04d Request: %s",session->socket,req_line);
2225: if(session->req.ld!=NULL && session->req.ld->request==NULL)
2226: /* FREE()d in http_logging_thread() */
2227: session->req.ld->request=strdup(req_line);
2228: }
2229: else {
2230: lprintf(LOG_DEBUG,"%04d Handling Internal Redirect to: %s",session->socket,request_line);
2231: SAFECOPY(req_line,request_line);
2232: is_redir=1;
2233: }
2234: if(req_line[0]) {
2235: p=NULL;
2236: p=get_method(session,req_line);
2237: if(p!=NULL) {
2238: p=get_request(session,p);
2239: session->http_ver=get_version(p);
2240: if(session->http_ver>=HTTP_1_1)
2241: session->req.keep_alive=TRUE;
2242: if(!is_redir)
2243: get_request_headers(session);
2244: if(!get_fullpath(session)) {
2245: send_error(session,error_500);
2246: return(FALSE);
2247: }
2248: if(session->req.ld!=NULL && session->req.ld->vhost==NULL)
2249: /* FREE()d in http_logging_thread() */
2250: session->req.ld->vhost=strdup(session->req.vhost);
2251: session->req.dynamic=is_dynamic_req(session);
2252: if(session->req.query_str[0])
2253: add_env(session,"QUERY_STRING",session->req.query_str);
2254:
2255: add_env(session,"REQUEST_METHOD",methods[session->req.method]);
2256: add_env(session,"SERVER_PROTOCOL",session->http_ver ?
2257: http_vers[session->http_ver] : "HTTP/0.9");
2258: return(TRUE);
2259: }
2260: }
2261: session->req.keep_alive=FALSE;
2262: send_error(session,"400 Bad Request");
2263: return FALSE;
2264: }
2265:
2266: /* This may exist somewhere else - ToDo */
2267: static char *find_last_slash(char *str)
2268: {
2269: #ifdef _WIN32
2270: char * LastFSlash;
2271: char * LastBSlash;
2272:
2273: LastFSlash=strrchr(str,'/');
2274: LastBSlash=strrchr(str,'\\');
2275: if(LastFSlash==NULL)
2276: return(LastBSlash);
2277: if(LastBSlash==NULL)
2278: return(LastFSlash);
2279: if(LastBSlash < LastFSlash)
2280: return(LastFSlash);
2281: return(LastBSlash);
2282: #else
2283: return(strrchr(str,'/'));
2284: #endif
2285: }
2286:
2287: /* This may exist somewhere else - ToDo */
2288: static char *find_first_slash(char *str)
2289: {
2290: #ifdef _WIN32
2291: char * FirstFSlash;
2292: char * FirstBSlash;
2293:
2294: FirstFSlash=strchr(str,'/');
2295: FirstBSlash=strchr(str,'\\');
2296: if(FirstFSlash==NULL)
2297: return(FirstBSlash);
2298: if(FirstBSlash==NULL)
2299: return(FirstFSlash);
2300: if(FirstBSlash > FirstFSlash)
2301: return(FirstFSlash);
2302: return(FirstBSlash);
2303: #else
2304: return(strchr(str,'/'));
2305: #endif
2306: }
2307:
2308: static BOOL check_extra_path(http_session_t * session)
2309: {
2310: char *rp_slash;
2311: char *vp_slash;
2312: char rpath[MAX_PATH+1];
2313: char vpath[MAX_PATH+1];
2314: char epath[MAX_PATH+1];
2315: char str[MAX_PATH+1];
2316: struct stat sb;
2317: int i;
2318: char *end;
2319: int use_epath=0;
2320:
2321: epath[0]=0;
2322: epath[1]=0;
2323: if(IS_PATH_DELIM(*lastchar(session->req.physical_path)) || stat(session->req.physical_path,&sb)==-1 /* && errno==ENOTDIR */)
2324: {
2325: SAFECOPY(vpath,session->req.virtual_path);
2326: SAFECOPY(rpath,session->req.physical_path);
2327: while((vp_slash=find_last_slash(vpath))!=NULL)
2328: {
2329: *vp_slash=0;
2330: if((rp_slash=find_last_slash(rpath))==NULL)
2331: return(FALSE);
2332: SAFECOPY(str,epath);
2333: if(*rp_slash)
2334: sprintf(epath,"%s%s",rp_slash,str);
2335: *(rp_slash+1)=0;
2336:
2337: /* Check if this contains an index */
2338: end=strchr(rpath,0);
2339: if(use_epath || session->req.path_info_index || strchr(epath+1,'/')!=NULL) {
2340: use_epath=1;
2341: if(isdir(rpath) && !isdir(session->req.physical_path)) {
2342: for(i=0; startup->index_file_name!=NULL && startup->index_file_name[i]!=NULL ;i++) {
2343: *end=0;
2344: strcat(rpath,startup->index_file_name[i]);
2345: if(!stat(rpath,&sb)) {
2346: /* *end=0; /* Removed Wed, Aug 09, 2006 to allow is_dynamic_req to detect correctly */
2347: SAFECOPY(session->req.extra_path_info,epath);
2348: SAFECOPY(session->req.virtual_path,vpath);
2349: strcat(session->req.virtual_path,"/");
2350: SAFECOPY(session->req.physical_path,rpath);
2351: return(TRUE);
2352: }
2353: }
2354: /* rpath was an existing path and DID NOT contain an index. */
2355: /* We do not allow scripts to mask existing dirs/files */
2356: return(FALSE);
2357: }
2358: }
2359: else {
2360: if(isdir(rpath))
2361: return(FALSE);
2362: }
2363:
2364: if(vp_slash==vpath)
2365: return(FALSE);
2366:
2367: /* Check if this is a script */
2368: *rp_slash=0;
2369: if(vp_slash!=vpath) {
2370: if(stat(rpath,&sb)!=-1 && (!(sb.st_mode&S_IFDIR)))
2371: {
2372: SAFECOPY(session->req.extra_path_info,epath);
2373: SAFECOPY(session->req.virtual_path,vpath);
2374: SAFECOPY(session->req.physical_path,rpath);
2375: return(TRUE);
2376: }
2377: }
2378: }
2379: }
2380: return(FALSE);
2381: }
2382:
2383: static BOOL check_request(http_session_t * session)
2384: {
2385: char path[MAX_PATH+1];
2386: char curdir[MAX_PATH+1];
2387: char str[MAX_PATH+1];
2388: char last_ch;
2389: char* last_slash;
2390: char* p;
2391: FILE* file;
2392: int i;
2393: struct stat sb;
2394: int send404=0;
2395: char filename[MAX_PATH+1];
2396: char *spec;
2397: str_list_t specs;
2398: BOOL recheck_dynamic=FALSE;
2399:
2400: if(session->req.finished)
2401: return(FALSE);
2402:
2403: SAFECOPY(path,session->req.physical_path);
2404: if(startup->options&WEB_OPT_DEBUG_TX)
2405: lprintf(LOG_DEBUG,"%04d Path is: %s",session->socket,path);
2406:
2407: if(isdir(path)) {
2408: last_ch=*lastchar(path);
2409: if(!IS_PATH_DELIM(last_ch)) {
2410: session->req.send_location=MOVED_PERM;
2411: strcat(path,"/");
2412: strcat(session->req.physical_path,"/");
2413: }
2414: last_ch=*lastchar(session->req.virtual_path);
2415: if(!IS_PATH_DELIM(last_ch)) {
2416: session->req.send_location=MOVED_PERM;
2417: strcat(session->req.virtual_path,"/");
2418: }
2419: last_slash=find_last_slash(path);
2420: if(last_slash==NULL) {
2421: send_error(session,error_500);
2422: return(FALSE);
2423: }
2424: last_slash++;
2425: for(i=0; startup->index_file_name!=NULL && startup->index_file_name[i]!=NULL ;i++) {
2426: *last_slash=0;
2427: strcat(path,startup->index_file_name[i]);
2428: if(startup->options&WEB_OPT_DEBUG_TX)
2429: lprintf(LOG_DEBUG,"%04d Checking for %s",session->socket,path);
2430: if(!stat(path,&sb))
2431: break;
2432: SAFECOPY(path,session->req.physical_path);
2433: }
2434:
2435: /* Don't send 404 unless authourized... prevent info leak */
2436: if(startup->index_file_name==NULL || startup->index_file_name[i] == NULL)
2437: send404=1;
2438: else {
2439: strcat(session->req.virtual_path,startup->index_file_name[i]);
2440: if(session->req.send_location != MOVED_PERM)
2441: session->req.send_location=MOVED_STAT;
2442: }
2443: filename[0]=0;
2444: }
2445: else {
2446: last_slash=find_last_slash(path);
2447: if(last_slash==NULL)
2448: last_slash=path;
2449: else
2450: last_slash++;
2451: strcpy(filename,last_slash);
2452: }
2453: if(strnicmp(path,root_dir,strlen(root_dir))) {
2454: session->req.keep_alive=FALSE;
2455: send_error(session,"400 Bad Request");
2456: lprintf(LOG_NOTICE,"%04d !ERROR Request for %s is outside of web root %s"
2457: ,session->socket,path,root_dir);
2458: return(FALSE);
2459: }
2460:
2461: /* Set default ARS to a 0-length string */
2462: session->req.ars[0]=0;
2463: /* Walk up from root_dir checking for access.ars and webconfig.ini */
2464: SAFECOPY(curdir,path);
2465: last_slash=curdir+strlen(root_dir)-1;
2466: /* Loop while there's more /s in path*/
2467: p=last_slash;
2468:
2469: while((last_slash=find_first_slash(p+1))!=NULL) {
2470: p=last_slash;
2471: /* Terminate the path after the slash */
2472: *(last_slash+1)=0;
2473: sprintf(str,"%saccess.ars",curdir);
2474: if(!stat(str,&sb)) {
2475: /* NEVER serve up an access.ars file */
2476: lprintf(LOG_WARNING,"%04d !WARNING! access.ars support is depreciated and will be REMOVED very soon.",session->socket);
2477: lprintf(LOG_WARNING,"%04d !WARNING! access.ars found at %s.",session->socket,str);
2478: if(!strcmp(path,str)) {
2479: send_error(session,"403 Forbidden");
2480: return(FALSE);
2481: }
2482: /* Read access.ars file */
2483: if((file=fopen(str,"r"))!=NULL) {
2484: fgets(session->req.ars,sizeof(session->req.ars),file);
2485: fclose(file);
2486: }
2487: else {
2488: /* If cannot open access.ars, only allow sysop access */
2489: SAFECOPY(session->req.ars,"LEVEL 90");
2490: break;
2491: }
2492: /* Truncate at \r or \n - can use last_slash since I'm done with it.*/
2493: truncsp(session->req.ars);
2494: }
2495: sprintf(str,"%swebctrl.ini",curdir);
2496: if(!stat(str,&sb)) {
2497: /* NEVER serve up a webctrl.ini file */
2498: if(!strcmp(path,str)) {
2499: send_error(session,"403 Forbidden");
2500: return(FALSE);
2501: }
2502: /* Read webctrl.ars file */
2503: if((file=fopen(str,"r"))!=NULL) {
2504: /* FREE()d in this block */
2505: specs=iniReadSectionList(file,NULL);
2506: /* Read in globals */
2507: if(iniReadString(file, NULL, "AccessRequirements", session->req.ars,str)==str)
2508: SAFECOPY(session->req.ars,str);
2509: if(iniReadString(file, NULL, "Realm", scfg.sys_name,str)==str) {
2510: FREE_AND_NULL(session->req.realm);
2511: /* FREE()d in close_request() */
2512: session->req.realm=strdup(str);
2513: }
2514: if(iniReadString(file, NULL, "ErrorDirectory", error_dir,str)==str) {
2515: prep_dir(root_dir, str, sizeof(str));
2516: FREE_AND_NULL(session->req.error_dir);
2517: /* FREE()d in close_request() */
2518: session->req.error_dir=strdup(str);
2519: }
2520: if(iniReadString(file, NULL, "CGIDirectory", cgi_dir,str)==str) {
2521: prep_dir(root_dir, str, sizeof(str));
2522: FREE_AND_NULL(session->req.cgi_dir);
2523: /* FREE()d in close_request() */
2524: session->req.cgi_dir=strdup(str);
2525: recheck_dynamic=TRUE;
2526: }
2527: session->req.path_info_index=iniReadBool(file, NULL, "PathInfoIndex", FALSE);
2528: /* Read in per-filespec */
2529: while((spec=strListPop(&specs))!=NULL) {
2530: if(wildmatch(filename,spec,TRUE)) {
2531: if(iniReadString(file, spec, "AccessRequirements", session->req.ars,str)==str)
2532: SAFECOPY(session->req.ars,str);
2533: if(iniReadString(file, spec, "Realm", scfg.sys_name,str)==str) {
2534: FREE_AND_NULL(session->req.realm);
2535: /* FREE()d in close_request() */
2536: session->req.realm=strdup(str);
2537: }
2538: if(iniReadString(file, spec, "ErrorDirectory", error_dir,str)==str) {
2539: FREE_AND_NULL(session->req.error_dir);
2540: prep_dir(root_dir, str, sizeof(str));
2541: /* FREE()d in close_request() */
2542: session->req.error_dir=strdup(str);
2543: }
2544: if(iniReadString(file, spec, "CGIDirectory", cgi_dir,str)==str) {
2545: FREE_AND_NULL(session->req.cgi_dir);
2546: prep_dir(root_dir, str, sizeof(str));
2547: /* FREE()d in close_request() */
2548: session->req.cgi_dir=strdup(str);
2549: recheck_dynamic=TRUE;
2550: }
2551: session->req.path_info_index=iniReadBool(file, spec, "PathInfoIndex", FALSE);
2552: }
2553: free(spec);
2554: }
2555: iniFreeStringList(specs);
2556: fclose(file);
2557: if(session->req.path_info_index)
2558: recheck_dynamic=TRUE;
2559: }
2560: else {
2561: /* If cannot open webctrl.ars, only allow sysop access */
2562: SAFECOPY(session->req.ars,"LEVEL 90");
2563: break;
2564: }
2565: /* Truncate at \r or \n - can use last_slash since I'm done with it.*/
2566: truncsp(session->req.ars);
2567: }
2568: SAFECOPY(curdir,path);
2569: }
2570:
2571: if(recheck_dynamic) {
2572: session->req.dynamic=is_dynamic_req(session);
2573: if(session->req.dynamic) /* Need to re-copy path here in case of re-checked PathInfoIndex change */
2574: SAFECOPY(path,session->req.physical_path);
2575: }
2576:
2577: if(!session->req.dynamic && session->req.extra_path_info[0])
2578: send404=TRUE;
2579:
2580: if(!check_ars(session)) {
2581: /* No authentication provided */
2582: sprintf(str,"401 Unauthorized%s%s: Basic realm=\"%s\""
2583: ,newline,get_header(HEAD_WWWAUTH),session->req.realm?session->req.realm:scfg.sys_name);
2584: send_error(session,str);
2585: return(FALSE);
2586: }
2587:
2588: if(stat(path,&sb) || IS_PATH_DELIM(*(lastchar(path))) || send404) {
2589: /* OPTIONS requests never return 404 errors (ala Apache) */
2590: if(session->req.method!=HTTP_OPTIONS) {
2591: if(startup->options&WEB_OPT_DEBUG_TX)
2592: lprintf(LOG_DEBUG,"%04d 404 - %s does not exist",session->socket,path);
2593: strcat(session->req.physical_path,session->req.extra_path_info);
2594: strcat(session->req.virtual_path,session->req.extra_path_info);
2595: send_error(session,error_404);
2596: return(FALSE);
2597: }
2598: }
2599: if(session->req.range_start || session->req.range_end) {
2600: if(session->req.range_start < 0)
2601: session->req.range_start=sb.st_size-session->req.range_start;
2602: if(session->req.range_end < 0)
2603: session->req.range_end=sb.st_size-session->req.range_end;
2604: if(session->req.range_end >= sb.st_size)
2605: session->req.range_end=sb.st_size-1;
2606: if(session->req.range_end < session->req.range_start || session->req.dynamic) {
2607: send_error(session,error_416);
2608: return(FALSE);
2609: }
2610: if(session->req.range_start < 0 || session->req.range_end < 0) {
2611: send_error(session,error_416);
2612: return(FALSE);
2613: }
2614: if(session->req.range_start >= sb.st_size) {
2615: send_error(session,error_416);
2616: return(FALSE);
2617: }
2618: SAFECOPY(session->req.status,"206 Partial Content");
2619: }
2620: SAFECOPY(session->req.physical_path,path);
2621: add_env(session,"SCRIPT_NAME",session->req.virtual_path);
2622: add_env(session,"SCRIPT_FILENAME",session->req.physical_path);
2623: SAFECOPY(str,session->req.virtual_path);
2624: last_slash=find_last_slash(str);
2625: if(last_slash!=NULL)
2626: *(last_slash+1)=0;
2627: if(*(session->req.extra_path_info))
2628: {
2629: sprintf(str,"%s%s",startup->root_dir,session->req.extra_path_info);
2630: add_env(session,"PATH_TRANSLATED",str);
2631: add_env(session,"PATH_INFO",session->req.extra_path_info);
2632: }
2633:
2634: return(TRUE);
2635: }
2636:
2637: static str_list_t get_cgi_env(http_session_t *session)
2638: {
2639: char value[INI_MAX_VALUE_LEN+1];
2640: char* deflt;
2641: char defltbuf[INI_MAX_VALUE_LEN+1];
2642: char append[INI_MAX_VALUE_LEN+1];
2643: char prepend[INI_MAX_VALUE_LEN+1];
2644: char env_str[(INI_MAX_VALUE_LEN*4)+2];
2645: FILE* fp;
2646: size_t i;
2647: str_list_t env_list;
2648: str_list_t add_list;
2649:
2650: /* Return value */
2651: if((env_list=strListInit())==NULL)
2652: return(NULL);
2653:
2654: strListAppendList(&env_list, session->req.cgi_env);
2655:
2656: strListPush(&env_list,"REDIRECT_STATUS=200"); /* Kludge for php-cgi */
2657:
2658: if((fp=iniOpenFile(cgi_env_ini,/* create? */FALSE))==NULL)
2659: return(env_list);
2660:
2661: /* FREE()d in this block */
2662: if((add_list=iniReadSectionList(fp,NULL))!=NULL) {
2663:
2664: for(i=0; add_list[i]!=NULL; i++) {
2665: if((deflt=getenv(add_list[i]))==NULL)
2666: deflt=iniReadString(fp,add_list[i],"default",NULL,defltbuf);
2667: if(iniReadString(fp,add_list[i],"value",deflt,value)==NULL)
2668: continue;
2669: iniReadString(fp,add_list[i],"append","",append);
2670: iniReadString(fp,add_list[i],"prepend","",prepend);
2671: safe_snprintf(env_str,sizeof(env_str),"%s=%s%s%s"
2672: ,add_list[i], prepend, value, append);
2673: strListPush(&env_list,env_str);
2674: }
2675: iniFreeStringList(add_list);
2676: }
2677:
2678: fclose(fp);
2679:
2680: return(env_list);
2681: }
2682:
2683:
2684: static BOOL exec_cgi(http_session_t *session)
2685: {
2686: #ifdef __unix__
2687: char cmdline[MAX_PATH+256];
2688: /* ToDo: Damn, that's WAY too many variables */
2689: int i=0;
2690: int j;
2691: int status=0;
2692: pid_t child=0;
2693: int out_pipe[2];
2694: int err_pipe[2];
2695: struct timeval tv={0,0};
2696: fd_set read_set;
2697: fd_set write_set;
2698: int high_fd=0;
2699: char buf[1024];
2700: char fbuf[1026];
2701: BOOL done_parsing_headers=FALSE;
2702: BOOL done_reading=FALSE;
2703: char cgi_status[MAX_REQUEST_LINE+1];
2704: char header[MAX_REQUEST_LINE+1];
2705: char *directive=NULL;
2706: char *value=NULL;
2707: char *last;
2708: BOOL done_wait=FALSE;
2709: BOOL got_valid_headers=FALSE;
2710: time_t start;
2711: char cgipath[MAX_PATH+1];
2712: char *p;
2713: char ch;
2714: BOOL orig_keep=FALSE;
2715: size_t idx;
2716: str_list_t tmpbuf;
2717: size_t tmpbuflen=0;
2718: BOOL no_chunked=FALSE;
2719: BOOL set_chunked=FALSE;
2720:
2721: SAFECOPY(cmdline,session->req.physical_path);
2722:
2723: lprintf(LOG_INFO,"%04d Executing CGI: %s",session->socket,cmdline);
2724:
2725: orig_keep=session->req.keep_alive;
2726: session->req.keep_alive=FALSE;
2727:
2728: /* Set up I/O pipes */
2729:
2730: if(pipe(out_pipe)!=0) {
2731: lprintf(LOG_ERR,"%04d Can't create out_pipe",session->socket);
2732: return(FALSE);
2733: }
2734:
2735: if(pipe(err_pipe)!=0) {
2736: lprintf(LOG_ERR,"%04d Can't create err_pipe",session->socket);
2737: return(FALSE);
2738: }
2739:
2740: if((child=fork())==0) {
2741: str_list_t env_list;
2742:
2743: /* Do a full suid thing. */
2744: if(startup->setuid!=NULL)
2745: startup->setuid(TRUE);
2746:
2747: env_list=get_cgi_env(session);
2748:
2749: /* Set up STDIO */
2750: dup2(session->socket,0); /* redirect stdin */
2751: close(out_pipe[0]); /* close read-end of pipe */
2752: dup2(out_pipe[1],1); /* stdout */
2753: close(out_pipe[1]); /* close excess file descriptor */
2754: close(err_pipe[0]); /* close read-end of pipe */
2755: dup2(err_pipe[1],2); /* stderr */
2756: close(err_pipe[1]); /* close excess file descriptor */
2757:
2758: SAFECOPY(cgipath,cmdline);
2759: if((p=strrchr(cgipath,'/'))!=NULL)
2760: {
2761: *p=0;
2762: chdir(cgipath);
2763: }
2764:
2765: /* Execute command */
2766: if(get_cgi_handler(cgipath, sizeof(cgipath))) {
2767: char* shell=os_cmdshell();
2768: lprintf(LOG_INFO,"%04d Using handler %s to execute %s",session->socket,cgipath,cmdline);
2769: execle(shell,shell,"-c",cgipath,NULL,env_list);
2770: }
2771: else {
2772: execle(cmdline,cmdline,NULL,env_list);
2773: }
2774:
2775: lprintf(LOG_ERR,"%04d !FAILED! execle() (%d)",session->socket,errno);
2776: exit(EXIT_FAILURE); /* Should never happen */
2777: }
2778:
2779: if(child==-1) {
2780: lprintf(LOG_ERR,"%04d !FAILED! fork() errno=%d",session->socket,errno);
2781: close(out_pipe[0]); /* close read-end of pipe */
2782: close(err_pipe[0]); /* close read-end of pipe */
2783: }
2784:
2785: close(out_pipe[1]); /* close excess file descriptor */
2786: close(err_pipe[1]); /* close excess file descriptor */
2787:
2788: if(child==-1)
2789: return(FALSE);
2790:
2791: start=time(NULL);
2792:
2793: high_fd=out_pipe[0];
2794: if(err_pipe[0]>high_fd)
2795: high_fd=err_pipe[0];
2796:
2797: /* ToDo: Magically set done_parsing_headers for nph-* scripts */
2798: cgi_status[0]=0;
2799: /* FREE()d following this block */
2800: tmpbuf=strListInit();
2801: while(!done_reading) {
2802: tv.tv_sec=startup->max_cgi_inactivity;
2803: tv.tv_usec=0;
2804:
2805: FD_ZERO(&read_set);
2806: FD_SET(out_pipe[0],&read_set);
2807: FD_SET(err_pipe[0],&read_set);
2808: FD_ZERO(&write_set);
2809:
2810: if(select(high_fd+1,&read_set,&write_set,NULL,&tv)>0) {
2811: if(FD_ISSET(out_pipe[0],&read_set)) {
2812: if(done_parsing_headers && got_valid_headers) {
2813: i=read(out_pipe[0],buf,sizeof(buf));
2814: if(i!=-1 && i!=0) {
2815: int snt=0;
2816: start=time(NULL);
2817: if(session->req.method!=HTTP_HEAD) {
2818: snt=writebuf(session,buf,i);
2819: if(session->req.ld!=NULL) {
2820: session->req.ld->size+=snt;
2821: }
2822: }
2823: }
2824: else
2825: done_reading=TRUE;
2826: }
2827: else {
2828: /* This is the tricky part */
2829: i=pipereadline(out_pipe[0],buf,sizeof(buf), fbuf, sizeof(fbuf));
2830: if(i==-1) {
2831: done_reading=TRUE;
2832: got_valid_headers=FALSE;
2833: }
2834: else
2835: start=time(NULL);
2836:
2837: if(!done_parsing_headers && *buf) {
2838: if(tmpbuf != NULL)
2839: strListPush(&tmpbuf, fbuf);
2840: SAFECOPY(header,buf);
2841: directive=strtok_r(header,":",&last);
2842: if(directive != NULL) {
2843: value=strtok_r(NULL,"",&last);
2844: i=get_header_type(directive);
2845: switch (i) {
2846: case HEAD_LOCATION:
2847: got_valid_headers=TRUE;
2848: if(*value=='/') {
2849: unescape(value);
2850: SAFECOPY(session->req.virtual_path,value);
2851: session->req.send_location=MOVED_STAT;
2852: if(cgi_status[0]==0)
2853: SAFECOPY(cgi_status,error_302);
2854: } else {
2855: SAFECOPY(session->req.virtual_path,value);
2856: session->req.send_location=MOVED_TEMP;
2857: if(cgi_status[0]==0)
2858: SAFECOPY(cgi_status,error_302);
2859: }
2860: break;
2861: case HEAD_STATUS:
2862: SAFECOPY(cgi_status,value);
2863: break;
2864: case HEAD_LENGTH:
2865: session->req.keep_alive=orig_keep;
2866: strListPush(&session->req.dynamic_heads,buf);
2867: no_chunked=TRUE;
2868: break;
2869: case HEAD_TYPE:
2870: got_valid_headers=TRUE;
2871: strListPush(&session->req.dynamic_heads,buf);
2872: break;
2873: case HEAD_TRANSFER_ENCODING:
2874: no_chunked=TRUE;
2875: break;
2876: default:
2877: strListPush(&session->req.dynamic_heads,buf);
2878: }
2879: }
2880: if(directive == NULL || value == NULL) {
2881: /* Invalid header line */
2882: done_parsing_headers=TRUE;
2883: }
2884: }
2885: else {
2886: if(!no_chunked && session->http_ver>=HTTP_1_1) {
2887: session->req.keep_alive=orig_keep;
2888: set_chunked=TRUE;
2889: }
2890: if(got_valid_headers) {
2891: session->req.dynamic=IS_CGI;
2892: if(cgi_status[0]==0)
2893: SAFECOPY(cgi_status,session->req.status);
2894: send_headers(session,cgi_status,set_chunked);
2895: }
2896: else {
2897: /* Invalid headers... send 'er all as plain-text */
2898: char content_type[MAX_REQUEST_LINE+1];
2899: int snt;
2900:
2901: lprintf(LOG_DEBUG,"%04d Recieved invalid CGI headers, sending result as plain-text",session->socket);
2902:
2903: /* free() the non-headers so they don't get sent, then recreate the list */
2904: strListFreeStrings(session->req.dynamic_heads);
2905:
2906: /* Copy current status */
2907: SAFECOPY(cgi_status,session->req.status);
2908:
2909: /* Add the content-type header (REQUIRED) */
2910: SAFEPRINTF2(content_type,"%s: %s",get_header(HEAD_TYPE),startup->default_cgi_content);
2911: strListPush(&session->req.dynamic_heads,content_type);
2912: send_headers(session,cgi_status,FALSE);
2913:
2914: /* Now send the tmpbuf */
2915: for(i=0; tmpbuf != NULL && tmpbuf[i] != NULL; i++) {
2916: if(strlen(tmpbuf[i])>0) {
2917: snt=writebuf(session,tmpbuf[i],strlen(tmpbuf[i]));
2918: if(session->req.ld!=NULL) {
2919: session->req.ld->size+=snt;
2920: }
2921: }
2922: }
2923: if(strlen(fbuf)>0) {
2924: snt=writebuf(session,fbuf,strlen(fbuf));
2925: if(session->req.ld!=NULL && snt>0) {
2926: session->req.ld->size+=snt;
2927: }
2928: }
2929: got_valid_headers=TRUE;
2930: }
2931: done_parsing_headers=TRUE;
2932: }
2933: }
2934: }
2935: if(FD_ISSET(err_pipe[0],&read_set)) {
2936: i=read(err_pipe[0],buf,sizeof(buf)-1);
2937: if(i>0) {
2938: buf[i]=0;
2939: lprintf(LOG_ERR,"%04d CGI Error: %s",session->socket,buf);
2940: start=time(NULL);
2941: }
2942: }
2943: if(!done_wait)
2944: done_wait = (waitpid(child,&status,WNOHANG)==child);
2945: if(!FD_ISSET(err_pipe[0],&read_set) && !FD_ISSET(out_pipe[0],&read_set) && done_wait)
2946: done_reading=TRUE;
2947: }
2948: else {
2949: if((time(NULL)-start) >= startup->max_cgi_inactivity) {
2950: lprintf(LOG_ERR,"%04d CGI Process %s Timed out",session->socket,getfname(cmdline));
2951: done_reading=TRUE;
2952: start=0;
2953: }
2954: }
2955: }
2956:
2957: if(tmpbuf != NULL)
2958: strListFree(&tmpbuf);
2959:
2960: if(!done_wait)
2961: done_wait = (waitpid(child,&status,WNOHANG)==child);
2962: if(!done_wait) {
2963: if(start)
2964: lprintf(LOG_NOTICE,"%04d CGI Process %s still alive on client exit"
2965: ,session->socket,getfname(cmdline));
2966: kill(child,SIGTERM);
2967: mswait(1000);
2968: done_wait = (waitpid(child,&status,WNOHANG)==child);
2969: if(!done_wait) {
2970: kill(child,SIGKILL);
2971: done_wait = (waitpid(child,&status,0)==child);
2972: }
2973: }
2974:
2975: /* Drain STDERR & STDOUT */
2976: tv.tv_sec=1;
2977: tv.tv_usec=0;
2978: FD_ZERO(&read_set);
2979: FD_SET(err_pipe[0],&read_set);
2980: FD_SET(out_pipe[0],&read_set);
2981: while(select(high_fd+1,&read_set,NULL,NULL,&tv)>0) {
2982: if(FD_ISSET(err_pipe[0],&read_set)) {
2983: i=read(err_pipe[0],buf,sizeof(buf)-1);
2984: if(i!=-1 && i!=0) {
2985: buf[i]=0;
2986: lprintf(LOG_ERR,"%04d CGI Error: %s",session->socket,buf);
2987: start=time(NULL);
2988: }
2989: }
2990:
2991: if(FD_ISSET(out_pipe[0],&read_set)) {
2992: i=read(out_pipe[0],buf,sizeof(buf));
2993: if(i!=-1 && i!=0) {
2994: int snt=0;
2995: start=time(NULL);
2996: if(session->req.method!=HTTP_HEAD) {
2997: snt=writebuf(session,buf,i);
2998: if(session->req.ld!=NULL) {
2999: session->req.ld->size+=snt;
3000: }
3001: }
3002: }
3003: }
3004:
3005: if(i==0 || i==-1)
3006: break;
3007:
3008: tv.tv_sec=1;
3009: tv.tv_usec=0;
3010: FD_ZERO(&read_set);
3011: FD_SET(err_pipe[0],&read_set);
3012: FD_SET(out_pipe[0],&read_set);
3013: }
3014:
3015: close(out_pipe[0]); /* close read-end of pipe */
3016: close(err_pipe[0]); /* close read-end of pipe */
3017: if(!got_valid_headers) {
3018: lprintf(LOG_ERR,"%04d CGI Process %s did not generate valid headers"
3019: ,session->socket,getfname(cmdline));
3020: return(FALSE);
3021: }
3022:
3023: if(!done_parsing_headers) {
3024: lprintf(LOG_ERR,"%04d CGI Process %s did not send data header termination"
3025: ,session->socket,getfname(cmdline));
3026: return(FALSE);
3027: }
3028:
3029: return(TRUE);
3030: #else
3031: /* Win32 exec_cgi() */
3032:
3033: /* These are (more or less) copied from the Unix version */
3034: char* p;
3035: char *last;
3036: char cmdline[MAX_PATH+256];
3037: char buf[4096];
3038: int i;
3039: BOOL orig_keep;
3040: BOOL done_parsing_headers=FALSE;
3041: BOOL got_valid_headers=FALSE;
3042: char cgi_status[MAX_REQUEST_LINE+1];
3043: char content_type[MAX_REQUEST_LINE+1];
3044: char header[MAX_REQUEST_LINE+1];
3045: char *directive=NULL;
3046: char *value=NULL;
3047: time_t start;
3048: BOOL no_chunked=FALSE;
3049: int set_chunked=FALSE;
3050:
3051: /* Win32-specific */
3052: char* env_block;
3053: char startup_dir[MAX_PATH+1];
3054: int wr;
3055: HANDLE rdpipe=INVALID_HANDLE_VALUE;
3056: HANDLE wrpipe=INVALID_HANDLE_VALUE;
3057: HANDLE rdoutpipe;
3058: HANDLE wrinpipe;
3059: DWORD waiting;
3060: DWORD msglen;
3061: DWORD retval;
3062: BOOL success;
3063: BOOL process_terminated=FALSE;
3064: PROCESS_INFORMATION process_info;
3065: SECURITY_ATTRIBUTES sa;
3066: STARTUPINFO startup_info={0};
3067: str_list_t env_list;
3068:
3069: startup_info.cb=sizeof(startup_info);
3070: startup_info.dwFlags|=STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW;
3071: startup_info.wShowWindow=SW_HIDE;
3072:
3073: SAFECOPY(cmdline,session->req.physical_path);
3074:
3075: SAFECOPY(startup_dir,cmdline);
3076: if((p=strrchr(startup_dir,'/'))!=NULL || (p=strrchr(startup_dir,'\\'))!=NULL)
3077: *p=0;
3078: else
3079: SAFECOPY(startup_dir,session->req.cgi_dir?session->req.cgi_dir:cgi_dir);
3080:
3081: lprintf(LOG_DEBUG,"%04d CGI startup dir: %s", session->socket, startup_dir);
3082:
3083: get_cgi_handler(cmdline, sizeof(cmdline));
3084:
3085: lprintf(LOG_INFO,"%04d Executing CGI: %s",session->socket,cmdline);
3086:
3087: orig_keep=session->req.keep_alive;
3088: session->req.keep_alive=FALSE;
3089:
3090: memset(&sa,0,sizeof(sa));
3091: sa.nLength= sizeof(SECURITY_ATTRIBUTES);
3092: sa.lpSecurityDescriptor = NULL;
3093: sa.bInheritHandle = TRUE;
3094:
3095: /* Create the child output pipe (override default 4K buffer size) */
3096: if(!CreatePipe(&rdoutpipe,&startup_info.hStdOutput,&sa,sizeof(buf))) {
3097: lprintf(LOG_ERR,"%04d !ERROR %d creating stdout pipe",session->socket,GetLastError());
3098: return(FALSE);
3099: }
3100: startup_info.hStdError=startup_info.hStdOutput;
3101:
3102: /* Create the child input pipe. */
3103: if(!CreatePipe(&startup_info.hStdInput,&wrinpipe,&sa,0 /* default buffer size */)) {
3104: lprintf(LOG_ERR,"%04d !ERROR %d creating stdin pipe",session->socket,GetLastError());
3105: return(FALSE);
3106: }
3107:
3108: DuplicateHandle(
3109: GetCurrentProcess(), rdoutpipe,
3110: GetCurrentProcess(), &rdpipe, 0, FALSE, DUPLICATE_SAME_ACCESS);
3111:
3112: DuplicateHandle(
3113: GetCurrentProcess(), wrinpipe,
3114: GetCurrentProcess(), &wrpipe, 0, FALSE, DUPLICATE_SAME_ACCESS);
3115:
3116: CloseHandle(rdoutpipe);
3117: CloseHandle(wrinpipe);
3118:
3119: env_list=get_cgi_env(session);
3120: env_block = strListCreateBlock(env_list);
3121: strListFree(&env_list);
3122:
3123: success=CreateProcess(
3124: NULL, /* pointer to name of executable module */
3125: cmdline, /* pointer to command line string */
3126: NULL, /* process security attributes */
3127: NULL, /* thread security attributes */
3128: TRUE, /* handle inheritance flag */
3129: CREATE_NEW_CONSOLE, /* creation flags */
3130: env_block, /* pointer to new environment block */
3131: startup_dir, /* pointer to current directory name */
3132: &startup_info, /* pointer to STARTUPINFO */
3133: &process_info /* pointer to PROCESS_INFORMATION */
3134: );
3135:
3136: strListFreeBlock(env_block);
3137:
3138: if(!success) {
3139: lprintf(LOG_ERR,"%04d !ERROR %d running %s",session->socket,GetLastError(),cmdline);
3140: return(FALSE);
3141: }
3142:
3143: start=time(NULL);
3144:
3145: SAFECOPY(cgi_status,session->req.status);
3146: SAFEPRINTF2(content_type,"%s: %s",get_header(HEAD_TYPE),startup->default_cgi_content);
3147: while(server_socket!=INVALID_SOCKET) {
3148:
3149: if(WaitForSingleObject(process_info.hProcess,0)==WAIT_OBJECT_0)
3150: process_terminated=TRUE; /* handle remaining data in pipe before breaking */
3151:
3152: if((time(NULL)-start) >= startup->max_cgi_inactivity) {
3153: lprintf(LOG_WARNING,"%04d CGI Process %s timed out after %u seconds of inactivity"
3154: ,session->socket,getfname(cmdline),startup->max_cgi_inactivity);
3155: break;
3156: }
3157:
3158: waiting = 0;
3159: PeekNamedPipe(
3160: rdpipe, /* handle to pipe to copy from */
3161: NULL, /* pointer to data buffer */
3162: 0, /* size, in bytes, of data buffer */
3163: NULL, /* pointer to number of bytes read */
3164: &waiting, /* pointer to total number of bytes available */
3165: NULL /* pointer to unread bytes in this message */
3166: );
3167: if(!waiting) {
3168: if(process_terminated)
3169: break;
3170: Sleep(1);
3171: continue;
3172: }
3173: /* reset inactivity timer */
3174: start=time(NULL);
3175:
3176: msglen=0;
3177: if(done_parsing_headers) {
3178: if(ReadFile(rdpipe,buf,sizeof(buf),&msglen,NULL)==FALSE) {
3179: lprintf(LOG_ERR,"%04d !ERROR %d reading from pipe"
3180: ,session->socket,GetLastError());
3181: break;
3182: }
3183: }
3184: else {
3185: /* This is the tricky part */
3186: buf[0]=0;
3187: i=pipereadline(rdpipe,buf,sizeof(buf),NULL,0);
3188: if(i<0) {
3189: lprintf(LOG_WARNING,"%04d CGI pipereadline returned %d",session->socket,i);
3190: got_valid_headers=FALSE;
3191: break;
3192: }
3193: lprintf(LOG_DEBUG,"%04d CGI header line: %s"
3194: ,session->socket, buf);
3195: SAFECOPY(header,buf);
3196: if(strchr(header,':')!=NULL) {
3197: if((directive=strtok_r(header,":",&last))!=NULL)
3198: value=strtok_r(NULL,"",&last);
3199: else
3200: value="";
3201: i=get_header_type(directive);
3202: switch (i) {
3203: case HEAD_LOCATION:
3204: got_valid_headers=TRUE;
3205: if(*value=='/') {
3206: unescape(value);
3207: SAFECOPY(session->req.virtual_path,value);
3208: session->req.send_location=MOVED_STAT;
3209: if(cgi_status[0]==0)
3210: SAFECOPY(cgi_status,error_302);
3211: } else {
3212: SAFECOPY(session->req.virtual_path,value);
3213: session->req.send_location=MOVED_TEMP;
3214: if(cgi_status[0]==0)
3215: SAFECOPY(cgi_status,error_302);
3216: }
3217: break;
3218: case HEAD_STATUS:
3219: SAFECOPY(cgi_status,value);
3220: break;
3221: case HEAD_LENGTH:
3222: session->req.keep_alive=orig_keep;
3223: strListPush(&session->req.dynamic_heads,buf);
3224: no_chunked=TRUE;
3225: break;
3226: case HEAD_TYPE:
3227: got_valid_headers=TRUE;
3228: SAFECOPY(content_type,buf);
3229: break;
3230: case HEAD_TRANSFER_ENCODING:
3231: no_chunked=TRUE;
3232: break;
3233: default:
3234: strListPush(&session->req.dynamic_heads,buf);
3235: }
3236: continue;
3237: }
3238: if(i) {
3239: strcat(buf,"\r\n"); /* Add back the missing line terminator */
3240: msglen=strlen(buf); /* we will send this text later */
3241: }
3242: done_parsing_headers = TRUE; /* invalid header */
3243: session->req.dynamic=IS_CGI;
3244: if(!no_chunked && session->http_ver>=HTTP_1_1) {
3245: session->req.keep_alive=orig_keep;
3246: set_chunked=TRUE;
3247: }
3248: strListPush(&session->req.dynamic_heads,content_type);
3249: send_headers(session,cgi_status,set_chunked);
3250: }
3251: if(msglen) {
3252: lprintf(LOG_DEBUG,"%04d Sending %d bytes: %.*s"
3253: ,session->socket,msglen,msglen,buf);
3254: wr=writebuf(session,buf,msglen);
3255: /* log actual bytes sent */
3256: if(session->req.ld!=NULL && wr>0)
3257: session->req.ld->size+=wr;
3258: }
3259: }
3260:
3261: if(GetExitCodeProcess(process_info.hProcess, &retval)==FALSE)
3262: lprintf(LOG_ERR,"%04d !ERROR GetExitCodeProcess(%s) returned %d"
3263: ,session->socket,getfname(cmdline),GetLastError());
3264:
3265: if(retval==STILL_ACTIVE) {
3266: lprintf(LOG_WARNING,"%04d Terminating CGI process: %s"
3267: ,session->socket,getfname(cmdline));
3268: TerminateProcess(process_info.hProcess, GetLastError());
3269: }
3270:
3271: if(rdpipe!=INVALID_HANDLE_VALUE)
3272: CloseHandle(rdpipe);
3273: if(wrpipe!=INVALID_HANDLE_VALUE)
3274: CloseHandle(wrpipe);
3275: CloseHandle(process_info.hProcess);
3276:
3277: if(!got_valid_headers)
3278: lprintf(LOG_WARNING,"%04d !CGI Process %s did not generate valid headers"
3279: ,session->socket,getfname(cmdline));
3280:
3281: if(!done_parsing_headers)
3282: lprintf(LOG_WARNING,"%04d !CGI Process %s did not send data header termination"
3283: ,session->socket,getfname(cmdline));
3284:
3285: return(TRUE);
3286: #endif
3287: }
3288:
3289: /********************/
3290: /* JavaScript stuff */
3291: /********************/
3292:
3293: JSObject* DLLCALL js_CreateHttpReplyObject(JSContext* cx
3294: ,JSObject* parent, http_session_t *session)
3295: {
3296: JSObject* reply;
3297: JSObject* headers;
3298: jsval val;
3299: JSString* js_str;
3300:
3301: /* Return existing object if it's already been created */
3302: if(JS_GetProperty(cx,parent,"http_reply",&val) && val!=JSVAL_VOID) {
3303: reply = JSVAL_TO_OBJECT(val);
3304: JS_ClearScope(cx,reply);
3305: }
3306: else
3307: reply = JS_DefineObject(cx, parent, "http_reply", NULL
3308: , NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
3309:
3310: if((js_str=JS_NewStringCopyZ(cx, session->req.status))==NULL)
3311: return(FALSE);
3312: JS_DefineProperty(cx, reply, "status", STRING_TO_JSVAL(js_str)
3313: ,NULL,NULL,JSPROP_ENUMERATE);
3314:
3315: /* Return existing object if it's already been created */
3316: if(JS_GetProperty(cx,reply,"header",&val) && val!=JSVAL_VOID) {
3317: headers = JSVAL_TO_OBJECT(val);
3318: JS_ClearScope(cx,headers);
3319: }
3320: else
3321: headers = JS_DefineObject(cx, reply, "header", NULL
3322: , NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
3323:
3324: if((js_str=JS_NewStringCopyZ(cx, "text/html"))==NULL)
3325: return(FALSE);
3326: JS_DefineProperty(cx, headers, "Content-Type", STRING_TO_JSVAL(js_str)
3327: ,NULL,NULL,JSPROP_ENUMERATE);
3328:
3329: return(reply);
3330: }
3331:
3332: JSObject* DLLCALL js_CreateHttpRequestObject(JSContext* cx
3333: ,JSObject* parent, http_session_t *session)
3334: {
3335: /* JSObject* cookie; */
3336: jsval val;
3337:
3338: /* Return existing object if it's already been created */
3339: if(JS_GetProperty(cx,parent,"http_request",&val) && val!=JSVAL_VOID) {
3340: session->js_request=JSVAL_TO_OBJECT(val);
3341: }
3342: else
3343: session->js_request = JS_DefineObject(cx, parent, "http_request", NULL
3344: , NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
3345:
3346: js_add_request_prop(session,"path_info",session->req.extra_path_info);
3347: js_add_request_prop(session,"method",methods[session->req.method]);
3348: js_add_request_prop(session,"virtual_path",session->req.virtual_path);
3349:
3350: /* Return existing object if it's already been created */
3351: if(JS_GetProperty(cx,session->js_request,"query",&val) && val!=JSVAL_VOID) {
3352: session->js_query = JSVAL_TO_OBJECT(val);
3353: JS_ClearScope(cx,session->js_query);
3354: }
3355: else
3356: session->js_query = JS_DefineObject(cx, session->js_request, "query", NULL
3357: , NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
3358:
3359: /* Return existing object if it's already been created */
3360: if(JS_GetProperty(cx,session->js_request,"header",&val) && val!=JSVAL_VOID) {
3361: session->js_header = JSVAL_TO_OBJECT(val);
3362: JS_ClearScope(cx,session->js_header);
3363: }
3364: else
3365: session->js_header = JS_DefineObject(cx, session->js_request, "header", NULL
3366: , NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
3367:
3368: /* Return existing object if it's already been created */
3369: if(JS_GetProperty(cx,session->js_request,"cookie",&val) && val!=JSVAL_VOID) {
3370: session->js_cookie = JSVAL_TO_OBJECT(val);
3371: JS_ClearScope(cx,session->js_cookie);
3372: }
3373: else
3374: session->js_cookie = JS_DefineObject(cx, session->js_request, "cookie", NULL
3375: , NULL, JSPROP_ENUMERATE|JSPROP_READONLY);
3376:
3377:
3378: return(session->js_request);
3379: }
3380:
3381: static void
3382: js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
3383: {
3384: char line[64];
3385: char file[MAX_PATH+1];
3386: char* warning;
3387: http_session_t* session;
3388:
3389: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3390: return;
3391:
3392: if(report==NULL) {
3393: lprintf(LOG_ERR,"%04d !JavaScript: %s", session->socket, message);
3394: if(session->req.fp!=NULL)
3395: fprintf(session->req.fp,"!JavaScript: %s", message);
3396: return;
3397: }
3398:
3399: if(report->filename)
3400: sprintf(file," %s",report->filename);
3401: else
3402: file[0]=0;
3403:
3404: if(report->lineno)
3405: sprintf(line," line %u",report->lineno);
3406: else
3407: line[0]=0;
3408:
3409: if(JSREPORT_IS_WARNING(report->flags)) {
3410: if(JSREPORT_IS_STRICT(report->flags))
3411: warning="strict warning";
3412: else
3413: warning="warning";
3414: } else
3415: warning="";
3416:
3417: lprintf(LOG_ERR,"%04d !JavaScript %s%s%s: %s",session->socket,warning,file,line,message);
3418: if(session->req.fp!=NULL)
3419: fprintf(session->req.fp,"!JavaScript %s%s%s: %s",warning,file,line,message);
3420: }
3421:
3422: static void js_writebuf(http_session_t *session, const char *buf, size_t buflen)
3423: {
3424: if(session->req.sent_headers) {
3425: if(session->req.method!=HTTP_HEAD && session->req.method!=HTTP_OPTIONS)
3426: writebuf(session,buf,buflen);
3427: }
3428: else
3429: fwrite(buf,1,buflen,session->req.fp);
3430: }
3431:
3432: static JSBool
3433: js_writefunc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval, BOOL writeln)
3434: {
3435: uintN i;
3436: JSString* str=NULL;
3437: http_session_t* session;
3438:
3439: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3440: return(JS_FALSE);
3441:
3442: if(session->req.fp==NULL)
3443: return(JS_FALSE);
3444:
3445: if((!session->req.prev_write) && (!session->req.sent_headers)) {
3446: if(session->http_ver>=HTTP_1_1 && session->req.keep_alive) {
3447: if(!ssjs_send_headers(session,TRUE))
3448: return(JS_FALSE);
3449: }
3450: else {
3451: /* "Fast Mode" requested? */
3452: jsval val;
3453: JSObject* reply;
3454: JS_GetProperty(cx, session->js_glob, "http_reply", &val);
3455: reply=JSVAL_TO_OBJECT(val);
3456: JS_GetProperty(cx, reply, "fast", &val);
3457: if(JSVAL_IS_BOOLEAN(val) && JSVAL_TO_BOOLEAN(val)) {
3458: session->req.keep_alive=FALSE;
3459: if(!ssjs_send_headers(session,FALSE))
3460: return(JS_FALSE);
3461: }
3462: }
3463: }
3464:
3465: session->req.prev_write=TRUE;
3466:
3467: for(i=0; i<argc; i++) {
3468: if((str=JS_ValueToString(cx, argv[i]))==NULL)
3469: continue;
3470: if(JS_GetStringLength(str)<1 && !writeln)
3471: continue;
3472: js_writebuf(session,JS_GetStringBytes(str), JS_GetStringLength(str));
3473: if(writeln)
3474: js_writebuf(session, newline, 2);
3475: }
3476:
3477: if(str==NULL)
3478: *rval = JSVAL_VOID;
3479: else
3480: *rval = STRING_TO_JSVAL(str);
3481:
3482: return(JS_TRUE);
3483: }
3484:
3485: static JSBool
3486: js_write(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3487: {
3488: http_session_t* session;
3489:
3490: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3491: return(JS_FALSE);
3492:
3493: js_writefunc(cx, obj, argc, argv, rval,FALSE);
3494:
3495: return(JS_TRUE);
3496: }
3497:
3498: static JSBool
3499: js_writeln(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3500: {
3501: http_session_t* session;
3502:
3503: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3504: return(JS_FALSE);
3505:
3506: js_writefunc(cx, obj, argc, argv, rval,TRUE);
3507:
3508: return(JS_TRUE);
3509: }
3510:
3511: static JSBool
3512: js_set_cookie(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3513: {
3514: char header_buf[8192];
3515: char *header;
3516: char *p;
3517: int32 i;
3518: JSBool b;
3519: struct tm tm;
3520: http_session_t* session;
3521:
3522: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3523: return(JS_FALSE);
3524:
3525: if(argc<2)
3526: return(JS_FALSE);
3527:
3528: header=header_buf;
3529: p=js_ValueToStringBytes(cx, argv[0],NULL);
3530: if(!p)
3531: return(JS_FALSE);
3532: header+=sprintf(header,"Set-Cookie: %s=",p);
3533: p=js_ValueToStringBytes(cx, argv[1],NULL);
3534: if(!p)
3535: return(JS_FALSE);
3536: header+=sprintf(header,"%s",p);
3537: if(argc>2) {
3538: JS_ValueToInt32(cx,argv[2],&i);
3539: if(i && gmtime_r((time_t *)&i,&tm)!=NULL)
3540: header += strftime(header,50,"; expires=%a, %d-%b-%Y %H:%M:%S GMT",&tm);
3541: }
3542: if(argc>3) {
3543: if(((p=js_ValueToStringBytes(cx, argv[3], NULL))!=NULL) && *p)
3544: header += sprintf(header,"; domain=%s",p);
3545: }
3546: if(argc>4) {
3547: if(((p=js_ValueToStringBytes(cx, argv[4], NULL))!=NULL) && *p)
3548: header += sprintf(header,"; path=%s",p);
3549: }
3550: if(argc>5) {
3551: JS_ValueToBoolean(cx, argv[5], &b);
3552: if(b)
3553: header += sprintf(header,"; secure");
3554: }
3555: strListPush(&session->req.dynamic_heads,header_buf);
3556:
3557: return(JS_TRUE);
3558: }
3559:
3560: static JSBool
3561: js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3562: {
3563: char str[512];
3564: uintN i=0;
3565: int32 level=LOG_INFO;
3566: JSString* js_str;
3567: http_session_t* session;
3568:
3569: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3570: return(JS_FALSE);
3571:
3572: if(startup==NULL || startup->lputs==NULL)
3573: return(JS_FALSE);
3574:
3575: if(argc > 1 && JSVAL_IS_NUMBER(argv[i]))
3576: JS_ValueToInt32(cx,argv[i++],&level);
3577:
3578: str[0]=0;
3579: for(;i<argc && strlen(str)<(sizeof(str)/2);i++) {
3580: if((js_str=JS_ValueToString(cx, argv[i]))==NULL)
3581: return(JS_FALSE);
3582: strncat(str,JS_GetStringBytes(js_str),sizeof(str)/2);
3583: strcat(str," ");
3584: }
3585:
3586: lprintf(level,"%04d %s",session->socket,str);
3587:
3588: *rval = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, str));
3589:
3590: return(JS_TRUE);
3591: }
3592:
3593: static JSBool
3594: js_login(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3595: {
3596: char* p;
3597: JSBool inc_logons=JS_FALSE;
3598: user_t user;
3599: JSString* js_str;
3600: http_session_t* session;
3601:
3602: *rval = BOOLEAN_TO_JSVAL(JS_FALSE);
3603:
3604: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3605: return(JS_FALSE);
3606:
3607: /* User name */
3608: if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
3609: return(JS_FALSE);
3610:
3611: if((p=JS_GetStringBytes(js_str))==NULL)
3612: return(JS_FALSE);
3613:
3614: memset(&user,0,sizeof(user));
3615:
3616: if(isdigit(*p))
3617: user.number=atoi(p);
3618: else if(*p)
3619: user.number=matchuser(&scfg,p,FALSE);
3620:
3621: if(getuserdat(&scfg,&user)!=0) {
3622: lprintf(LOG_NOTICE,"%04d !USER NOT FOUND: '%s'"
3623: ,session->socket,p);
3624: return(JS_TRUE);
3625: }
3626:
3627: if(user.misc&(DELETED|INACTIVE)) {
3628: lprintf(LOG_WARNING,"%04d !DELETED OR INACTIVE USER #%d: %s"
3629: ,session->socket,user.number,p);
3630: return(JS_TRUE);
3631: }
3632:
3633: /* Password */
3634: if(user.pass[0]) {
3635: if((js_str=JS_ValueToString(cx, argv[1]))==NULL)
3636: return(JS_FALSE);
3637:
3638: if((p=JS_GetStringBytes(js_str))==NULL)
3639: return(JS_FALSE);
3640:
3641: if(stricmp(user.pass,p)) { /* Wrong password */
3642: lprintf(LOG_WARNING,"%04d !INVALID PASSWORD ATTEMPT FOR USER: %s"
3643: ,session->socket,user.alias);
3644: return(JS_TRUE);
3645: }
3646: }
3647:
3648: if(argc>2)
3649: JS_ValueToBoolean(cx,argv[2],&inc_logons);
3650:
3651: if(inc_logons) {
3652: user.logons++;
3653: user.ltoday++;
3654: }
3655:
3656: http_logon(session, &user);
3657:
3658: /* user-specific objects */
3659: if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
3660: ,NULL /* ftp index file */, session->subscan /* subscan */)) {
3661: lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
3662: send_error(session,"500 Error initializing JavaScript User Objects");
3663: return(FALSE);
3664: }
3665:
3666: *rval=BOOLEAN_TO_JSVAL(JS_TRUE);
3667:
3668: return(JS_TRUE);
3669: }
3670:
3671: #if 0
3672: static char *find_next_pair(char *buffer, size_t buflen, char find)
3673: {
3674: char *p;
3675: char *search;
3676: char *end;
3677: size_t buflen2;
3678: char chars[5]="@%^<";
3679:
3680: end=buffer+buflen;
3681: search=buffer;
3682: buflen2=buflen;
3683:
3684: for(;search<end;) {
3685: p=memchr(search, chars[i], buflen2);
3686: /* Can't even find one... there's definatly no pair */
3687: if(p==NULL)
3688: return(NULL);
3689:
3690: if(*(p+1)==find)
3691: return(p);
3692:
3693: /* Next search pos is at the char after the match */
3694: search=p+1;
3695: buflen2=end-search;
3696: }
3697: }
3698:
3699: static void js_write_escaped(JSContext *cx, JSObject *obj, char *pos, size_t len, char *name_end, char *repeat_section)
3700: {
3701: char *name=pos+2;
3702:
3703: }
3704:
3705: enum {
3706: T_AT
3707: ,T_PERCENT
3708: ,T_CARET
3709: ,T_LT
3710: };
3711:
3712: static int js_write_template_part(JSContext *cx, JSObject *obj, char *template, size_t len, char *repeat_section)
3713: {
3714: size_t len2;
3715: char *pos;
3716: char *end;
3717: char *p;
3718: char *p2;
3719: char *send_end;
3720: int no_more[4];
3721: char *next[4];
3722: int i,j;
3723: char chars[5]="@%^<";
3724:
3725: end=template+len;
3726: pos=template;
3727: memset(&next,0,sizeof(next));
3728: memset(&no_more,0,sizeof(no_more));
3729:
3730: while(pos<end) {
3731: send_end=NULL;
3732:
3733: /* Find next seperator */
3734: for(i=0; i<4; i++) {
3735: if(!no_more[i]) {
3736: if(next[i] < pos)
3737: next[i]=NULL;
3738: if(next[i] == NULL) {
3739: if((next[i]=find_next_pair(pos, len, chars[i]))==NULL) {
3740: no_more[i]=TRUE;
3741: continue;
3742: }
3743: }
3744: if(!send_end || next[i] < send_end)
3745: send_end=next[i];
3746: }
3747: }
3748: if(send_end==NULL) {
3749: /* Nothing else matched... we're good now! */
3750: js_writebuf(session, pos, len);
3751: pos=end;
3752: len=0;
3753: continue;
3754: }
3755: if(send_end > pos) {
3756: i=send_end-pos;
3757: js_writebuf(session, pos, i);
3758: pos+=i;
3759: len-=i;
3760: }
3761:
3762: /*
3763: * At this point, pos points to a matched introducer.
3764: * If it's not a repeat section, we can just output it here.
3765: */
3766: if(*pos != '<') {
3767: /*
3768: * If there is no corresponding terminator to this introdcer,
3769: * force it to be output unchanged.
3770: */
3771: if((p=find_next_pair(pos, len, *pos))==NULL) {
3772: no_more[strchr(chars,*pos)-char]=TRUE;
3773: continue;
3774: }
3775: js_write_escaped(cx, obj, pos, len, p, repeat_section);
3776: continue;
3777: }
3778:
3779: /*
3780: * Pos is the start of a repeat section now... this is where things
3781: * start to get tricky. Set up RepeatObj object, then call self
3782: * once for each repeat.
3783: */
3784: }
3785: }
3786:
3787: static JSBool
3788: js_write_template(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3789: {
3790: JSString* js_str;
3791: char *filename;
3792: char *template;
3793: FILE *tfile;
3794: size_t len;
3795: http_session_t* session;
3796:
3797: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3798: return(JS_FALSE);
3799:
3800: if(session->req.fp==NULL)
3801: return(JS_FALSE);
3802:
3803: if((filename=js_ValueToStringBytes(cx, argv[0]))==NULL)
3804: return(JS_FALSE);
3805:
3806: if(!fexist(filename)) {
3807: JS_ReportError(cx, "Template file %s does not exist.", filename);
3808: return(JS_FALSE);
3809: }
3810: len=flength(filename);
3811:
3812: if((tfile=fopen(filename,"r"))==NULL) {
3813: JS_ReportError(cx, "Unable to open template %s for read.", filename);
3814: return(JS_FALSE);
3815: }
3816:
3817: if((template=(char *)alloca(len))==NULL) {
3818: JS_ReportError(cx, "Unable to allocate %u bytes for template.", len);
3819: return(JS_FALSE);
3820: }
3821:
3822: if(fread(template, 1, len, tfile) != len) {
3823: fclose(tfile);
3824: JS_ReportError(cx, "Unable to read %u bytes from template %s.", len, filename);
3825: return(JS_FALSE);
3826: }
3827: fclose(tfile);
3828:
3829: if((!session->req.prev_write) && (!session->req.sent_headers)) {
3830: if(session->http_ver>=HTTP_1_1 && session->req.keep_alive) {
3831: if(!ssjs_send_headers(session,TRUE))
3832: return(JS_FALSE);
3833: }
3834: else {
3835: /* "Fast Mode" requested? */
3836: jsval val;
3837: JSObject* reply;
3838: JS_GetProperty(cx, session->js_glob, "http_reply", &val);
3839: reply=JSVAL_TO_OBJECT(val);
3840: JS_GetProperty(cx, reply, "fast", &val);
3841: if(JSVAL_IS_BOOLEAN(val) && JSVAL_TO_BOOLEAN(val)) {
3842: session->req.keep_alive=FALSE;
3843: if(!ssjs_send_headers(session,FALSE))
3844: return(JS_FALSE);
3845: }
3846: }
3847: }
3848:
3849: session->req.prev_write=TRUE;
3850: js_write_template_part(cx, obj, template, len, NULL);
3851:
3852: return(JS_TRUE);
3853: }
3854: #endif
3855:
3856: static JSFunctionSpec js_global_functions[] = {
3857: {"write", js_write, 1}, /* write to HTML file */
3858: {"writeln", js_writeln, 1}, /* write line to HTML file */
3859: {"print", js_writeln, 1}, /* write line to HTML file (alias) */
3860: {"log", js_log, 0}, /* Log a string */
3861: {"login", js_login, 2}, /* log in as a different user */
3862: {"set_cookie", js_set_cookie, 2}, /* Set a cookie */
3863: {0}
3864: };
3865:
3866: static JSBool
3867: js_BranchCallback(JSContext *cx, JSScript *script)
3868: {
3869: http_session_t* session;
3870:
3871: if((session=(http_session_t*)JS_GetContextPrivate(cx))==NULL)
3872: return(JS_FALSE);
3873:
3874: return(js_CommonBranchCallback(cx,&session->js_branch));
3875: }
3876:
3877: static JSContext*
3878: js_initcx(http_session_t *session)
3879: {
3880: JSContext* js_cx;
3881:
3882: lprintf(LOG_INFO,"%04d JavaScript: Initializing context (stack: %lu bytes)"
3883: ,session->socket,startup->js.cx_stack);
3884:
3885: if((js_cx = JS_NewContext(session->js_runtime, startup->js.cx_stack))==NULL)
3886: return(NULL);
3887:
3888: lprintf(LOG_INFO,"%04d JavaScript: Context created",session->socket);
3889:
3890: JS_SetErrorReporter(js_cx, js_ErrorReporter);
3891:
3892: JS_SetBranchCallback(js_cx, js_BranchCallback);
3893:
3894: lprintf(LOG_INFO,"%04d JavaScript: Creating Global Objects and Classes",session->socket);
3895: if((session->js_glob=js_CreateCommonObjects(js_cx, &scfg, NULL
3896: ,NULL /* global */
3897: ,uptime /* system */
3898: ,startup->host_name /* system */
3899: ,SOCKLIB_DESC /* system */
3900: ,&session->js_branch /* js */
3901: ,&session->client /* client */
3902: ,session->socket /* client */
3903: ,&js_server_props /* server */
3904: ))==NULL
3905: || !JS_DefineFunctions(js_cx, session->js_glob, js_global_functions)) {
3906: JS_DestroyContext(js_cx);
3907: return(NULL);
3908: }
3909:
3910: return(js_cx);
3911: }
3912:
3913: static BOOL js_setup(http_session_t* session)
3914: {
3915: JSObject* argv;
3916:
3917: #ifndef ONE_JS_RUNTIME
3918: if(session->js_runtime == NULL) {
3919: lprintf(LOG_INFO,"%04d JavaScript: Creating runtime: %lu bytes"
3920: ,session->socket,startup->js.max_bytes);
3921:
3922: if((session->js_runtime=JS_NewRuntime(startup->js.max_bytes))==NULL) {
3923: lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",session->socket);
3924: return(FALSE);
3925: }
3926: }
3927: #endif
3928:
3929: if(session->js_cx==NULL) { /* Context not yet created, create it now */
3930: if(((session->js_cx=js_initcx(session))==NULL)) {
3931: lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript context",session->socket);
3932: return(FALSE);
3933: }
3934: argv=JS_NewArrayObject(session->js_cx, 0, NULL);
3935:
3936: JS_DefineProperty(session->js_cx, session->js_glob, "argv", OBJECT_TO_JSVAL(argv)
3937: ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
3938: JS_DefineProperty(session->js_cx, session->js_glob, "argc", INT_TO_JSVAL(0)
3939: ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
3940:
3941: JS_DefineProperty(session->js_cx, session->js_glob, "web_root_dir",
3942: STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx, root_dir))
3943: ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
3944: JS_DefineProperty(session->js_cx, session->js_glob, "web_error_dir",
3945: STRING_TO_JSVAL(JS_NewStringCopyZ(session->js_cx, session->req.error_dir?session->req.error_dir:error_dir))
3946: ,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);
3947:
3948: }
3949:
3950: lprintf(LOG_INFO,"%04d JavaScript: Initializing HttpRequest object",session->socket);
3951: if(js_CreateHttpRequestObject(session->js_cx, session->js_glob, session)==NULL) {
3952: lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript HttpRequest object",session->socket);
3953: return(FALSE);
3954: }
3955:
3956: lprintf(LOG_INFO,"%04d JavaScript: Initializing HttpReply object",session->socket);
3957: if(js_CreateHttpReplyObject(session->js_cx, session->js_glob, session)==NULL) {
3958: lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript HttpReply object",session->socket);
3959: return(FALSE);
3960: }
3961:
3962: JS_SetContextPrivate(session->js_cx, session);
3963:
3964: return(TRUE);
3965: }
3966:
3967: static BOOL ssjs_send_headers(http_session_t* session,int chunked)
3968: {
3969: jsval val;
3970: JSObject* reply;
3971: JSIdArray* heads;
3972: JSObject* headers;
3973: int i;
3974: JSString* js_str;
3975: char str[MAX_REQUEST_LINE+1];
3976:
3977: JS_GetProperty(session->js_cx,session->js_glob,"http_reply",&val);
3978: reply = JSVAL_TO_OBJECT(val);
3979: JS_GetProperty(session->js_cx,reply,"status",&val);
3980: SAFECOPY(session->req.status,JS_GetStringBytes(JSVAL_TO_STRING(val)));
3981: JS_GetProperty(session->js_cx,reply,"header",&val);
3982: headers = JSVAL_TO_OBJECT(val);
3983: heads=JS_Enumerate(session->js_cx,headers);
3984: if(heads != NULL) {
3985: for(i=0;i<heads->length;i++) {
3986: JS_IdToValue(session->js_cx,heads->vector[i],&val);
3987: js_str=JSVAL_TO_STRING(val);
3988: JS_GetProperty(session->js_cx,headers,JS_GetStringBytes(js_str),&val);
3989: safe_snprintf(str,sizeof(str),"%s: %s"
3990: ,JS_GetStringBytes(js_str),JS_GetStringBytes(JSVAL_TO_STRING(val)));
3991: strListPush(&session->req.dynamic_heads,str);
3992: }
3993: JS_ClearScope(session->js_cx, headers);
3994: }
3995: return(send_headers(session,session->req.status,chunked));
3996: }
3997:
3998: static BOOL exec_ssjs(http_session_t* session, char* script) {
3999: JSScript* js_script;
4000: jsval rval;
4001: char path[MAX_PATH+1];
4002: BOOL retval=TRUE;
4003: long double start;
4004:
4005: /* External JavaScript handler? */
4006: if(script == session->req.physical_path && session->req.xjs_handler[0])
4007: script = session->req.xjs_handler;
4008:
4009: sprintf(path,"%sSBBS_SSJS.%u.%u.html",temp_dir,getpid(),session->socket);
4010: if((session->req.fp=fopen(path,"wb"))==NULL) {
4011: lprintf(LOG_ERR,"%04d !ERROR %d opening/creating %s", session->socket, errno, path);
4012: return(FALSE);
4013: }
4014: if(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]) {
4015: if(!(startup->options&WEB_OPT_DEBUG_SSJS))
4016: remove(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]);
4017: free(session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]);
4018: }
4019: /* FREE()d in close_request() */
4020: session->req.cleanup_file[CLEANUP_SSJS_TMP_FILE]=strdup(path);
4021:
4022: js_add_request_prop(session,"real_path",session->req.physical_path);
4023: js_add_request_prop(session,"virtual_path",session->req.virtual_path);
4024: js_add_request_prop(session,"ars",session->req.ars);
4025: js_add_request_prop(session,"request_string",session->req.request_line);
4026: js_add_request_prop(session,"host",session->req.host);
4027: js_add_request_prop(session,"vhost",session->req.vhost);
4028: js_add_request_prop(session,"http_ver",http_vers[session->http_ver]);
4029: js_add_request_prop(session,"remote_ip",session->host_ip);
4030: js_add_request_prop(session,"remote_host",session->host_name);
4031: if(session->req.query_str && session->req.query_str[0]) {
4032: js_add_request_prop(session,"query_string",session->req.query_str);
4033: js_parse_query(session,session->req.query_str);
4034: }
4035: if(session->req.post_data && session->req.post_data[0]) {
4036: js_add_request_prop(session,"post_data",session->req.post_data);
4037: js_parse_query(session,session->req.post_data);
4038: }
4039:
4040: do {
4041: /* RUN SCRIPT */
4042: JS_ClearPendingException(session->js_cx);
4043:
4044: session->js_branch.counter=0;
4045:
4046: lprintf(LOG_DEBUG,"%04d JavaScript: Compiling script: %s",session->socket,script);
4047: if((js_script=JS_CompileFile(session->js_cx, session->js_glob
4048: ,script))==NULL) {
4049: lprintf(LOG_ERR,"%04d !JavaScript FAILED to compile script (%s)"
4050: ,session->socket,script);
4051: return(FALSE);
4052: }
4053:
4054: lprintf(LOG_DEBUG,"%04d JavaScript: Executing script: %s",session->socket,script);
4055: start=xp_timer();
4056: JS_ExecuteScript(session->js_cx, session->js_glob, js_script, &rval);
4057: js_EvalOnExit(session->js_cx, session->js_glob, &session->js_branch);
4058: lprintf(LOG_DEBUG,"%04d JavaScript: Done executing script: %s (%.2Lf seconds)"
4059: ,session->socket,script,xp_timer()-start);
4060: } while(0);
4061:
4062: SAFECOPY(session->req.physical_path, path);
4063: if(session->req.fp!=NULL) {
4064: fclose(session->req.fp);
4065: session->req.fp=NULL;
4066: }
4067:
4068:
4069: /* Read http_reply object */
4070: if(!session->req.sent_headers) {
4071: retval=ssjs_send_headers(session,FALSE);
4072: }
4073:
4074: /* Free up temporary resources here */
4075:
4076: if(js_script!=NULL)
4077: JS_DestroyScript(session->js_cx, js_script);
4078: session->req.dynamic=IS_SSJS;
4079:
4080: return(retval);
4081: }
4082:
4083: static void respond(http_session_t * session)
4084: {
4085: BOOL send_file=TRUE;
4086:
4087: if(session->req.method==HTTP_OPTIONS) {
4088: send_headers(session,session->req.status,FALSE);
4089: }
4090: else {
4091: if(session->req.dynamic==IS_CGI) {
4092: if(!exec_cgi(session)) {
4093: send_error(session,error_500);
4094: return;
4095: }
4096: session->req.finished=TRUE;
4097: return;
4098: }
4099:
4100: if(session->req.dynamic==IS_SSJS) { /* Server-Side JavaScript */
4101: if(!exec_ssjs(session,session->req.physical_path)) {
4102: send_error(session,error_500);
4103: return;
4104: }
4105: sprintf(session->req.physical_path
4106: ,"%sSBBS_SSJS.%u.%u.html",temp_dir,getpid(),session->socket);
4107: }
4108: else {
4109: session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
4110: send_file=send_headers(session,session->req.status,FALSE);
4111: }
4112: }
4113: if(session->req.method==HTTP_HEAD || session->req.method==HTTP_OPTIONS)
4114: send_file=FALSE;
4115: if(send_file) {
4116: int snt=0;
4117: lprintf(LOG_INFO,"%04d Sending file: %s (%u bytes)"
4118: ,session->socket, session->req.physical_path, flength(session->req.physical_path));
4119: snt=sock_sendfile(session,session->req.physical_path,session->req.range_start,session->req.range_end);
4120: if(session->req.ld!=NULL) {
4121: if(snt<0)
4122: snt=0;
4123: session->req.ld->size=snt;
4124: }
4125: if(snt>0)
4126: lprintf(LOG_INFO,"%04d Sent file: %s (%d bytes)"
4127: ,session->socket, session->req.physical_path, snt);
4128: }
4129: session->req.finished=TRUE;
4130: }
4131:
4132: int read_post_data(http_session_t * session)
4133: {
4134: unsigned i=0;
4135:
4136: if(session->req.dynamic!=IS_CGI && (session->req.post_len || session->req.read_chunked)) {
4137: if(session->req.read_chunked) {
4138: char *p;
4139: size_t ch_len=0;
4140: int bytes_read=0;
4141: char ch_lstr[12];
4142: session->req.post_len=0;
4143:
4144: while(1) {
4145: /* Read chunk length */
4146: if(sockreadline(session,ch_lstr,sizeof(ch_lstr)-1)>0) {
4147: ch_len=strtol(ch_lstr,NULL,16);
4148: }
4149: else {
4150: send_error(session,error_500);
4151: return(FALSE);
4152: }
4153: if(ch_len==0)
4154: break;
4155: /* Check size */
4156: i += ch_len;
4157: if(i > MAX_POST_LEN) {
4158: send_error(session,"413 Request entity too large");
4159: return(FALSE);
4160: }
4161: /* realloc() to new size */
4162: /* FREE()d in close_request */
4163: p=realloc(session->req.post_data, i);
4164: if(p==NULL) {
4165: lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,session->req.post_len);
4166: send_error(session,"413 Request entity too large");
4167: return(FALSE);
4168: }
4169: session->req.post_data=p;
4170: /* read new data */
4171: bytes_read=recvbufsocket(&session->socket,session->req.post_data+session->req.post_len,ch_len);
4172: if(!bytes_read) {
4173: send_error(session,error_500);
4174: return(FALSE);
4175: }
4176: session->req.post_len+=bytes_read;
4177: }
4178: /* Read more headers! */
4179: if(!get_request_headers(session))
4180: return(FALSE);
4181: if(!parse_headers(session))
4182: return(FALSE);
4183: }
4184: else {
4185: i = session->req.post_len;
4186: FREE_AND_NULL(session->req.post_data);
4187: /* FREE()d in close_request() */
4188: if(i < (MAX_POST_LEN+1) && (session->req.post_data=malloc(i+1)) != NULL)
4189: session->req.post_len=recvbufsocket(&session->socket,session->req.post_data,i);
4190: else {
4191: lprintf(LOG_CRIT,"%04d !ERROR Allocating %d bytes of memory",session->socket,i);
4192: send_error(session,"413 Request entity too large");
4193: return(FALSE);
4194: }
4195: }
4196: if(session->req.post_len != i)
4197: lprintf(LOG_DEBUG,"%04d !ERROR Browser said they sent %d bytes, but I got %d",session->socket,i,session->req.post_len);
4198: if(session->req.post_len > i)
4199: session->req.post_len = i;
4200: session->req.post_data[session->req.post_len]=0;
4201: }
4202: return(TRUE);
4203: }
4204:
4205: void http_output_thread(void *arg)
4206: {
4207: http_session_t *session=(http_session_t *)arg;
4208: RingBuf *obuf;
4209: char buf[OUTBUF_LEN+12]; /* *MUST* be large enough to hold the buffer,
4210: the size of the buffer in hex, and four extra bytes. */
4211: char *bufdata;
4212: int failed=0;
4213: int len;
4214: unsigned avail;
4215: int chunked;
4216: int i;
4217: unsigned mss=OUTBUF_LEN;
4218:
4219: obuf=&(session->outbuf);
4220: /* Destroyed at end of function */
4221: if((i=pthread_mutex_init(&session->outbuf_write,NULL))!=0) {
4222: lprintf(LOG_DEBUG,"Error %d initializing outbuf mutex",i);
4223: close_socket(&session->socket);
4224: return;
4225: }
4226: session->outbuf_write_initialized=1;
4227:
4228: #ifdef TCP_MAXSEG
4229: /*
4230: * Auto-tune the highwater mark to be the negotiated MSS for the
4231: * socket (when possible)
4232: */
4233: if(!obuf->highwater_mark) {
4234: socklen_t sl;
4235: sl=sizeof(i);
4236: if(!getsockopt(session->socket, IPPROTO_TCP, TCP_MAXSEG, &i, &sl)) {
4237: /* Check for sanity... */
4238: if(i>100) {
4239: obuf->highwater_mark=i-12;
4240: lprintf(LOG_DEBUG,"Autotuning outbuf highwater mark to %d based on MSS",i);
4241: mss=obuf->highwater_mark;
4242: if(mss>OUTBUF_LEN) {
4243: mss=OUTBUF_LEN;
4244: lprintf(LOG_DEBUG,"MSS (%d) is higher than OUTBUF_LEN (%d)",i,OUTBUF_LEN);
4245: }
4246: }
4247: }
4248: }
4249: #endif
4250:
4251: thread_up(TRUE /* setuid */);
4252: /*
4253: * Do *not* exit on terminate_server... wait for session thread
4254: * to close the socket and set it to INVALID_SOCKET
4255: */
4256: while(session->socket!=INVALID_SOCKET) {
4257:
4258: /* Wait for something to output in the RingBuffer */
4259: if((avail=RingBufFull(obuf))==0) { /* empty */
4260: if(sem_trywait_block(&obuf->sem,1000))
4261: continue;
4262: /* Check for spurious sem post... */
4263: if((avail=RingBufFull(obuf))==0)
4264: continue;
4265: }
4266: else
4267: sem_trywait(&obuf->sem);
4268:
4269: /* Wait for full buffer or drain timeout */
4270: if(obuf->highwater_mark) {
4271: if(avail<obuf->highwater_mark) {
4272: sem_trywait_block(&obuf->highwater_sem,startup->outbuf_drain_timeout);
4273: /* We (potentially) blocked, so get fill level again */
4274: avail=RingBufFull(obuf);
4275: } else
4276: sem_trywait(&obuf->highwater_sem);
4277: }
4278:
4279: /*
4280: * At this point, there's something to send and,
4281: * if the highwater mark is set, the timeout has
4282: * passed or we've hit highwater. Read ring buffer
4283: * into linear buffer.
4284: */
4285: len=avail;
4286: if(avail>mss)
4287: len=(avail=mss);
4288:
4289: /*
4290: * Read the current value of write_chunked... since we wait until the
4291: * ring buffer is empty before fiddling with it.
4292: */
4293: chunked=session->req.write_chunked;
4294:
4295: bufdata=buf;
4296: if(chunked) {
4297: i=sprintf(buf, "%X\r\n", avail);
4298: bufdata+=i;
4299: len+=i;
4300: }
4301:
4302: pthread_mutex_lock(&session->outbuf_write);
4303: RingBufRead(obuf, bufdata, avail);
4304: if(chunked) {
4305: bufdata+=avail;
4306: *(bufdata++)='\r';
4307: *(bufdata++)='\n';
4308: len+=2;
4309: }
4310:
4311: if(!failed)
4312: sock_sendbuf(&session->socket, buf, len, &failed);
4313: pthread_mutex_unlock(&session->outbuf_write);
4314: }
4315: thread_down();
4316: /* Ensure outbuf isn't currently being drained */
4317: pthread_mutex_lock(&session->outbuf_write);
4318: session->outbuf_write_initialized=0;
4319: pthread_mutex_unlock(&session->outbuf_write);
4320: pthread_mutex_destroy(&session->outbuf_write);
4321: sem_post(&session->output_thread_terminated);
4322: }
4323:
4324: void http_session_thread(void* arg)
4325: {
4326: int i;
4327: char* host_name;
4328: HOSTENT* host;
4329: SOCKET socket;
4330: char redir_req[MAX_REQUEST_LINE+1];
4331: char *redirp;
4332: http_session_t session;
4333: int loop_count;
4334: BOOL init_error;
4335:
4336: pthread_mutex_lock(&((http_session_t*)arg)->struct_filled);
4337: pthread_mutex_unlock(&((http_session_t*)arg)->struct_filled);
4338: pthread_mutex_destroy(&((http_session_t*)arg)->struct_filled);
4339:
4340: session=*(http_session_t*)arg; /* copies arg BEFORE it's freed */
4341: FREE_AND_NULL(arg);
4342:
4343: socket=session.socket;
4344: if(socket==INVALID_SOCKET) {
4345: session_threads--;
4346: return;
4347: }
4348: lprintf(LOG_DEBUG,"%04d Session thread started", session.socket);
4349:
4350: if(startup->index_file_name==NULL || startup->cgi_ext==NULL)
4351: lprintf(LOG_DEBUG,"%04d !!! DANGER WILL ROBINSON, DANGER !!!", session.socket);
4352:
4353: #ifdef _WIN32
4354: if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE))
4355: PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
4356: #endif
4357:
4358: thread_up(TRUE /* setuid */);
4359: session.finished=FALSE;
4360:
4361: /* Start up the output buffer */
4362: /* FREE()d in this block (RingBufDispose before all returns) */
4363: if(RingBufInit(&(session.outbuf), OUTBUF_LEN)) {
4364: lprintf(LOG_ERR,"%04d Canot create output ringbuffer!", session.socket);
4365: close_socket(&session.socket);
4366: thread_down();
4367: session_threads--;
4368: return;
4369: }
4370:
4371: /* Destroyed in this block (before all returns) */
4372: sem_init(&session.output_thread_terminated,0,0);
4373: _beginthread(http_output_thread, 0, &session);
4374:
4375: sbbs_srand(); /* Seed random number generator */
4376:
4377: if(startup->options&BBS_OPT_NO_HOST_LOOKUP)
4378: host=NULL;
4379: else
4380: host=gethostbyaddr ((char *)&session.addr.sin_addr
4381: ,sizeof(session.addr.sin_addr),AF_INET);
4382:
4383: if(host!=NULL && host->h_name!=NULL)
4384: host_name=host->h_name;
4385: else
4386: host_name=session.host_ip;
4387:
4388: SAFECOPY(session.host_name,host_name);
4389:
4390: if(!(startup->options&BBS_OPT_NO_HOST_LOOKUP)) {
4391: lprintf(LOG_INFO,"%04d Hostname: %s", session.socket, host_name);
4392: for(i=0;host!=NULL && host->h_aliases!=NULL
4393: && host->h_aliases[i]!=NULL;i++)
4394: lprintf(LOG_INFO,"%04d HostAlias: %s", session.socket, host->h_aliases[i]);
4395: if(trashcan(&scfg,host_name,"host")) {
4396: lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in host.can: %s", session.socket, host_name);
4397: close_socket(&session.socket);
4398: sem_wait(&session.output_thread_terminated);
4399: sem_destroy(&session.output_thread_terminated);
4400: RingBufDispose(&session.outbuf);
4401: thread_down();
4402: session_threads--;
4403: return;
4404: }
4405: }
4406:
4407: /* host_ip wasn't defined in http_session_thread */
4408: if(trashcan(&scfg,session.host_ip,"ip")) {
4409: lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip);
4410: close_socket(&session.socket);
4411: sem_wait(&session.output_thread_terminated);
4412: sem_destroy(&session.output_thread_terminated);
4413: RingBufDispose(&session.outbuf);
4414: thread_down();
4415: session_threads--;
4416: return;
4417: }
4418:
4419: active_clients++;
4420: update_clients();
4421: SAFECOPY(session.username,unknown);
4422:
4423: SAFECOPY(session.client.addr,session.host_ip);
4424: SAFECOPY(session.client.host,session.host_name);
4425: session.client.port=ntohs(session.addr.sin_port);
4426: session.client.time=time(NULL);
4427: session.client.protocol="HTTP";
4428: session.client.user=session.username;
4429: session.client.size=sizeof(session.client);
4430: client_on(session.socket, &session.client, /* update existing client record? */FALSE);
4431:
4432: session.last_user_num=-1;
4433: session.last_js_user_num=-1;
4434: session.logon_time=0;
4435:
4436: session.subscan=(subscan_t*)alloca(sizeof(subscan_t)*scfg.total_subs);
4437:
4438: while(!session.finished) {
4439: init_error=FALSE;
4440: memset(&(session.req), 0, sizeof(session.req));
4441: redirp=NULL;
4442: loop_count=0;
4443: if(session.req.ld) {
4444: FREE_AND_NULL(session.req.ld->hostname);
4445: FREE_AND_NULL(session.req.ld->ident);
4446: FREE_AND_NULL(session.req.ld->user);
4447: FREE_AND_NULL(session.req.ld->request);
4448: FREE_AND_NULL(session.req.ld->referrer);
4449: FREE_AND_NULL(session.req.ld->agent);
4450: FREE_AND_NULL(session.req.ld->vhost);
4451: FREE_AND_NULL(session.req.ld);
4452: }
4453: if(startup->options&WEB_OPT_HTTP_LOGGING) {
4454: /* FREE()d in http_logging_thread... passed there by close_request() */
4455: if((session.req.ld=(struct log_data*)malloc(sizeof(struct log_data)))==NULL)
4456: lprintf(LOG_ERR,"%04d Cannot allocate memory for log data!",session.socket);
4457: }
4458: if(session.req.ld!=NULL) {
4459: memset(session.req.ld,0,sizeof(struct log_data));
4460: /* FREE()d in http_logging_thread */
4461: session.req.ld->hostname=strdup(session.host_name);
4462: }
4463: while((redirp==NULL || session.req.send_location >= MOVED_TEMP)
4464: && !session.finished && !session.req.finished
4465: && session.socket!=INVALID_SOCKET) {
4466: SAFECOPY(session.req.status,"200 OK");
4467: session.req.send_location=NO_LOCATION;
4468: if(session.req.headers==NULL) {
4469: /* FREE()d in close_request() */
4470: if((session.req.headers=strListInit())==NULL) {
4471: lprintf(LOG_ERR,"%04d !ERROR allocating memory for header list",session.socket);
4472: init_error=TRUE;
4473: }
4474: }
4475: if(session.req.cgi_env==NULL) {
4476: /* FREE()d in close_request() */
4477: if((session.req.cgi_env=strListInit())==NULL) {
4478: lprintf(LOG_ERR,"%04d !ERROR allocating memory for CGI environment list",session.socket);
4479: init_error=TRUE;
4480: }
4481: }
4482: if(session.req.dynamic_heads==NULL) {
4483: /* FREE()d in close_request() */
4484: if((session.req.dynamic_heads=strListInit())==NULL) {
4485: lprintf(LOG_ERR,"%04d !ERROR allocating memory for dynamic header list",session.socket);
4486: init_error=TRUE;
4487: }
4488: }
4489:
4490: if(get_req(&session,redirp)) {
4491: if(init_error) {
4492: send_error(&session, error_500);
4493: }
4494: /* At this point, if redirp is non-NULL then the headers have already been parsed */
4495: if((session.http_ver<HTTP_1_0)||redirp!=NULL||parse_headers(&session)) {
4496: if(check_request(&session)) {
4497: if(session.req.send_location < MOVED_TEMP || session.req.virtual_path[0]!='/' || loop_count++ >= MAX_REDIR_LOOPS) {
4498: if(read_post_data(&session))
4499: respond(&session);
4500: }
4501: else {
4502: safe_snprintf(redir_req,sizeof(redir_req),"%s %s%s%s",methods[session.req.method]
4503: ,session.req.virtual_path,session.http_ver<HTTP_1_0?"":" ",http_vers[session.http_ver]);
4504: lprintf(LOG_DEBUG,"%04d Internal Redirect to: %s",socket,redir_req);
4505: redirp=redir_req;
4506: }
4507: }
4508: }
4509: }
4510: else {
4511: session.req.keep_alive=FALSE;
4512: break;
4513: }
4514: }
4515: close_request(&session);
4516: }
4517:
4518: http_logoff(&session,socket,__LINE__);
4519:
4520: if(session.js_cx!=NULL) {
4521: lprintf(LOG_INFO,"%04d JavaScript: Destroying context",socket);
4522: JS_DestroyContext(session.js_cx); /* Free Context */
4523: session.js_cx=NULL;
4524: }
4525:
4526: #ifndef ONE_JS_RUNTIME
4527: if(session.js_runtime!=NULL) {
4528: lprintf(LOG_INFO,"%04d JavaScript: Destroying runtime",socket);
4529: JS_DestroyRuntime(session.js_runtime);
4530: session.js_runtime=NULL;
4531: }
4532: #endif
4533:
4534: #ifdef _WIN32
4535: if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE))
4536: PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME);
4537: #endif
4538:
4539: close_socket(&session.socket);
4540: sem_wait(&session.output_thread_terminated);
4541: sem_destroy(&session.output_thread_terminated);
4542: RingBufDispose(&session.outbuf);
4543:
4544: active_clients--;
4545: update_clients();
4546: client_off(socket);
4547:
4548: session_threads--;
4549: thread_down();
4550:
4551: if(startup->index_file_name==NULL || startup->cgi_ext==NULL)
4552: lprintf(LOG_DEBUG,"%04d !!! ALL YOUR BASE ARE BELONG TO US !!!", socket);
4553:
4554: lprintf(LOG_INFO,"%04d Session thread terminated (%u clients, %u threads remain, %lu served)"
4555: ,socket, active_clients, thread_count, served);
4556:
4557: }
4558:
4559: void DLLCALL web_terminate(void)
4560: {
4561: lprintf(LOG_INFO,"%04d Web Server terminate",server_socket);
4562: terminate_server=TRUE;
4563: }
4564:
4565: static void cleanup(int code)
4566: {
4567: while(session_threads) {
4568: lprintf(LOG_INFO,"#### Web Server waiting on %d active session threads",session_threads);
4569: SLEEP(1000);
4570: }
4571: free_cfg(&scfg);
4572:
4573: listFree(&log_list);
4574:
4575: mime_types=iniFreeNamedStringList(mime_types);
4576:
4577: cgi_handlers=iniFreeNamedStringList(cgi_handlers);
4578: xjs_handlers=iniFreeNamedStringList(xjs_handlers);
4579:
4580: semfile_list_free(&recycle_semfiles);
4581: semfile_list_free(&shutdown_semfiles);
4582:
4583: if(server_socket!=INVALID_SOCKET) {
4584: close_socket(&server_socket);
4585: }
4586:
4587: update_clients();
4588:
4589: #ifdef _WINSOCKAPI_
4590: if(WSAInitialized && WSACleanup()!=0)
4591: lprintf(LOG_ERR,"0000 !WSACleanup ERROR %d",ERROR_VALUE);
4592: #endif
4593:
4594: thread_down();
4595: status("Down");
4596: if(terminate_server || code)
4597: lprintf(LOG_INFO,"#### Web Server thread terminated (%u threads remain, %lu clients served)"
4598: ,thread_count, served);
4599: if(startup!=NULL && startup->terminated!=NULL)
4600: startup->terminated(startup->cbdata,code);
4601: }
4602:
4603: const char* DLLCALL web_ver(void)
4604: {
4605: static char ver[256];
4606: char compiler[32];
4607:
4608: DESCRIBE_COMPILER(compiler);
4609:
4610: sscanf("$Revision: 1.458 $", "%*s %s", revision);
4611:
4612: sprintf(ver,"%s %s%s "
4613: "Compiled %s %s with %s"
4614: ,server_name
4615: ,revision
4616: #ifdef _DEBUG
4617: ," Debug"
4618: #else
4619: ,""
4620: #endif
4621: ,__DATE__, __TIME__, compiler);
4622:
4623: return(ver);
4624: }
4625:
4626: void http_logging_thread(void* arg)
4627: {
4628: char base[MAX_PATH+1];
4629: char filename[MAX_PATH+1];
4630: char newfilename[MAX_PATH+1];
4631: FILE* logfile=NULL;
4632:
4633: http_logging_thread_running=TRUE;
4634: terminate_http_logging_thread=FALSE;
4635:
4636: SAFECOPY(base,arg);
4637: if(!base[0])
4638: SAFEPRINTF(base,"%slogs/http-",scfg.logs_dir);
4639:
4640: filename[0]=0;
4641: newfilename[0]=0;
4642:
4643: thread_up(TRUE /* setuid */);
4644:
4645: lprintf(LOG_DEBUG,"%04d http logging thread started", server_socket);
4646:
4647: for(;;) {
4648: struct log_data *ld;
4649: char timestr[128];
4650: char sizestr[100];
4651:
4652: if(!listSemTryWait(&log_list)) {
4653: if(logfile!=NULL)
4654: fflush(logfile);
4655: listSemWait(&log_list);
4656: }
4657:
4658: ld=listShiftNode(&log_list);
4659: /*
4660: * Because the sem is posted when terminate_http_logging_thread is set, this will
4661: * ensure that all pending log entries are written to disk
4662: */
4663: if(ld==NULL) {
4664: if(terminate_http_logging_thread)
4665: break;
4666: lprintf(LOG_ERR,"%04d http logging thread received NULL linked list log entry"
4667: ,server_socket);
4668: continue;
4669: }
4670: SAFECOPY(newfilename,base);
4671: if(startup->options&WEB_OPT_VIRTUAL_HOSTS && ld->vhost!=NULL) {
4672: strcat(newfilename,ld->vhost);
4673: if(ld->vhost[0])
4674: strcat(newfilename,"-");
4675: }
4676: strftime(strchr(newfilename,0),15,"%Y-%m-%d.log",&ld->completed);
4677: if(strcmp(newfilename,filename)) {
4678: if(logfile!=NULL)
4679: fclose(logfile);
4680: SAFECOPY(filename,newfilename);
4681: logfile=fopen(filename,"ab");
4682: lprintf(LOG_INFO,"%04d http logfile is now: %s",server_socket,filename);
4683: }
4684: if(logfile!=NULL) {
4685: if(ld->status) {
4686: sprintf(sizestr,"%d",ld->size);
4687: strftime(timestr,sizeof(timestr),"%d/%b/%Y:%H:%M:%S %z",&ld->completed);
4688: /*
4689: * In case of a termination, do no block for a lock... just discard
4690: * the output.
4691: */
4692: while(lock(fileno(logfile),0,1) && !terminate_http_logging_thread) {
4693: SLEEP(10);
4694: }
4695: fprintf(logfile,"%s %s %s [%s] \"%s\" %d %s \"%s\" \"%s\"\n"
4696: ,ld->hostname?(ld->hostname[0]?ld->hostname:"-"):"-"
4697: ,ld->ident?(ld->ident[0]?ld->ident:"-"):"-"
4698: ,ld->user?(ld->user[0]?ld->user:"-"):"-"
4699: ,timestr
4700: ,ld->request?(ld->request[0]?ld->request:"-"):"-"
4701: ,ld->status
4702: ,ld->size?sizestr:"-"
4703: ,ld->referrer?(ld->referrer[0]?ld->referrer:"-"):"-"
4704: ,ld->agent?(ld->agent[0]?ld->agent:"-"):"-");
4705: unlock(fileno(logfile),0,1);
4706: }
4707: }
4708: else {
4709: logfile=fopen(filename,"ab");
4710: lprintf(LOG_ERR,"%04d http logfile %s was not open!",server_socket,filename);
4711: }
4712: FREE_AND_NULL(ld->hostname);
4713: FREE_AND_NULL(ld->ident);
4714: FREE_AND_NULL(ld->user);
4715: FREE_AND_NULL(ld->request);
4716: FREE_AND_NULL(ld->referrer);
4717: FREE_AND_NULL(ld->agent);
4718: FREE_AND_NULL(ld->vhost);
4719: FREE_AND_NULL(ld);
4720: }
4721: if(logfile!=NULL) {
4722: fclose(logfile);
4723: logfile=NULL;
4724: }
4725: thread_down();
4726: lprintf(LOG_DEBUG,"%04d http logging thread terminated",server_socket);
4727:
4728: http_logging_thread_running=FALSE;
4729: }
4730:
4731: void DLLCALL web_server(void* arg)
4732: {
4733: int i;
4734: int result;
4735: time_t start;
4736: WORD host_port;
4737: char host_ip[32];
4738: char path[MAX_PATH+1];
4739: char logstr[256];
4740: SOCKADDR_IN server_addr={0};
4741: SOCKADDR_IN client_addr;
4742: socklen_t client_addr_len;
4743: SOCKET client_socket;
4744: SOCKET high_socket_set;
4745: fd_set socket_set;
4746: time_t t;
4747: time_t initialized=0;
4748: char* p;
4749: char compiler[32];
4750: http_session_t * session=NULL;
4751: struct timeval tv;
4752: #ifdef ONE_JS_RUNTIME
4753: JSRuntime* js_runtime;
4754: #endif
4755: #ifdef SO_ACCEPTFILTER
4756: struct accept_filter_arg afa;
4757: #endif
4758:
4759: startup=(web_startup_t*)arg;
4760:
4761: web_ver(); /* get CVS revision */
4762:
4763: if(startup==NULL) {
4764: sbbs_beep(100,500);
4765: fprintf(stderr, "No startup structure passed!\n");
4766: return;
4767: }
4768:
4769: if(startup->size!=sizeof(web_startup_t)) { /* verify size */
4770: sbbs_beep(100,500);
4771: sbbs_beep(300,500);
4772: sbbs_beep(100,500);
4773: fprintf(stderr, "Invalid startup structure!\n");
4774: return;
4775: }
4776:
4777: #ifdef _THREAD_SUID_BROKEN
4778: if(thread_suid_broken)
4779: startup->seteuid(TRUE);
4780: #endif
4781:
4782: /* Setup intelligent defaults */
4783: if(startup->port==0) startup->port=IPPORT_HTTP;
4784: if(startup->root_dir[0]==0) SAFECOPY(startup->root_dir,WEB_DEFAULT_ROOT_DIR);
4785: if(startup->error_dir[0]==0) SAFECOPY(startup->error_dir,WEB_DEFAULT_ERROR_DIR);
4786: if(startup->cgi_dir[0]==0) SAFECOPY(startup->cgi_dir,WEB_DEFAULT_CGI_DIR);
4787: if(startup->default_cgi_content[0]==0) SAFECOPY(startup->default_cgi_content,WEB_DEFAULT_CGI_CONTENT);
4788: if(startup->max_inactivity==0) startup->max_inactivity=120; /* seconds */
4789: if(startup->max_cgi_inactivity==0) startup->max_cgi_inactivity=120; /* seconds */
4790: if(startup->sem_chk_freq==0) startup->sem_chk_freq=2; /* seconds */
4791: if(startup->js.max_bytes==0) startup->js.max_bytes=JAVASCRIPT_MAX_BYTES;
4792: if(startup->js.cx_stack==0) startup->js.cx_stack=JAVASCRIPT_CONTEXT_STACK;
4793: if(startup->ssjs_ext[0]==0) SAFECOPY(startup->ssjs_ext,".ssjs");
4794: if(startup->js_ext[0]==0) SAFECOPY(startup->js_ext,".bbs");
4795:
4796: ZERO_VAR(js_server_props);
4797: SAFEPRINTF2(js_server_props.version,"%s %s",server_name,revision);
4798: js_server_props.version_detail=web_ver();
4799: js_server_props.clients=&active_clients;
4800: js_server_props.options=&startup->options;
4801: js_server_props.interface_addr=&startup->interface_addr;
4802:
4803: uptime=0;
4804: served=0;
4805: startup->recycle_now=FALSE;
4806: startup->shutdown_now=FALSE;
4807: terminate_server=FALSE;
4808:
4809: do {
4810:
4811: thread_up(FALSE /* setuid */);
4812:
4813: status("Initializing");
4814:
4815: /* Copy html directories */
4816: SAFECOPY(root_dir,startup->root_dir);
4817: SAFECOPY(error_dir,startup->error_dir);
4818: SAFECOPY(cgi_dir,startup->cgi_dir);
4819: if(startup->temp_dir[0])
4820: SAFECOPY(temp_dir,startup->temp_dir);
4821: else
4822: SAFECOPY(temp_dir,"../temp");
4823:
4824: /* Change to absolute path */
4825: prep_dir(startup->ctrl_dir, root_dir, sizeof(root_dir));
4826: prep_dir(startup->ctrl_dir, temp_dir, sizeof(temp_dir));
4827: prep_dir(root_dir, error_dir, sizeof(error_dir));
4828: prep_dir(root_dir, cgi_dir, sizeof(cgi_dir));
4829:
4830: /* Trim off trailing slash/backslash */
4831: if(IS_PATH_DELIM(*(p=lastchar(root_dir)))) *p=0;
4832:
4833: memset(&scfg, 0, sizeof(scfg));
4834:
4835: lprintf(LOG_INFO,"%s Revision %s%s"
4836: ,server_name
4837: ,revision
4838: #ifdef _DEBUG
4839: ," Debug"
4840: #else
4841: ,""
4842: #endif
4843: );
4844:
4845: DESCRIBE_COMPILER(compiler);
4846:
4847: lprintf(LOG_INFO,"Compiled %s %s with %s", __DATE__, __TIME__, compiler);
4848:
4849: if(!winsock_startup()) {
4850: cleanup(1);
4851: return;
4852: }
4853:
4854: t=time(NULL);
4855: lprintf(LOG_INFO,"Initializing on %.24s with options: %lx"
4856: ,CTIME_R(&t,logstr),startup->options);
4857:
4858: if(chdir(startup->ctrl_dir)!=0)
4859: lprintf(LOG_ERR,"!ERROR %d changing directory to: %s", errno, startup->ctrl_dir);
4860:
4861: /* Initial configuration and load from CNF files */
4862: SAFECOPY(scfg.ctrl_dir,startup->ctrl_dir);
4863: lprintf(LOG_INFO,"Loading configuration files from %s", scfg.ctrl_dir);
4864: scfg.size=sizeof(scfg);
4865: SAFECOPY(logstr,UNKNOWN_LOAD_ERROR);
4866: if(!load_cfg(&scfg, NULL, TRUE, logstr)) {
4867: lprintf(LOG_ERR,"!ERROR %s",logstr);
4868: lprintf(LOG_ERR,"!FAILED to load configuration files");
4869: cleanup(1);
4870: return;
4871: }
4872: scfg_reloaded=TRUE;
4873:
4874: lprintf(LOG_DEBUG,"Temporary file directory: %s", temp_dir);
4875: MKDIR(temp_dir);
4876: if(!isdir(temp_dir)) {
4877: lprintf(LOG_ERR,"!Invalid temp directory: %s", temp_dir);
4878: cleanup(1);
4879: return;
4880: }
4881: lprintf(LOG_DEBUG,"Root directory: %s", root_dir);
4882: lprintf(LOG_DEBUG,"Error directory: %s", error_dir);
4883: lprintf(LOG_DEBUG,"CGI directory: %s", cgi_dir);
4884:
4885: mime_types=read_ini_list("mime_types.ini",NULL /* root section */,"MIME types"
4886: ,mime_types);
4887: cgi_handlers=read_ini_list("web_handler.ini","CGI","CGI content handlers"
4888: ,cgi_handlers);
4889: xjs_handlers=read_ini_list("web_handler.ini","JavaScript","JavaScript content handlers"
4890: ,xjs_handlers);
4891:
4892: /* Don't do this for *each* CGI request, just once here during [re]init */
4893: iniFileName(cgi_env_ini,sizeof(cgi_env_ini),scfg.ctrl_dir,"cgi_env.ini");
4894:
4895: if(startup->host_name[0]==0)
4896: SAFECOPY(startup->host_name,scfg.sys_inetaddr);
4897:
4898: if(!(scfg.sys_misc&SM_LOCAL_TZ) && !(startup->options&BBS_OPT_LOCAL_TIMEZONE)) {
4899: if(putenv("TZ=UTC0"))
4900: lprintf(LOG_WARNING,"!putenv() FAILED");
4901: tzset();
4902: }
4903:
4904: if(uptime==0)
4905: uptime=time(NULL); /* this must be done *after* setting the timezone */
4906:
4907: active_clients=0;
4908: update_clients();
4909:
4910: /* open a socket and wait for a client */
4911:
4912: server_socket = open_socket(SOCK_STREAM);
4913:
4914: if(server_socket == INVALID_SOCKET) {
4915: lprintf(LOG_ERR,"!ERROR %d creating HTTP socket", ERROR_VALUE);
4916: cleanup(1);
4917: return;
4918: }
4919:
4920: /*
4921: * i=1;
4922: * if(setsockopt(server_socket, IPPROTO_TCP, TCP_NOPUSH, &i, sizeof(i)))
4923: * lprintf("Cannot set TCP_NOPUSH socket option");
4924: */
4925:
4926: #ifdef SO_ACCEPTFILTER
4927: memset(&afa, 0, sizeof(afa));
4928: strcpy(afa.af_name, "httpready");
4929: setsockopt(server_socket, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa));
4930: #endif
4931:
4932: lprintf(LOG_INFO,"%04d Web Server socket opened",server_socket);
4933:
4934: /*****************************/
4935: /* Listen for incoming calls */
4936: /*****************************/
4937: memset(&server_addr, 0, sizeof(server_addr));
4938:
4939: server_addr.sin_addr.s_addr = htonl(startup->interface_addr);
4940: server_addr.sin_family = AF_INET;
4941: server_addr.sin_port = htons(startup->port);
4942:
4943: if(startup->seteuid!=NULL)
4944: startup->seteuid(FALSE);
4945: result = retry_bind(server_socket,(struct sockaddr *)&server_addr,sizeof(server_addr)
4946: ,startup->bind_retry_count,startup->bind_retry_delay,"Web Server",lprintf);
4947: if(startup->seteuid!=NULL)
4948: startup->seteuid(TRUE);
4949: if(result != 0) {
4950: lprintf(LOG_NOTICE,"%s",BIND_FAILURE_HELP);
4951: cleanup(1);
4952: return;
4953: }
4954:
4955: result = listen(server_socket, 64);
4956:
4957: if(result != 0) {
4958: lprintf(LOG_ERR,"%04d !ERROR %d (%d) listening on socket"
4959: ,server_socket, result, ERROR_VALUE);
4960: cleanup(1);
4961: return;
4962: }
4963: lprintf(LOG_INFO,"%04d Web Server listening on port %d"
4964: ,server_socket, startup->port);
4965: status("Listening");
4966:
4967: lprintf(LOG_INFO,"%04d Web Server thread started", server_socket);
4968:
4969: listInit(&log_list,/* flags */ LINK_LIST_MUTEX|LINK_LIST_SEMAPHORE);
4970: if(startup->options&WEB_OPT_HTTP_LOGGING) {
4971: /********************/
4972: /* Start log thread */
4973: /********************/
4974: _beginthread(http_logging_thread, 0, startup->logfile_base);
4975: }
4976:
4977: #ifdef ONE_JS_RUNTIME
4978: if(js_runtime == NULL) {
4979: lprintf(LOG_INFO,"%04d JavaScript: Creating runtime: %lu bytes"
4980: ,server_socket,startup->js.max_bytes);
4981:
4982: if((js_runtime=JS_NewRuntime(startup->js.max_bytes))==NULL) {
4983: lprintf(LOG_ERR,"%04d !ERROR creating JavaScript runtime",server_socket);
4984: /* Sleep 15 seconds then try again */
4985: /* ToDo: Something better should be used here. */
4986: SLEEP(15000);
4987: continue;
4988: }
4989: }
4990: #endif
4991:
4992: /* Setup recycle/shutdown semaphore file lists */
4993: shutdown_semfiles=semfile_list_init(scfg.ctrl_dir,"shutdown","web");
4994: recycle_semfiles=semfile_list_init(scfg.ctrl_dir,"recycle","web");
4995: SAFEPRINTF(path,"%swebsrvr.rec",scfg.ctrl_dir); /* legacy */
4996: semfile_list_add(&recycle_semfiles,path);
4997: if(!initialized) {
4998: initialized=time(NULL);
4999: semfile_list_check(&initialized,recycle_semfiles);
5000: semfile_list_check(&initialized,shutdown_semfiles);
5001: }
5002:
5003: /* signal caller that we've started up successfully */
5004: if(startup->started!=NULL)
5005: startup->started(startup->cbdata);
5006:
5007: while(server_socket!=INVALID_SOCKET && !terminate_server) {
5008:
5009: /* check for re-cycle/shutdown semaphores */
5010: if(active_clients==0) {
5011: if(!(startup->options&BBS_OPT_NO_RECYCLE)) {
5012: if((p=semfile_list_check(&initialized,recycle_semfiles))!=NULL) {
5013: lprintf(LOG_INFO,"%04d Recycle semaphore file (%s) detected"
5014: ,server_socket,p);
5015: if(session!=NULL) {
5016: pthread_mutex_unlock(&session->struct_filled);
5017: session=NULL;
5018: }
5019: break;
5020: }
5021: #if 0 /* unused */
5022: if(startup->recycle_sem!=NULL && sem_trywait(&startup->recycle_sem)==0)
5023: startup->recycle_now=TRUE;
5024: #endif
5025: if(startup->recycle_now==TRUE) {
5026: lprintf(LOG_INFO,"%04d Recycle semaphore signaled",server_socket);
5027: startup->recycle_now=FALSE;
5028: if(session!=NULL) {
5029: pthread_mutex_unlock(&session->struct_filled);
5030: session=NULL;
5031: }
5032: break;
5033: }
5034: }
5035: if(((p=semfile_list_check(&initialized,shutdown_semfiles))!=NULL
5036: && lprintf(LOG_INFO,"%04d Shutdown semaphore file (%s) detected"
5037: ,server_socket,p))
5038: || (startup->shutdown_now==TRUE
5039: && lprintf(LOG_INFO,"%04d Shutdown semaphore signaled"
5040: ,server_socket))) {
5041: startup->shutdown_now=FALSE;
5042: terminate_server=TRUE;
5043: if(session!=NULL) {
5044: pthread_mutex_unlock(&session->struct_filled);
5045: session=NULL;
5046: }
5047: break;
5048: }
5049: }
5050:
5051: /* Startup next session thread */
5052: if(session==NULL) {
5053: /* FREE()d at the start of the session thread */
5054: if((session=malloc(sizeof(http_session_t)))==NULL) {
5055: lprintf(LOG_CRIT,"%04d !ERROR allocating %u bytes of memory for http_session_t"
5056: ,client_socket, sizeof(http_session_t));
5057: mswait(3000);
5058: continue;
5059: }
5060: memset(session, 0, sizeof(http_session_t));
5061: session->socket=INVALID_SOCKET;
5062: /* Destroyed in http_session_thread */
5063: pthread_mutex_init(&session->struct_filled,NULL);
5064: pthread_mutex_lock(&session->struct_filled);
5065: session_threads++;
5066: _beginthread(http_session_thread, 0, session);
5067: }
5068:
5069: /* now wait for connection */
5070:
5071: FD_ZERO(&socket_set);
5072: FD_SET(server_socket,&socket_set);
5073: high_socket_set=server_socket+1;
5074:
5075: tv.tv_sec=startup->sem_chk_freq;
5076: tv.tv_usec=0;
5077:
5078: if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) {
5079: if(i==0)
5080: continue;
5081: if(ERROR_VALUE==EINTR)
5082: lprintf(LOG_DEBUG,"Web Server listening interrupted");
5083: else if(ERROR_VALUE == ENOTSOCK)
5084: lprintf(LOG_INFO,"Web Server socket closed");
5085: else
5086: lprintf(LOG_WARNING,"!ERROR %d selecting socket",ERROR_VALUE);
5087: continue;
5088: }
5089:
5090: if(server_socket==INVALID_SOCKET) { /* terminated */
5091: pthread_mutex_unlock(&session->struct_filled);
5092: session=NULL;
5093: break;
5094: }
5095:
5096: client_addr_len = sizeof(client_addr);
5097:
5098: if(server_socket!=INVALID_SOCKET
5099: && FD_ISSET(server_socket,&socket_set)) {
5100: client_socket = accept(server_socket, (struct sockaddr *)&client_addr
5101: ,&client_addr_len);
5102: }
5103: else {
5104: lprintf(LOG_NOTICE,"!NO SOCKETS set by select");
5105: continue;
5106: }
5107:
5108: if(client_socket == INVALID_SOCKET) {
5109: lprintf(LOG_WARNING,"!ERROR %d accepting connection", ERROR_VALUE);
5110: #ifdef _WIN32
5111: if(WSAGetLastError()==WSAENOBUFS) { /* recycle (re-init WinSock) on this error */
5112: pthread_mutex_unlock(&session->struct_filled);
5113: session=NULL;
5114: break;
5115: }
5116: #endif
5117: continue;
5118: }
5119:
5120: if(startup->socket_open!=NULL)
5121: startup->socket_open(startup->cbdata,TRUE);
5122:
5123: SAFECOPY(host_ip,inet_ntoa(client_addr.sin_addr));
5124:
5125: if(trashcan(&scfg,host_ip,"ip-silent")) {
5126: close_socket(&client_socket);
5127: continue;
5128: }
5129:
5130: if(startup->max_clients && active_clients>=startup->max_clients) {
5131: lprintf(LOG_WARNING,"%04d !MAXIMUM CLIENTS (%d) reached, access denied"
5132: ,client_socket, startup->max_clients);
5133: mswait(3000);
5134: close_socket(&client_socket);
5135: continue;
5136: }
5137:
5138: host_port=ntohs(client_addr.sin_port);
5139:
5140: lprintf(LOG_INFO,"%04d HTTP connection accepted from: %s port %u"
5141: ,client_socket
5142: ,host_ip, host_port);
5143:
5144: SAFECOPY(session->host_ip,host_ip);
5145: session->addr=client_addr;
5146: session->socket=client_socket;
5147: session->js_branch.auto_terminate=TRUE;
5148: session->js_branch.terminated=&terminate_server;
5149: session->js_branch.limit=startup->js.branch_limit;
5150: session->js_branch.gc_interval=startup->js.gc_interval;
5151: session->js_branch.yield_interval=startup->js.yield_interval;
5152: #ifdef ONE_JS_RUNTIME
5153: session->js_runtime=js_runtime;
5154: #endif
5155:
5156: pthread_mutex_unlock(&session->struct_filled);
5157: session=NULL;
5158: served++;
5159: }
5160:
5161: if(session) {
5162: pthread_mutex_unlock(&session->struct_filled);
5163: session=NULL;
5164: }
5165:
5166: /* Wait for active clients to terminate */
5167: if(active_clients) {
5168: lprintf(LOG_DEBUG,"%04d Waiting for %d active clients to disconnect..."
5169: ,server_socket, active_clients);
5170: start=time(NULL);
5171: while(active_clients) {
5172: if(time(NULL)-start>startup->max_inactivity) {
5173: lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for %d active clients"
5174: ,server_socket, active_clients);
5175: break;
5176: }
5177: mswait(100);
5178: }
5179: }
5180:
5181: if(http_logging_thread_running) {
5182: terminate_http_logging_thread=TRUE;
5183: listSemPost(&log_list);
5184: mswait(100);
5185: }
5186: if(http_logging_thread_running) {
5187: lprintf(LOG_DEBUG,"%04d Waiting for HTTP logging thread to terminate..."
5188: ,server_socket);
5189: start=time(NULL);
5190: while(http_logging_thread_running) {
5191: if(time(NULL)-start>TIMEOUT_THREAD_WAIT) {
5192: lprintf(LOG_WARNING,"%04d !TIMEOUT waiting for HTTP logging thread to "
5193: "terminate", server_socket);
5194: break;
5195: }
5196: mswait(100);
5197: }
5198: }
5199:
5200: #ifdef ONE_JS_RUNTIME
5201: if(js_runtime!=NULL) {
5202: lprintf(LOG_INFO,"%04d JavaScript: Destroying runtime",server_socket);
5203: JS_DestroyRuntime(js_runtime);
5204: js_runtime=NULL;
5205: }
5206: #endif
5207:
5208: cleanup(0);
5209:
5210: if(!terminate_server) {
5211: lprintf(LOG_INFO,"Recycling server...");
5212: mswait(2000);
5213: if(startup->recycle!=NULL)
5214: startup->recycle(startup->cbdata);
5215: }
5216:
5217: } while(!terminate_server);
5218: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.