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
657
	char	*token;
	time_t	t;
658
659
660
661
662
663
664
665
666

	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? */

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

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

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) 
743
		startup->socket_open(startup->cbdata,TRUE);
744
	if(sock!=INVALID_SOCKET) {
745
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
746
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
747
748
749
750
751
752

		sockets++;
	}
	return(sock);
}

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

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

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

	return(result);
}

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

deuce's avatar
deuce committed
795
796
797
798
799
800
801
802
/**************************************************/
/* 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 */
/**************************************************/
803
804
static void close_request(http_session_t * session)
{
805
	time_t		now;
806
	int			i;
807

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

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

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

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

deuce's avatar
deuce committed
846
847
848
	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
849
850
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
851

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

855
856
857
858
859
860
	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]);
		}
861
862
	}

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

static int get_header_type(char *header)
{
868
	int i;
869
870
871
872
873
874
875
876
	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
877
/* Opposite of get_header_type() */
878
879
static char *get_header(int id) 
{
880
	int i;
881
882
	if(headers[id].id==id)
		return(headers[id].text);
883
884
885
886
887
888
889
890
891

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

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

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

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

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

	return(unknown_mime_type);
906
907
}

908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
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
931
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
932
933
934
935
936
937
938
939
940
941
942
943
944
945
		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);
}

946
947
/* 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) {
948
	size_t dstlen,appendlen;
949
950
951
952
953
954
955
956
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

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

deuce's avatar
deuce committed
973
974
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
975
		return(FALSE);
deuce's avatar
deuce committed
976
	}
977
978
979
	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
980
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
981
982
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
983
		return(TRUE);
984
	}
985
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
986
987
988
989
990
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
991
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
992
		session->req.sent_headers=TRUE;
993
994
995
996
997
998
999
1000
1001
		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
1002
1003
1004
1005
1006
		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;
		}
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
		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;
		}
1017

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

1021
1022
1023
1024
		/* 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);
1025

1026
		safecat(headers,header,MAX_HEADERS_SIZE);
1027

1028
1029
1030
1031
1032
1033
1034
1035
		/* 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);
1036
		safecat(headers,header,MAX_HEADERS_SIZE);
1037
1038
1039
1040
1041
1042
1043
1044
		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);
		}
1045

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

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

1064
1065
1066
1067
1068
		if(session->req.send_location) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
			safecat(headers,header,MAX_HEADERS_SIZE);
		}

1069
		if(chunked) {
1070
1071
1072
1073
1074
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TRANSFER_ENCODING),"Chunked");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}

		/* DO NOT send a content-length for chunked */
1075
		if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
1076
1077
1078
1079
1080
			if(ret)  {
				safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
				safecat(headers,header,MAX_HEADERS_SIZE);
			}
			else  {
1081
				if((session->req.range_start || session->req.range_end) && atoi(status_line)==206) {
deuce's avatar
deuce committed
1082
					safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),session->req.range_end-session->req.range_start+1);
1083
1084
1085
1086
1087
1088
					safecat(headers,header,MAX_HEADERS_SIZE);
				}
				else {
					safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
					safecat(headers,header,MAX_HEADERS_SIZE);
				}
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
			}
		}

		if(!ret && !session->req.dynamic)  {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
			safecat(headers,header,MAX_HEADERS_SIZE);
			gmtime_r(&stats.st_mtime,&tm);
			safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
				,get_header(HEAD_LASTMODIFIED)
				,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
				,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
1102
1103
1104
1105

		if(session->req.range_start || session->req.range_end) {
			switch(atoi(status_line)) {
				case 206:	/* Partial reply */