websrvr.c 131 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
76
static const char*	error_404="404 Not Found";
static const char*	error_500="500 Internal Server Error";
77
static const char*	unknown="<unknown>";
78

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

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

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

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

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

135
typedef struct  {
136
	int			method;
137
138
139
140
141
142
143
	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 */
144
145
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
146
	int			send_location;
147
	const char*	mime_type;
148
	str_list_t	headers;
149
	char		status[MAX_REQUEST_LINE+1];
150
151
	char *		post_data;
	size_t		post_len;
152
	int			dynamic;
153
	char		xjs_handler[MAX_PATH+1];
154
	struct log_data	*ld;
155
	char		request_line[MAX_REQUEST_LINE+1];
156
	BOOL		finished;				/* Done processing request. */
157
158
	BOOL		read_chunked;
	BOOL		write_chunked;
159

160
161
162
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
163
164
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
165

166
167
	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;
168
	char		*cleanup_file[MAX_CLEANUPS];
169
170
	BOOL	sent_headers;
	BOOL	prev_write;
171
172
173
174
175

	/* webconfig.ini overrides */
	char	*error_dir;
	char	*cgi_dir;
	char	*realm;
176
177
178
} http_request_t;

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

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
196
197
198
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
199
	js_branch_t		js_branch;
deuce's avatar
deuce committed
200
	subscan_t		*subscan;
201

202
203
204
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
205
206
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
207

208
209
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
210
211
212

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
213
214
215
216
217
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
230
231
	,HTTP_POST
	,HTTP_OPTIONS
232
};
233

rswindell's avatar
rswindell committed
234
235
236
static char* methods[] = {
	 "HEAD"
	,"GET"
237
	,"POST"
238
	,"OPTIONS"
rswindell's avatar
rswindell committed
239
240
	,NULL	/* terminator */
};
241

242
enum {
243
244
245
246
247
248
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

249
enum { 
250
251
252
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
253
254
	,HEAD_LENGTH
	,HEAD_TYPE
255
256
257
258
259
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
260
261
262
263
264
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
265
266
	,HEAD_REFERER
	,HEAD_AGENT
267
	,HEAD_TRANSFER_ENCODING
268
269
270
271
272
273
};

static struct {
	int		id;
	char*	text;
} headers[] = {
274
275
276
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
277
278
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
279
280
281
282
283
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
284
285
286
287
288
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
289
290
	{ HEAD_REFERER,			"Referer"				},
	{ HEAD_AGENT,			"User-Agent"			},
291
	{ HEAD_TRANSFER_ENCODING,			"Transfer-Encoding"			},
292
	{ -1,					NULL /* terminator */	},
293
294
};

295
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
296
enum  {
297
	 NO_LOCATION
298
	,MOVED_PERM
299
	,MOVED_TEMP
300
	,MOVED_STAT
301
302
};

303
304
305
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"};

306
static void respond(http_session_t * session);
307
static BOOL js_setup(http_session_t* session);
308
static char *find_last_slash(char *str);
309
static BOOL check_extra_path(http_session_t * session);
310
static BOOL exec_ssjs(http_session_t* session, char* script);
311
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
312

313
314
315
316
317
318
319
320
321
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
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);
}
414

415
static int lprintf(int level, char *fmt, ...)
416
417
418
419
420
421
422
423
424
425
426
{
	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);
427
    return(startup->lputs(startup->cbdata,level,sbuf));
428
429
}

430
431
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
432
433
	size_t	sent=0;
	size_t	avail;
434

435
	while(sent < len) {
436
		avail=RingBufFree(&session->outbuf);
deuce's avatar
deuce committed
437
		if(!avail) {
438
			SLEEP(1);
deuce's avatar
deuce committed
439
440
			continue;
		}
441
442
		if(avail > len-sent)
			avail=len-sent;
443
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
444
445
446
447
	}
	return(sent);
}

448
static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
449
450
451
452
{
	size_t sent=0;
	int result;

453
454
	while(sent<len && *sock!=INVALID_SOCKET) {
		result=sendsocket(*sock,buf+sent,len-sent);
455
456
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
457
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",*sock);
458
			else if(ERROR_VALUE==ECONNABORTED) 
459
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",*sock);
460
			else
461
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE);
462
463
464
465
466
467
468
469
470
			break;
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

471
472
473
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
474
#define SOCKLIB_DESC WSAData.szDescription
475
476
477
478
479
480
481
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
482
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
483
484
485
486
		WSAInitialized=TRUE;
		return (TRUE);
	}

487
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
488
489
490
491
492
493
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
494
#define SOCKLIB_DESC NULL
495
496
497
498
499
500

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
501
	    startup->status(startup->cbdata,str);
