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 time_t	uptime=0;
108
static DWORD	served=0;
109
static web_startup_t* startup=NULL;
110
static js_server_props_t js_server_props;
111
112
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
113

114
static named_string_t** mime_types;
115
116
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
117

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

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

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

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

	/* webconfig.ini overrides */
	char	*error_dir;
	char	*cgi_dir;
	char	*realm;
174
175
176
} http_request_t;

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

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

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

206
207
	/* Client info */
	client_t		client;
208
209
210
211
212
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
225
226
	,HTTP_POST
	,HTTP_OPTIONS
227
};
228

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

237
enum {
238
239
240
241
242
243
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

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

298
299
300
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"};

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

308
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
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);
}
409

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

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

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

443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
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);
}

466
467
468
#ifdef _WINSOCKAPI_

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

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

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

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

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
489
#define SOCKLIB_DESC NULL
490
491
492
493
494
495

#endif

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

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

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

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

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

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

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

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

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

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

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

	len=strlen(str);
588
	return(writebuf(session,str,len));
589
590
}

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

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

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

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

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

		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) {
718
		startup->socket_open(startup->cbdata,FALSE);
719
720
721
722
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
723
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
724
725
726
727
728
	}

	return(result);
}

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

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

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

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

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

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

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

803
804
805
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

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

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

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

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

843
844
static const char* unknown_mime_type="application/octet-stream";

845
static const char* get_mime_type(char *ext)
846
847
848
{
	uint i;

849
	if(ext==NULL || mime_types==NULL)
850
851
		return(unknown_mime_type);

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

	return(unknown_mime_type);
857
858
}

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

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

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

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

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

967
968
969
970
		/* 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);
971

972
		safecat(headers,header,MAX_HEADERS_SIZE);
973

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

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

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

1006
1007
1008
1009
1010
		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);
		}

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

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

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

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

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

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

1088
1089
	if(session->socket==INVALID_SOCKET)
		return;
1090
	session->req.if_modified_since=0;
1091
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1092
	session->req.keep_alive=FALSE;
1093
	session->req.send_location=NO_LOCATION;
1094
	SAFECOPY(error_code,message);
1095
1096
1097
1098
1099
1100
1101
1102
	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
		 */

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