websrvr.c 143 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;
deuce's avatar
deuce committed
97
static DWORD	active_clients=0;
98
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
	BOOL		path_info_index;
165

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

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

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

typedef struct  {
185
186
	SOCKET			socket;
	SOCKADDR_IN		addr;
187
	http_request_t	req;
188
189
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
190
191
	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 */
192
193
194
	user_t			user;
	int				last_user_num;
	time_t			logon_time;
195
	char			username[LEN_NAME+1];
196
	int				last_js_user_num;
197
198
199
200
201

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

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

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

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

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

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

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

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

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

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

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

320
321
322
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"};

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

330
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
430
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);
}
431

432
static int lprintf(int level, char *fmt, ...)
433
434
435
436
437
438
439
440
441
442
443
{
	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);
444
    return(startup->lputs(startup->cbdata,level,sbuf));
445
446
}

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

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

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

473
	while(sent<len && *sock!=INVALID_SOCKET) {
474
475
476
477
478
		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;
479
480
		sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
		switch(sel) {
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
			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:
496
				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
497
498
499
500
				if(failed)
					*failed=TRUE;
				return(sent);
			case -1:
501
				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
502
503
504
				if(failed)
					*failed=TRUE;
				return(sent);
505
506
507
508
509
510
511
512
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

513
514
515
#ifdef _WINSOCKAPI_

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

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

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

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

#else /* No WINSOCK */

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

#endif

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

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

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

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

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

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

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

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

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

deuce's avatar
deuce committed
609
610
611
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
612
613
614
615
616
617
618
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");
619
	if(!strcmp(session->host_name,session->host_ip))
620
621
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
622
	add_env(session,"REQUEST_URI",session->req.request_line);
623
624
}

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

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

deuce's avatar
deuce committed
637
638
639
640
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
641
642
643
644
645
646
647
648
649
650
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
651
652
653
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
654
655
656
static time_t decode_date(char *date)
{
	struct	tm	ti;
657
	char	*token;
658
	char	*last;
659
	time_t	t;
660
661
662
663
664
665
666
667
668

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

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

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

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

		sockets++;
	}
	return(sock);
}

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

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

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

	return(result);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return(unknown_mime_type);
908
909
}

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

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

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

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

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

1023
1024
1025
1026
		/* 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);
1027

1028
		safecat(headers,header,MAX_HEADERS_SIZE);
1029

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

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

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