502
503
504
505
506
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
507
		startup->clients(startup->cbdata,active_clients);
508
509
510
511
512
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
513
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
514
515
516
517
518
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
519
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
520
521
522
523
524
525
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
526
		startup->thread_up(startup->cbdata,TRUE, setuid);
527
528
529
530
531
532
533
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
534
		startup->thread_up(startup->cbdata,FALSE, FALSE);
535
536
}

deuce's avatar
deuce committed
537
538
539
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
540
static void add_env(http_session_t *session, const char *name,const char *value)  {
541
	char	newname[129];
542
	char	*p;
543

544
	if(name==NULL || value==NULL)  {
545
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
546
547
548
549
550
551
552
553
554
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
555
	p=(char *)alloca(strlen(name)+strlen(value)+2);
556
557
558
559
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
560
#if 0	/* this is way too verbose for every request */
561
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
562
#endif
563
	sprintf(p,"%s=%s",newname,value);
564
	strListPush(&session->req.cgi_env,p);
565
566
}

deuce's avatar
deuce committed
567
568
569
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
570
571
572
573
574
575
576
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");
577
	if(!strcmp(session->host_name,session->host_ip))
578
579
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
580
	add_env(session,"REQUEST_URI",session->req.request_line);
581
582
}

583
/*
deuce's avatar
deuce committed
584
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
585
586
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
587
static int bufprint(http_session_t *session, const char *str)
588
{
589
590
591
	int len;

	len=strlen(str);
592
	return(writebuf(session,str,len));
593
594
}

deuce's avatar
deuce committed
595
596
597
598
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
599
600
601
602
603
604
605
606
607
608
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
609
610
611
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
612
613
614
static time_t decode_date(char *date)
{
	struct	tm	ti;
615
616
	char	*token;
	time_t	t;
617
618
619
620
621
622
623
624
625

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

626
	token=strtok(date,",");
627
628
	if(token==NULL)
		return(0);
629
630
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
631
		/* asctime() */
632
633
		/* Toss away week day */
		token=strtok(date," ");
634
635
		if(token==NULL)
			return(0);
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
		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;
660
661
662
	}
	else  {
		/* RFC 1123 or RFC 850 */
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
		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);
687
688
689
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
690

691
	t=time_gm(&ti);
692
	return(t);
693
694
695
696
697
698
699
700
701
}

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) 
702
		startup->socket_open(startup->cbdata,TRUE);
703
	if(sock!=INVALID_SOCKET) {
704
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
705
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
706
707
708
709
710
711

		sockets++;
	}
	return(sock);
}

712
static int close_socket(SOCKET *sock)
713
714
715
{
	int		result;

716
	if(sock==NULL || *sock==INVALID_SOCKET)
717
718
		return(-1);

deuce's avatar
deuce committed
719
720
	/* required to ensure all data is send when SO_LINGER is off (Not functional on Win32) */
	shutdown(*sock,SHUT_RDWR);
721
722
	result=closesocket(*sock);
	*sock=INVALID_SOCKET;
723
	if(startup!=NULL && startup->socket_open!=NULL) {
724
		startup->socket_open(startup->cbdata,FALSE);
725
726
727
728
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
729
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE);
730
731
732
733
734
	}

	return(result);
}

735
736
737
/* Waits for the outbuf to drain */
static void drain_outbuf(http_session_t * session)
{
738
739
	if(session->socket==INVALID_SOCKET)
		return;
740
741
742
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));
	/* ToDo: This should probobly timeout eventually... */
743
	while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET)
744
745
		SLEEP(1);
	/* Lock the mutex to ensure data has been sent */
746
	while(session->socket!=INVALID_SOCKET && !session->outbuf_write_initialized)
747
		SLEEP(1);
748
	if(session->socket==INVALID_SOCKET)
749
		return;
750
	pthread_mutex_lock(&session->outbuf_write);		/* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */
751
752
753
	pthread_mutex_unlock(&session->outbuf_write);
}

deuce's avatar
deuce committed
754
755
756
757
758
759
760
761
/**************************************************/
/* 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 */
/**************************************************/
762
763
static void close_request(http_session_t * session)
{
764
	time_t		now;
765
	int			i;
766

767
	if(session->req.write_chunked) {
768
		drain_outbuf(session);
769
770
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
771
		if(session->req.dynamic==IS_SSJS)
772
			ssjs_send_headers(session,FALSE);
773
774
		else
			/* Non-ssjs isn't capable of generating headers during execution */
775
			writebuf(session, newline, 2);
776
777
	}

778
779
780
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

781
782
783
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
784
		listPushNode(&log_list,session->req.ld);
785
786
		session->req.ld=NULL;
	}
