websrvr.c 135 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
162
	unsigned long	range_start;
	unsigned long	range_end;
	BOOL		accept_ranges;
163

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

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

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

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

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

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

212
213
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
214
215
216

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
217
218
219
220
221
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
234
235
	,HTTP_POST
	,HTTP_OPTIONS
236
};
237

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

246
enum {
247
248
249
250
251
252
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

305
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
306
enum  {
307
	 NO_LOCATION
308
	,MOVED_PERM
309
	,MOVED_TEMP
310
	,MOVED_STAT
311
312
};

313
314
315
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"};

316
static void respond(http_session_t * session);
317
static BOOL js_setup(http_session_t* session);
318
static char *find_last_slash(char *str);
319
static BOOL check_extra_path(http_session_t * session);
320
static BOOL exec_ssjs(http_session_t* session, char* script);
321
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
322

323
324
325
326
327
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
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);
}
424

425
static int lprintf(int level, char *fmt, ...)
426
427
428
429
430
431
432
433
434
435
436
{
	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);
437
    return(startup->lputs(startup->cbdata,level,sbuf));
438
439
}

440
441
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
442
443
	size_t	sent=0;
	size_t	avail;
444

445
	while(sent < len) {
446
		avail=RingBufFree(&session->outbuf);
deuce's avatar
deuce committed
447
		if(!avail) {
448
			SLEEP(1);
deuce's avatar
deuce committed
449
450
			continue;
		}
451
452
		if(avail > len-sent)
			avail=len-sent;
453
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
454
455
456
457
	}
	return(sent);
}

458
static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
459
460
461
{
	size_t sent=0;
	int result;
462
	int sel;
463
464
	fd_set	wr_set;
	struct timeval tv;
465

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

506
507
508
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
509
#define SOCKLIB_DESC WSAData.szDescription
510
511
512
513
514
515
516
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
517
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
518
519
520
521
		WSAInitialized=TRUE;
		return (TRUE);
	}

522
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
523
524
525
526
527
528
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
529
#define SOCKLIB_DESC NULL
530
531
532
533
534
535

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
536
	    startup->status(startup->cbdata,str);
537
538
539
540
541
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
542
		startup->clients(startup->cbdata,active_clients);
543
544
545
546
547
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
548
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
549
550
551
552
553
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
554
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
555
556
557
558
559
560
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
561
		startup->thread_up(startup->cbdata,TRUE, setuid);
562
563
564
565
566
567
568
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
569
		startup->thread_up(startup->cbdata,FALSE, FALSE);
570
571
}

deuce's avatar
deuce committed
572
573
574
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
575
static void add_env(http_session_t *session, const char *name,const char *value)  {
576
	char	newname[129];
577
	char	*p;
578

579
	if(name==NULL || value==NULL)  {
580
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
581
582
583
584
585
586
587
588
589
		return;
	}
	SAFECOPY(newname,name);

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

deuce's avatar
deuce committed
602
603
604
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
605
606
607
608
609
610
611
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");
612
	if(!strcmp(session->host_name,session->host_ip))
613
614
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
615
	add_env(session,"REQUEST_URI",session->req.request_line);
616
617
}

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

	len=strlen(str);
627
	return(writebuf(session,str,len));
628
629
}

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

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

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

726
	t=time_gm(&ti);
727
	return(t);
728
729
730
731
732
733
734
735
736
}

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) 
737
		startup->socket_open(startup->cbdata,TRUE);
738
	if(sock!=INVALID_SOCKET) {
739
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
740
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
741
742
743
744
745
746

		sockets++;
	}
	return(sock);
}

747
static int close_socket(SOCKET *sock)
748
749
750
{
	int		result;

751
	if(sock==NULL || *sock==INVALID_SOCKET)
752
753
		return(-1);

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

	return(result);
}

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

