websrvr.c 139 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/* websrvr.c */

/* Synchronet Web Server */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright 2006 Rob Swindell - http://www.synchro.net/copyright.html		*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

38
39
40
41
42
/*
 * General notes: (ToDo stuff)
 *
 * Should support RFC2617 Digest auth.
 *
43
 * Support the ident protocol... the standard log format supports it.
44
 *
deuce's avatar
deuce committed
45
46
47
 * Add in support to pass connections through to a different webserver...
 *      probobly in access.ars... with like a simplified mod_rewrite.
 *      This would allow people to run apache and Synchronet as the same site.
48
49
 */

deuce's avatar
deuce committed
50
/* Headers for CGI stuff */
51
52
#if defined(__unix__)
	#include <sys/wait.h>		/* waitpid() */
rswindell's avatar
rswindell committed
53
54
	#include <sys/types.h>
	#include <signal.h>			/* kill() */
55
56
#endif

57
#ifndef JAVASCRIPT
58
#define JAVASCRIPT
59
60
#endif

61
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
62
63
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
64
#include "threadwrap.h"
65
#include "semwrap.h"
66
#include "websrvr.h"
deuce's avatar
deuce committed
67
#include "base64.h"
68

69
70
static const char*	server_name="Synchronet Web Server";
static const char*	newline="\r\n";
71
72
static const char*	http_scheme="http://";
static const size_t	http_scheme_len=7;
73
74
static const char*	error_301="301 Moved Permanently";
static const char*	error_302="302 Moved Temporarily";
75
static const char*	error_404="404 Not Found";
76
static const char*	error_416="416 Requested Range Not Satisfiable";
77
static const char*	error_500="500 Internal Server Error";
78
static const char*	unknown="<unknown>";
79

rswindell's avatar
rswindell committed
80
#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
deuce's avatar
deuce committed
81
82
83
#define MAX_REQUEST_LINE		1024	/* NOT including terminator */
#define MAX_HEADERS_SIZE		16384	/* Maximum total size of all headers 
										   (Including terminator )*/
84
#define MAX_REDIR_LOOPS			20		/* Max. times to follow internal redirects for a single request */
85
#define MAX_POST_LEN			1048576	/* Max size of body for POSTS */
86
#define	OUTBUF_LEN				20480	/* Size of output thread ring buffer */
87

88
89
90
enum {
	 CLEANUP_SSJS_TMP_FILE
	,CLEANUP_POST_DATA
91
	,MAX_CLEANUPS
92
};
93

94
95
static scfg_t	scfg;
static BOOL		scfg_reloaded=TRUE;
96
static BOOL		http_logging_thread_running=FALSE;
97
98
static ulong	active_clients=0;
static ulong	sockets=0;
99
static BOOL		terminate_server=FALSE;
100
static BOOL		terminate_http_logging_thread=FALSE;
101
102
103
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
static char		revision[16];
104
105
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
106
static char		temp_dir[MAX_PATH+1];
107
static char		cgi_dir[MAX_PATH+1];
108
static char		cgi_env_ini[MAX_PATH+1];
109
static time_t	uptime=0;
110
static DWORD	served=0;
111
static web_startup_t* startup=NULL;
112
static js_server_props_t js_server_props;
113
114
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
115
static int session_threads=0;
116

117
static named_string_t** mime_types;
118
119
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
120

121
/* Logging stuff */
122
link_list_t	log_list;
123
124
125
126
127
128
129
struct log_data {
	char	*hostname;
	char	*ident;
	char	*user;
	char	*request;
	char	*referrer;
	char	*agent;
130
	char	*vhost;
131
132
133
134
135
	int		status;
	unsigned int	size;
	struct tm completed;
};