787

788
789
790
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
791
	FREE_AND_NULL(session->req.post_data);
792
793
794
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
	FREE_AND_NULL(session->req.realm);
795
796
797
798
	/*
	 * This causes all active http_session_threads to terminate.
	 */
	if((!session->req.keep_alive) || terminate_server) {
799
		drain_outbuf(session);
800
		close_socket(&session->socket);
801
	}
802
803
804
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
805
806
807
	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
808
809
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
810

811
812
813
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

814
815
816
817
818
819
	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]);
		}
820
821
	}

822
	memset(&session->req,0,sizeof(session->req));
823
824
825
826
}

static int get_header_type(char *header)
{
827
	int i;
828
829
830
831
832
833
834
835
	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
836
/* Opposite of get_header_type() */
837
838
static char *get_header(int id) 
{
839
	int i;
840
841
	if(headers[id].id==id)
		return(headers[id].text);
842
843
844
845
846
847
848
849
850

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

851
852
static const char* unknown_mime_type="application/octet-stream";

853
static const char* get_mime_type(char *ext)
854
855
856
{
	uint i;

857
	if(ext==NULL || mime_types==NULL)
858
859
		return(unknown_mime_type);

860
	for(i=0;mime_types[i]!=NULL;i++)
861
		if(stricmp(ext+1,mime_types[i]->name)==0)
862
			return(mime_types[i]->value);
863
864

	return(unknown_mime_type);
865
866
}

867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
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
890
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
891
892
893
894
895
896
897
898
899
900
901
902
903
904
		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);
}

905
906
/* 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) {
907
	size_t dstlen,appendlen;
908
909
910
911
912
913
914
915
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
916
917
918
919
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
920
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
921
{
922
	int		ret;
923
	BOOL	send_file=TRUE;
924
	time_t	ti;
925
	size_t	idx;
926
	const char	*status_line;
927
	struct stat	stats;
928
	struct tm	tm;
929
	char	*headers;
930
	char	header[MAX_REQUEST_LINE+1];
931

deuce's avatar
deuce committed
932
933
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
934
		return(FALSE);
deuce's avatar
deuce committed
935
	}
936
937
938
	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
939
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
940
941
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
942
		return(TRUE);
943
	}
944
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
945
946
947
948
949
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
950
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
951
		session->req.sent_headers=TRUE;
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
		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;
		}
971

972
973
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
974

975
976
977
978
		/* 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);
979

980
		safecat(headers,header,MAX_HEADERS_SIZE);
981

982
983
984
985
986
987
988
989
		/* 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);
990
		safecat(headers,header,MAX_HEADERS_SIZE);
991
992
993
994
995
996
997
998
		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);
		}
999

1000
1001
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
1002
1003
		safecat(headers,header,MAX_HEADERS_SIZE);

1004
1005
1006
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
1007
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1008
		}
1009
1010
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
1011
			safecat(headers,header,MAX_HEADERS_SIZE);
1012
		}
1013

1014
1015
1016
1017
1018
		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);
		}

1019
		if(chunked) {
1020
1021
1022
1023
1024
			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 */
1025
		if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
			if(ret)  {
				safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
				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);
			}
		}

		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);
		}
	}
rswindell's avatar
rswindell committed
1047

1048
1049
	if(session->req.dynamic)  {
		/* Dynamic headers */
1050
		/* Set up environment */
1051
1052
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
1053
1054
		/* 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);
1055
	}
1056

1057
	safecat(headers,"",MAX_HEADERS_SIZE);
1058
	send_file = (bufprint(session,headers) && send_file);
1059
	drain_outbuf(session);
1060
	session->req.write_chunked=chunked;
1061
	return(send_file);
1062
1063
}

1064
static int sock_sendfile(http_session_t *session,char *path)
1065
1066
{
	int		file;
1067
	int		ret=0;
1068
1069
	int		i;
	char	buf[2048];		/* Input buffer */
1070

1071
	if(startup->options&WEB_OPT_DEBUG_TX)
1072
		lprintf(LOG_DEBUG,"%04d Sending %s",session->socket,path);
1073
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
1074
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",session->socket,errno,path);
1075
	else {
1076
1077
		while((i=read(file, buf, sizeof(buf)))>0) {
			writebuf(session,buf,i);
1078
		}
1079
1080
		close(file);
	}
1081
	return(ret);
1082
1083
}

deuce's avatar
deuce committed
1084
1085
1086
1087
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
1088
static void send_error(http_session_t * session, const char* message)
1089
1090
{
	char	error_code[4];