websrvr.c 136 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/* websrvr.c */

/* Synchronet Web Server */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright 2006 Rob Swindell - http://www.synchro.net/copyright.html		*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

38
39
40
41
42
/*
 * General notes: (ToDo stuff)
 *
 * Should support RFC2617 Digest auth.
 *
43
 * Support the ident protocol... the standard log format supports it.
44
 *
deuce's avatar
deuce committed
45
46
47
 * Add in support to pass connections through to a different webserver...
 *      probobly in access.ars... with like a simplified mod_rewrite.
 *      This would allow people to run apache and Synchronet as the same site.
48
49
 */

deuce's avatar
deuce committed
50
/* Headers for CGI stuff */
51
52
#if defined(__unix__)
	#include <sys/wait.h>		/* waitpid() */
rswindell's avatar
rswindell committed
53
54
	#include <sys/types.h>
	#include <signal.h>			/* kill() */
55
56
#endif

57
#ifndef JAVASCRIPT
58
#define JAVASCRIPT
59
60
#endif

61
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
62
63
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
64
#include "threadwrap.h"
65
#include "semwrap.h"
66
#include "websrvr.h"
deuce's avatar
deuce committed
67
#include "base64.h"
68

69
70
static const char*	server_name="Synchronet Web Server";
static const char*	newline="\r\n";
71
72
static const char*	http_scheme="http://";
static const size_t	http_scheme_len=7;
73
74
static const char*	error_301="301 Moved Permanently";
static const char*	error_302="302 Moved Temporarily";
75
static const char*	error_404="404 Not Found";
76
static const char*	error_416="416 Requested Range Not Satisfiable";
77
static const char*	error_500="500 Internal Server Error";
78
static const char*	unknown="<unknown>";
79

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

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

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

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

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

136
typedef struct  {
137
	int			method;
138
139
140
141
142
143
144
	char		virtual_path[MAX_PATH+1];
	char		physical_path[MAX_PATH+1];
	BOOL    	expect_go_ahead;
	time_t		if_modified_since;
	BOOL		keep_alive;
	char		ars[256];
	char    	auth[128];				/* UserID:Password */
145
146
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
147
	int			send_location;
148
	const char*	mime_type;
149
	str_list_t	headers;
150
	char		status[MAX_REQUEST_LINE+1];
151
152
	char *		post_data;
	size_t		post_len;
153
	int			dynamic;
154
	char		xjs_handler[MAX_PATH+1];
155
	struct log_data	*ld;
156
	char		request_line[MAX_REQUEST_LINE+1];
157
	BOOL		finished;				/* Done processing request. */
158
159
	BOOL		read_chunked;
	BOOL		write_chunked;
160
161
	long		range_start;
	long		range_end;
162
	BOOL		accept_ranges;
deuce's avatar
deuce committed
163
	time_t		if_range;
164

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

316
317
318
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"};

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

326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
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);
}
427

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

443
444
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
445
446
	size_t	sent=0;
	size_t	avail;
447

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

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

469
	while(sent<len && *sock!=INVALID_SOCKET) {
470
471
472
473
474
		FD_ZERO(&wr_set);
		FD_SET(*sock,&wr_set);
		/* Convert timeout from ms to sec/usec */
		tv.tv_sec=startup->max_inactivity;
		tv.tv_usec=0;
475
476
		sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
		switch(sel) {
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
			case 1:
				result=sendsocket(*sock,buf+sent,len-sent);
				if(result==SOCKET_ERROR) {
					if(ERROR_VALUE==ECONNRESET) 
						lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",*sock);
					else if(ERROR_VALUE==ECONNABORTED) 
						lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",*sock);
					else
						lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE);
					if(failed)
						*failed=TRUE;
					return(sent);
				}
				break;
			case 0:
492
				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
493
494
495
496
				if(failed)
					*failed=TRUE;
				return(sent);
			case -1:
497
				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
498
499
500
				if(failed)
					*failed=TRUE;
				return(sent);
501
502
503
504
505
506
507
508
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

509
510
511
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
512
#define SOCKLIB_DESC WSAData.szDescription
513
514
515
516
517
518
519
static BOOL WSAInitialized=FALSE;

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

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

525
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
526
527
528
529
530
531
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
532
#define SOCKLIB_DESC NULL
533
534
535
536
537
538

#endif

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

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
545
		startup->clients(startup->cbdata,active_clients);
546
547
548
549
550
}

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

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

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

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
572
		startup->thread_up(startup->cbdata,FALSE, FALSE);