136
typedef struct  {
137
	int			method;
138
139
140
141
142
143
144
	char		virtual_path[MAX_PATH+1];
	char		physical_path[MAX_PATH+1];
	BOOL    	expect_go_ahead;
	time_t		if_modified_since;
	BOOL		keep_alive;
	char		ars[256];
	char    	auth[128];				/* UserID:Password */
145
146
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
147
	int			send_location;
148
	const char*	mime_type;
149
	str_list_t	headers;
150
	char		status[MAX_REQUEST_LINE+1];
151
152
	char *		post_data;
	size_t		post_len;
153
	int			dynamic;
154
	char		xjs_handler[MAX_PATH+1];
155
	struct log_data	*ld;
156
	char		request_line[MAX_REQUEST_LINE+1];
157
	BOOL		finished;				/* Done processing request. */
158
159
	BOOL		read_chunked;
	BOOL		write_chunked;
160
161
	long		range_start;
	long		range_end;
162
	BOOL		accept_ranges;
deuce's avatar
deuce committed
163
	time_t		if_range;
164

165
166
167
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
168
169
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
170

171
172
	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;
173
	char		*cleanup_file[MAX_CLEANUPS];
174
175
	BOOL	sent_headers;
	BOOL	prev_write;
176
177
178
179
180

	/* webconfig.ini overrides */
	char	*error_dir;
	char	*cgi_dir;
	char	*realm;
181
182
183
} http_request_t;

typedef struct  {
184
185
	SOCKET			socket;
	SOCKADDR_IN		addr;
186
	http_request_t	req;
187
188
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
189
190
	int				http_ver;       /* HTTP version.  0 = HTTP/0.9, 1=HTTP/1.0, 2=HTTP/1.1 */
	BOOL			finished;		/* Do not accept any more imput from client */
191
192
193
	user_t			user;
	int				last_user_num;
	time_t			logon_time;
194
	char			username[LEN_NAME+1];
195
	int				last_js_user_num;
196
197
198
199
200

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
201
202
	JSObject*		js_query;
	JSObject*		js_header;
203
	JSObject*		js_cookie;
204
	JSObject*		js_request;
205
	js_branch_t		js_branch;
deuce's avatar
deuce committed
206
	subscan_t		*subscan;
207

208
209
210
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
211
212
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
213

214
215
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
216
217
218

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
219
220
221
222
223
} http_session_t;

enum { 
	 HTTP_0_9
	,HTTP_1_0
224
	,HTTP_1_1
225
226
227
228
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
229
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
230
	,NULL	/* terminator */
231
232
233
234
235
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
236
237
	,HTTP_POST
	,HTTP_OPTIONS
238
};
239

rswindell's avatar
rswindell committed
240
241
242
static char* methods[] = {
	 "HEAD"
	,"GET"
243
	,"POST"
244
	,"OPTIONS"
rswindell's avatar
rswindell committed
245
246
	,NULL	/* terminator */
};
247

