websrvr.c 128 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;
210
211
212
213
214
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
227
228
	,HTTP_POST
	,HTTP_OPTIONS
229
};
230

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

239
enum {
240
241
242
243
244
245
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

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

300
301
302
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"};

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

310
311
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
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);
}
411

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

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

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

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
static int sock_sendbuf(SOCKET sock, const char *buf, size_t len, BOOL *failed)
{
	size_t sent=0;
	int result;

	while(sent<len) {
		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);
			break;
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

468
469
470
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
471
#define SOCKLIB_DESC WSAData.szDescription
472
473
474
475
476
477
478
static BOOL WSAInitialized=FALSE;

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

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

484
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
485
486
487
488
489
490
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
491
#define SOCKLIB_DESC NULL
492
493
494
495
496
497

#endif

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

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
504
		startup->clients(startup->cbdata,active_clients);
505
506
507
508
509
}

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

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

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

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
531
		startup->thread_up(startup->cbdata,FALSE, FALSE);
532
533
}

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

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

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

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

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

	len=strlen(str);
590
	return(writebuf(session,str,len));
591
592
}

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

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

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

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

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

		sockets++;
	}
	return(sock);
}

static int close_socket(SOCKET sock)
{
	int		result;

	if(sock==INVALID_SOCKET)
		return(-1);

	shutdown(sock,SHUT_RDWR);	/* required on Unix */
	result=closesocket(sock);
	if(startup!=NULL && startup->socket_open!=NULL) {
720
		startup->socket_open(startup->cbdata,FALSE);
721
722
723
724
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
725
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
726
727
728
729
730
	}

	return(result);
}

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

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

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

774
775
776
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

777
778
779
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
780
		listPushNode(&log_list,session->req.ld);
781
782
		session->req.ld=NULL;
	}
783

784
785
786
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
787
	FREE_AND_NULL(session->req.post_data);
788
789
790
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
	FREE_AND_NULL(session->req.realm);
791
	if(!session->req.keep_alive) {
792
		drain_outbuf(session);
793
		close_socket(session->socket);
794
		session->socket=INVALID_SOCKET;
795
	}
796
797
798
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
799
800
801
	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
802
803
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
804

805
806
807
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

808
809
810
811
812
813
	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]);
		}
814
815
	}

816
	memset(&session->req,0,sizeof(session->req));
817
818
819
820
}

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

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

845
846
static const char* unknown_mime_type="application/octet-stream";

847
static const char* get_mime_type(char *ext)
848
849
850
{
	uint i;

851
	if(ext==NULL || mime_types==NULL)
852
853
		return(unknown_mime_type);

854
	for(i=0;mime_types[i]!=NULL;i++)
855
		if(stricmp(ext+1,mime_types[i]->name)==0)
856
			return(mime_types[i]->value);
857
858

	return(unknown_mime_type);
859
860
}

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

899
900
/* 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) {
901
	size_t dstlen,appendlen;
902
903
904
905
906
907
908
909
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

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

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

966
967
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
968

969
970
971
972
		/* 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);
973

974
		safecat(headers,header,MAX_HEADERS_SIZE);
975

976
977
978
979
980
981
982
983
		/* 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);
984
		safecat(headers,header,MAX_HEADERS_SIZE);
985
986
987
988
989
990
991
992
		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);
		}
993

994
995
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
996
997
		safecat(headers,header,MAX_HEADERS_SIZE);

998
999
1000
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
1001
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1002
		}
1003
1004
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
1005
			safecat(headers,header,MAX_HEADERS_SIZE);
1006
		}
1007

1008
1009
1010
1011
1012
		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);
		}

1013
		if(chunked) {
1014
1015
1016
1017
1018
			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 */
1019
		if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
			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
1041

1042
1043
	if(session->req.dynamic)  {
		/* Dynamic headers */
1044
		/* Set up environment */
1045
1046
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
1047
1048
		/* 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);
1049
	}
1050

1051
	safecat(headers,"",MAX_HEADERS_SIZE);
1052
	send_file = (bufprint(session,headers) && send_file);
deuce's avatar
deuce committed
1053
	FREE_AND_NULL(headers);
1054
	drain_outbuf(session);
1055
	session->req.write_chunked=chunked;
1056
	return(send_file);
1057
1058
}

1059
static int sock_sendfile(http_session_t *session,char *path)
1060
1061
{
	int		file;
1062
	int		ret=0;
1063
1064
	int		i;
	char	buf[2048];		/* Input buffer */
1065

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

deuce's avatar
deuce committed
1079
1080
1081
1082
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
1083
static void send_error(http_session_t * session, const char* message)
1084
1085
{
	char	error_code[4];
1086
	struct stat	sb;
1087
	char	sbuf[1024];
1088
	BOOL	sent_ssjs=FALSE;
1089

1090
1091
	if(session->socket==INVALID_SOCKET)
		return;
1092
	session->req.if_modified_since=0;
1093
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1094
	session->req.keep_alive=FALSE;
1095
	session->req.send_location=NO_LOCATION;
1096
	SAFECOPY(error_code,message);
1097
1098
1099
1100
1101
1102
1103
1104
	SAFECOPY(session->req.status,message);
	if(atoi(error_code)<500) {
		/*
		 * Attempt to run SSJS error pages
		 * If this fails, do the standard error page instead,
		 * ie: Don't "upgrade" to a 500 error
		 */

1105
		sprintf(sbuf,"%s%s%s",session->req.error_dir?session->req.error_dir:error_dir,error_code,startup->ssjs_ext);
1106
1107
		if(!stat(sbuf,&sb)) {
			lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket);
1108
			session->req<