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