248
enum {
249
250
251
252
253
254
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

255
enum { 
256
257
258
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
259
260
	,HEAD_LENGTH
	,HEAD_TYPE
261
262
263
264
265
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
266
267
268
269
270
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
271
272
	,HEAD_REFERER
	,HEAD_AGENT
273
	,HEAD_TRANSFER_ENCODING
274
275
276
	,HEAD_ACCEPT_RANGES
	,HEAD_CONTENT_RANGE
	,HEAD_RANGE
deuce's avatar
deuce committed
277
	,HEAD_IFRANGE
278
	,HEAD_COOKIE
279
280
281
282
283
284
};

static struct {
	int		id;
	char*	text;
} headers[] = {
285
286
287
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
288
289
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
290
291
292
293
294
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
295
296
297
298
299
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
300
301
	{ HEAD_REFERER,			"Referer"				},
	{ HEAD_AGENT,			"User-Agent"			},
302
	{ HEAD_TRANSFER_ENCODING,			"Transfer-Encoding"			},
303
304
305
	{ HEAD_ACCEPT_RANGES,	"Accept-Ranges"			},
	{ HEAD_CONTENT_RANGE,	"Content-Range"			},
	{ HEAD_RANGE,			"Range"					},
deuce's avatar
deuce committed
306
	{ HEAD_IFRANGE,			"If-Range"				},
307
	{ HEAD_COOKIE,			"Cookie"				},
308
	{ -1,					NULL /* terminator */	},
309
310
};

311
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
312
enum  {
313
	 NO_LOCATION
314
	,MOVED_PERM
315
	,MOVED_TEMP
316
	,MOVED_STAT
317
318
};

319
320
321
static char	*days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static char	*months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

322
static void respond(http_session_t * session);
323
static BOOL js_setup(http_session_t* session);
324
static char *find_last_slash(char *str);
325
static BOOL check_extra_path(http_session_t * session);
326
static BOOL exec_ssjs(http_session_t* session, char* script);
327
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
328

329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
static time_t
sub_mkgmt(struct tm *tm)
{
        int y, nleapdays;
        time_t t;
        /* days before the month */
        static const unsigned short moff[12] = {
                0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
        };

        /*
         * XXX: This code assumes the given time to be normalized.
         * Normalizing here is impossible in case the given time is a leap
         * second but the local time library is ignorant of leap seconds.
         */

        /* minimal sanity checking not to access outside of the array */
        if ((unsigned) tm->tm_mon >= 12)
                return (time_t) -1;
        if (tm->tm_year < 1970 - 1900)
                return (time_t) -1;

        y = tm->tm_year + 1900 - (tm->tm_mon < 2);
        nleapdays = y / 4 - y / 100 + y / 400 -
            ((1970-1) / 4 - (1970-1) / 100 + (1970-1) / 400);
        t = ((((time_t) (tm->tm_year - (1970 - 1900)) * 365 +
                        moff[tm->tm_mon] + tm->tm_mday - 1 + nleapdays) * 24 +
                tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;

        return (t < 0 ? (time_t) -1 : t);
}

time_t
time_gm(struct tm *tm)
{
        time_t t, t2;
        struct tm *tm2;
        int sec;

        /* Do the first guess. */
        if ((t = sub_mkgmt(tm)) == (time_t) -1)
                return (time_t) -1;

        /* save value in case *tm is overwritten by gmtime() */
        sec = tm->tm_sec;

        tm2 = gmtime(&t);
        if ((t2 = sub_mkgmt(tm2)) == (time_t) -1)
                return (time_t) -1;

        if (t2 < t || tm2->tm_sec != sec) {
                /*
                 * Adjust for leap seconds.
                 *
                 *     real time_t time
                 *           |
                 *          tm
                 *         /        ... (a) first sub_mkgmt() conversion
                 *       t
                 *       |
                 *      tm2
                 *     /        ... (b) second sub_mkgmt() conversion
                 *   t2
                 *                        --->time
                 */
                /*
                 * Do the second guess, assuming (a) and (b) are almost equal.
                 */
                t += t - t2;
                tm2 = gmtime(&t);

                /*
                 * Either (a) or (b), may include one or two extra
                 * leap seconds.  Try t, t + 2, t - 2, t + 1, and t - 1.
                 */
                if (tm2->tm_sec == sec
                    || (t += 2, tm2 = gmtime(&t), tm2->tm_sec == sec)
                    || (t -= 4, tm2 = gmtime(&t), tm2->tm_sec == sec)
                    || (t += 3, tm2 = gmtime(&t), tm2->tm_sec == sec)
                    || (t -= 2, tm2 = gmtime(&t), tm2->tm_sec == sec))
                        ;        /* found */
                else {
                        /*
                         * Not found.
                         */
                        if (sec >= 60)
                                /*
                                 * The given time is a leap second
                                 * (sec 60 or 61), but the time library
                                 * is ignorant of the leap second.
                                 */
                                ;        /* treat sec 60 as 59,
                                           sec 61 as 0 of the next minute */
                        else
                                /* The given time may not be normalized. */
                                t++;        /* restore t */
                }
        }

        return (t < 0 ? (time_t) -1 : t);
}
430

431
static int lprintf(int level, char *fmt, ...)
432
433
434
435
436
437
438
439
440
441
442
{
	va_list argptr;
	char sbuf[1024];

    if(startup==NULL || startup->lputs==NULL)
        return(0);

	va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
443
    return(startup->lputs(startup->cbdata,level,sbuf));
444
445
}

446
447
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
448
449
	size_t	sent=0;
	size_t	avail;
450

451
	while(sent < len) {
452
		avail=RingBufFree(&session->outbuf);
deuce's avatar
deuce committed
453
		if(!avail) {
454
			SLEEP(1);
deuce's avatar
deuce committed
455
456
			continue;
		}
457
458
		if(avail > len-sent)
			avail=len-sent;
459
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
460
461
462
463
	}
	return(sent);
}

464
static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
465
466
467
{
	size_t sent=0;
	int result;
468
	int sel;
469
470
	fd_set	wr_set;
	struct timeval tv;
471

472
	while(sent<len && *sock!=INVALID_SOCKET) {
473
474
475
476
477
		FD_ZERO(&wr_set);
		FD_SET(*sock,&wr_set);
		/* Convert timeout from ms to sec/usec */
		tv.tv_sec=startup->max_inactivity;
		tv.tv_usec=0;
478
479
		sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
		switch(sel) {
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
			case 1:
				result=sendsocket(*sock,buf+sent,len-sent);
				if(result==SOCKET_ERROR) {
					if(ERROR_VALUE==ECONNRESET) 
						lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",*sock);
					else if(ERROR_VALUE==ECONNABORTED) 
						lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",*sock);
					else
						lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE);
					if(failed)
						*failed=TRUE;
					return(sent);
				}
				break;
			case 0:
495
				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
496
497
498
499
				if(failed)
					*failed=TRUE;
				return(sent);
			case -1:
500
				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
501
502
503
				if(failed)
					*failed=TRUE;
				return(sent);
504
505
506
507
508
509
510
511
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

512
513
514
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
515
#define SOCKLIB_DESC WSAData.szDescription
516
517
518
519
520
521
522
static BOOL WSAInitialized=FALSE;

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
523
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
524
525
526
527
		WSAInitialized=TRUE;
		return (TRUE);
	}

528
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
529
530
531
532
533
534
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
535
#define SOCKLIB_DESC NULL
536
537
538
539
540
541

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
542
	    startup->status(startup->cbdata,str);
543
544
545
546
547
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
548
		startup->clients(startup->cbdata,active_clients);
549
550
551
552
553
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
554
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
555
556
557
558
559
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
560
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
561
562
563
564
565
566
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
567
		startup->thread_up(startup->cbdata,TRUE, setuid);
568
569
570
571
572
573
574
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
575
		startup->thread_up(startup->cbdata,FALSE, FALSE);
576
577
}

deuce's avatar
deuce committed
578
579
580
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
581
static void add_env(http_session_t *session, const char *name,const char *value)  {
582
	char	newname[129];
583
	char	*p;
584

585
	if(name==NULL || value==NULL)  {
586
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
587
588
589
590
591
592
593
594
595
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
596
	p=(char *)alloca(strlen(name)+strlen(value)+2);
597
598
599
600
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
601
#if 0	/* this is way too verbose for every request */
602
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
603
#endif
604
	sprintf(p,"%s=%s",newname,value);
605
	strListPush(&session->req.cgi_env,p);
606
607
}

deuce's avatar
deuce committed
608
609
610
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
611
612
613
614
615
616
617
static void init_enviro(http_session_t *session)  {
	char	str[128];

	add_env(session,"SERVER_SOFTWARE",VERSION_NOTICE);
	sprintf(str,"%d",startup->port);
	add_env(session,"SERVER_PORT",str);
	add_env(session,"GATEWAY_INTERFACE","CGI/1.1");
618
	if(!strcmp(session->host_name,session->host_ip))
619
620
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
621
	add_env(session,"REQUEST_URI",session->req.request_line);
622
623
}

624
/*
deuce's avatar
deuce committed
625
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
626
627
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
628
static int bufprint(http_session_t *session, const char *str)
629
{
630
631
632
	int len;

	len=strlen(str);
633
	return(writebuf(session,str,len));
634
635
}

deuce's avatar
deuce committed
636
637
638
639
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
640
641
642
643
644
645
646
647
648
649
static int getmonth(char *mon)
{
	int	i;
	for(i=0;i<12;i++)
		if(!stricmp(mon,months[i]))
			return(i);

	return 0;
}

deuce's avatar
deuce committed
650
651
652
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
653
654
655
static time_t decode_date(char *date)
{
	struct	tm	ti;
656
	char	*token;
657
	char	*last;
658
	time_t	t;
659
660
661
662
663
664
665
666
667

	ti.tm_sec=0;		/* seconds (0 - 60) */
	ti.tm_min=0;		/* minutes (0 - 59) */
	ti.tm_hour=0;		/* hours (0 - 23) */
	ti.tm_mday=1;		/* day of month (1 - 31) */
	ti.tm_mon=0;		/* month of year (0 - 11) */
	ti.tm_year=0;		/* year - 1900 */
	ti.tm_isdst=0;		/* is summer time in effect? */

668
	token=strtok_r(date,",",&last);
669
670
	if(token==NULL)
		return(0);
671
672
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
673
		/* asctime() */
674
		/* Toss away week day */
675
		token=strtok_r(date," ",&last);
676
677
		if(token==NULL)
			return(0);
678
		token=strtok_r(NULL," ",&last);
679
680
681
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
682
		token=strtok_r(NULL," ",&last);
683
684
685
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
686
		token=strtok_r(NULL,":",&last);
687
688
689
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
690
		token=strtok_r(NULL,":",&last);
691
692
693
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
694
		token=strtok_r(NULL," ",&last);
695
696
697
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
698
		token=strtok_r(NULL,"",&last);
699
700
701
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token)-1900;
702
703
704
	}
	else  {
		/* RFC 1123 or RFC 850 */
705
		token=strtok_r(NULL," -",&last);
706
707
708
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
709
		token=strtok_r(NULL," -",&last);
710
711
712
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
713
		token=strtok_r(NULL," ",&last);
714
715
716
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token);
717
		token=strtok_r(NULL,":",&last);
718
719
720
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
721
		token=strtok_r(NULL,":",&last);
722
723
724
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
725
		token=strtok_r(NULL," ",&last);
726
727
728
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
729
730
731
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
732

733
	t=time_gm(&ti);
734
	return(t);
735
736
737
738
739
740
741
742
743
}