573
574
}

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

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

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

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

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

	len=strlen(str);
630
	return(writebuf(session,str,len));
631
632
}

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

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

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

729
	t=time_gm(&ti);
730
	return(t);
731
732
733
734
735
736
737
738
739
}

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

		sockets++;
	}
	return(sock);
}

750
static int close_socket(SOCKET *sock)
751
752
753
{
	int		result;

754
	if(sock==NULL || *sock==INVALID_SOCKET)
755
756
		return(-1);

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

	return(result);
}

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

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

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

816
817
818
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

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

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

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

849
850
851
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

852
853
854
855
856
857
	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]);
		}
858
859
	}

860
	memset(&session->req,0,sizeof(session->req));
861
862
863
864
}

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

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

889
890
static const char* unknown_mime_type="application/octet-stream";

891
static const char* get_mime_type(char *ext)
892
893
894
{
	uint i;

895
	if(ext==NULL || mime_types==NULL)
896
897
		return(unknown_mime_type);

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

	return(unknown_mime_type);
903
904
}

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

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

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

deuce's avatar
deuce committed
970
971
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
972
		return(FALSE);
deuce's avatar
deuce committed
973
	}
974
975
976
	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
977
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
978
979
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
980
		return(TRUE);
981
	}
982
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
983
984
985
986
987
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
988
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
989
		session->req.sent_headers=TRUE;
990
991
992
993
994
995
996
997
998
		status_line=status;
		ret=stat(session->req.physical_path,&stats);
		if(session->req.method==HTTP_OPTIONS)
			ret=-1;
		if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
			status_line="304 Not Modified";
			ret=-1;
			send_file=FALSE;
		}
deuce's avatar
deuce committed
999
1000
1001
1002
1003
		if(!ret && session->req.if_range && (stats.st_mtime > session->req.if_range || session->req.dynamic)) {
			status_line="200 OK";
			session->req.range_start=0;
			session->req.range_end=0;
		}
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
		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;
		}
1014

1015
1016
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
1017

1018
1019
1020
1021
		/* 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);
1022

1023
		safecat(headers,header,MAX_HEADERS_SIZE);
1024

1025
1026
1027
1028
1029
1030
1031
1032
		/* 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);
1033
		safecat(headers,header,MAX_HEADERS_SIZE);
1034
1035
1036
1037
1038
1039
1040
1041
		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);
		}
1042

1043
1044
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
1045
1046
		safecat(headers,header,MAX_HEADERS_SIZE);

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

1061
1062
1063
1064
1065
		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);
		}

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

		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);
		}
1099
1100
1101
1102

		if(session->req.range_start || session->req.range_end) {
			switch(atoi(status_line)) {
				case 206:	/* Partial reply */
1103
					safe_snprintf(header,sizeof(header),"%s: bytes %d-%d/%d",get_header(HEAD_CONTENT_RANGE),session->req.range_start,session->req.range_end,stats.st_size);
1104
1105
1106
1107
1108
1109
1110
1111
					safecat(headers,header,MAX_HEADERS_SIZE);
					break;
				default:
					safe_snprintf(header,sizeof(header),"%s: *",get_header(HEAD_CONTENT_RANGE));
					safecat(headers,header,MAX_HEADERS_SIZE);
					break;
			}
		}