deuce's avatar
deuce committed
789
790
791
792
793
794
795
796
/**************************************************/
/* 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 */
/**************************************************/
797
798
static void close_request(http_session_t * session)
{
799
	time_t		now;
800
	int			i;
801

802
	if(session->req.write_chunked) {
803
		drain_outbuf(session);
804
805
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
806
		if(session->req.dynamic==IS_SSJS)
807
			ssjs_send_headers(session,FALSE);
808
809
		else
			/* Non-ssjs isn't capable of generating headers during execution */
810
			writebuf(session, newline, 2);
811
812
	}

813
814
815
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

816
817
818
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
819
		listPushNode(&log_list,session->req.ld);
820
821
		session->req.ld=NULL;
	}
822

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

deuce's avatar
deuce committed
840
841
842
	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
843
844
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
845

846
847
848
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

849
850
851
852
853
854
	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]);
		}
855
856
	}

857
	memset(&session->req,0,sizeof(session->req));
858
859
860
861
}

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

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

886
887
static const char* unknown_mime_type="application/octet-stream";

888
static const char* get_mime_type(char *ext)
889
890
891
{
	uint i;

892
	if(ext==NULL || mime_types==NULL)
893
894
		return(unknown_mime_type);

895
	for(i=0;mime_types[i]!=NULL;i++)
896
		if(stricmp(ext+1,mime_types[i]->name)==0)
897
			return(mime_types[i]->value);
898
899

	return(unknown_mime_type);
900
901
}

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

940
941
/* 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) {
942
	size_t dstlen,appendlen;
943
944
945
946
947
948
949
950
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

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

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

1007
1008
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
1009

1010
1011
1012
1013
		/* 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);
1014

1015
		safecat(headers,header,MAX_HEADERS_SIZE);
1016

1017
1018
1019
1020
1021
1022
1023
1024
		/* 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);
1025
		safecat(headers,header,MAX_HEADERS_SIZE);
1026
1027
1028
1029
1030
1031
1032
1033
		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);
		}
1034

1035
1036
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
1037
1038
		safecat(headers,header,MAX_HEADERS_SIZE);

1039
1040
1041
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
1042
			safecat(headers,header,MAX_HEADERS_SIZE);
1043
1044
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"none");
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1045
		}
1046
1047
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
1048
			safecat(headers,header,MAX_HEADERS_SIZE);
1049
1050
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"bytes");
			safecat(headers,header,MAX_HEADERS_SIZE);
1051
		}
1052

1053
1054
1055
1056
1057
		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);
		}

1058
		if(chunked) {
1059
1060
1061
1062
1063
			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 */
1064
		if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
1065
1066
1067
1068
1069
			if(ret)  {
				safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
				safecat(headers,header,MAX_HEADERS_SIZE);
			}
			else  {
1070
				if((session->req.range_start || session->req.range_end) && atoi(status_line)==206) {
deuce's avatar
deuce committed
1071
					safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),session->req.range_end-session->req.range_start+1);
1072
1073
1074
1075
1076
1077
					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);
				}
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
			}
		}

		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);
		}
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103

		if(session->req.range_start || session->req.range_end) {
			switch(atoi(status_line)) {
				case 206:	/* Partial reply */
					safe_snprintf(header,sizeof(header),"%s: %d-%d/%d",get_header(HEAD_CONTENT_RANGE),session->req.range_start,session->req.range_end,stats.st_size);
					safecat(headers,header,MAX_HEADERS_SIZE);
					break;
				default:
					safe_snprintf(header,sizeof(header),"%s: *",get_header(HEAD_CONTENT_RANGE));
					safecat(headers,header,MAX_HEADERS_SIZE);
					break;
			}
		}
1104
	}
rswindell's avatar
rswindell committed
1105

1106
1107
	if(session->req.dynamic)  {
		/* Dynamic headers */
1108
		/* Set up environment */
1109
1110
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
1111
1112
		/* free() the headers so they don't get sent again if more are sent at the end of the request (chunked) */
		strListFreeStrings(session->req.dynamic_heads);
1113
	}
1114