static SOCKET open_socket(int type)
{
	char	error[256];
	SOCKET	sock;

	sock=socket(AF_INET, type, IPPROTO_IP);
	if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) 
744
		startup->socket_open(startup->cbdata,TRUE);
745
	if(sock!=INVALID_SOCKET) {
746
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
747
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
748
749
750
751
752
753

		sockets++;
	}
	return(sock);
}

754
static int close_socket(SOCKET *sock)
755
756
757
{
	int		result;

758
	if(sock==NULL || *sock==INVALID_SOCKET)
759
760
		return(-1);

deuce's avatar
deuce committed
761
762
	/* required to ensure all data is send when SO_LINGER is off (Not functional on Win32) */
	shutdown(*sock,SHUT_RDWR);
763
764
	result=closesocket(*sock);
	*sock=INVALID_SOCKET;
765
	if(startup!=NULL && startup->socket_open!=NULL) {
766
		startup->socket_open(startup->cbdata,FALSE);
767
768
769
770
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
771
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE);
772
773
774
775
776
	}

	return(result);
}

777
778
779
/* Waits for the outbuf to drain */
static void drain_outbuf(http_session_t * session)
{
780
781
	if(session->socket==INVALID_SOCKET)
		return;
782
783
784
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));
	/* ToDo: This should probobly timeout eventually... */
785
	while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET)
786
787
		SLEEP(1);
	/* Lock the mutex to ensure data has been sent */
788
	while(session->socket!=INVALID_SOCKET && !session->outbuf_write_initialized)
789
		SLEEP(1);
790
	if(session->socket==INVALID_SOCKET)
791
		return;
792
	pthread_mutex_lock(&session->outbuf_write);		/* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */
793
794
795
	pthread_mutex_unlock(&session->outbuf_write);
}

deuce's avatar
deuce committed
796
797
798
799
800
801
802
803
/**************************************************/
/* End of a single request...					  */
/* This is called at the end of EVERY request	  */
/*  Log the request       						  */
/*  Free request-specific data ie: dynamic stuff  */
/*  Close socket unless it's being kept alive     */
/*   If the socket is closed, the session is done */
/**************************************************/
804
805
static void close_request(http_session_t * session)
{
806
	time_t		now;
807
	int			i;
808

809
	if(session->req.write_chunked) {
810
		drain_outbuf(session);
811
812
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
813
		if(session->req.dynamic==IS_SSJS)
814
			ssjs_send_headers(session,FALSE);
815
816
		else
			/* Non-ssjs isn't capable of generating headers during execution */
817
			writebuf(session, newline, 2);
818
819
	}

820
821
822
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

823
824
825
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
826
		listPushNode(&log_list,session->req.ld);
827
828
		session->req.ld=NULL;
	}
829

830
831
832
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
833
	FREE_AND_NULL(session->req.post_data);
834
835
836
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
	FREE_AND_NULL(session->req.realm);
837
838
839
840
	/*
	 * This causes all active http_session_threads to terminate.
	 */
	if((!session->req.keep_alive) || terminate_server) {
841
		drain_outbuf(session);
842
		close_socket(&session->socket);
843
	}
844
845
846
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
847
848
849
	if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
		JS_GC(session->js_cx);
	}
deuce's avatar
deuce committed
850
851
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
852

853
854
855
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

856
857
858
859
860
861
	for(i=0;i<MAX_CLEANUPS;i++) {
		if(session->req.cleanup_file[i]!=NULL) {
			if(!(startup->options&WEB_OPT_DEBUG_SSJS))
				remove(session->req.cleanup_file[i]);
			free(session->req.cleanup_file[i]);
		}
862
863
	}

864
	memset(&session->req,0,sizeof(session->req));
865
866
867
868
}

static int get_header_type(char *header)
{
869
	int i;
870
871
872
873
874
875
876
877
	for(i=0; headers[i].text!=NULL; i++) {
		if(!stricmp(header,headers[i].text)) {
			return(headers[i].id);
		}
	}
	return(-1);
}

deuce's avatar
deuce committed
878
/* Opposite of get_header_type() */
879
880
static char *get_header(int id) 
{
881
	int i;
882
883
	if(headers[id].id==id)
		return(headers[id].text);
884
885
886
887
888
889
890
891
892

	for(i=0;headers[i].text!=NULL;i++) {
		if(headers[i].id==id) {
			return(headers[i].text);
		}
	}
	return(NULL);
}

893
894
static const char* unknown_mime_type="application/octet-stream";

895
static const char* get_mime_type(char *ext)
896
897
898
{
	uint i;

899
	if(ext==NULL || mime_types==NULL)
900
901
		return(unknown_mime_type);

902
	for(i=0;mime_types[i]!=NULL;i++)
903
		if(stricmp(ext+1,mime_types[i]->name)==0)
904
			return(mime_types[i]->value);
905
906

	return(unknown_mime_type);
907
908
}

909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
static BOOL get_cgi_handler(char* cmdline, size_t maxlen)
{
	char	fname[MAX_PATH+1];
	char*	ext;
	size_t	i;

	if(cgi_handlers==NULL || (ext=getfext(cmdline))==NULL)
		return(FALSE);

	for(i=0;cgi_handlers[i]!=NULL;i++) {
		if(stricmp(cgi_handlers[i]->name, ext+1)==0) {
			SAFECOPY(fname,cmdline);
			safe_snprintf(cmdline,maxlen,"%s %s",cgi_handlers[i]->value,fname);
			return(TRUE);
		}
	}
	return(FALSE);
}

static BOOL get_xjs_handler(char* ext, http_session_t* session)
{
	size_t	i;

deuce's avatar
deuce committed
932
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
933
934
935
936
937
938
939
940
941
942
943
944
945
946
		return(FALSE);

	for(i=0;xjs_handlers[i]!=NULL;i++) {
		if(stricmp(xjs_handlers[i]->name, ext+1)==0) {
			if(getfname(xjs_handlers[i]->value)==xjs_handlers[i]->value)	/* no path specified */
				SAFEPRINTF2(session->req.xjs_handler,"%s%s",scfg.exec_dir,xjs_handlers[i]->value);
			else
				SAFECOPY(session->req.xjs_handler,xjs_handlers[i]->value);
			return(TRUE);
		}
	}
	return(FALSE);
}

947
948
/* This function appends append plus a newline IF the final dst string would have a length less than maxlen */
static void safecat(char *dst, const char *append, size_t maxlen) {
949
	size_t dstlen,appendlen;
950
951
952
953
954
955
956
957
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
958
959
960
961
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
962
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
963
{
964
	int		ret;
965
	BOOL	send_file=TRUE;
966
	time_t	ti;
967
	size_t	idx;
968
	const char	*status_line;
969
	struct stat	stats;
970
	struct tm	tm;
971
	char	*headers;
972
	char	header[MAX_REQUEST_LINE+1];
973

deuce's avatar
deuce committed
974
975
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
976
		return(FALSE);
deuce's avatar
deuce committed
977
	}
978
979
980
	lprintf(LOG_DEBUG,"%04d Request resolved to: %s"
		,session->socket,session->req.physical_path);
	if(session->http_ver <= HTTP_0_9) {
deuce's avatar
deuce committed
981
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
982
983
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
984
		return(TRUE);
985
	}
986
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
987
988
989
990
991
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
992
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
993
		session->req.sent_headers=TRUE;
994
995
996
997
998
999
1000
1001
1002
		status_line=status;
		ret=stat(session->req.physical_path,&stats);
		if(session->req.method==HTTP_OPTIONS)
			ret=-1;
		if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
			status_line="304 Not Modified";
			ret=-1;
			send_file=FALSE;
		}
deuce's avatar
deuce committed
1003
1004
1005
1006
1007
		if(!ret && session->req.if_range && (stats.st_mtime > session->req.if_range || session->req.dynamic)) {
			status_line="200 OK";
			session->req.range_start=0;
			session->req.range_end=0;
		}
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
		if(session->req.send_location==MOVED_PERM)  {
			status_line=error_301;
			ret=-1;
			send_file=FALSE;
		}
		if(session->req.send_location==MOVED_TEMP)  {
			status_line=error_302;
			ret=-1;
			send_file=FALSE;
		}
1018

1019
1020
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
1021

1022
1023
1024
1025
		/* Status-Line */
		safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);

		lprintf(LOG_DEBUG,"%04d Result: %s",session->socket,header);
1026

1027
		safecat(headers,header,MAX_HEADERS_SIZE);
1028

1029
1030
1031
1032
1033
1034
1035
1036
		/* General Headers */
		ti=time(NULL);
		if(gmtime_r(&ti,&tm)==NULL)
			memset(&tm,0,sizeof(tm));
		safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
			,get_header(HEAD_DATE)
			,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
			,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
1037
		safecat(headers,header,MAX_HEADERS_SIZE);
1038
1039
1040
1041
1042
1043
1044
1045
		if(session->req.keep_alive) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
1046

1047
1048
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
1049
1050
		safecat(headers,header,MAX_HEADERS_SIZE);

1051
1052
1053
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
1054
			safecat(headers,header,MAX_HEADERS_SIZE);
1055
1056
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"none");
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1057
		}
1058
1059
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
1060
			safecat(headers,header,MAX_HEADERS_SIZE);
1061
1062
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"bytes");
			safecat(headers,