websrvr.c 158 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 2008 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
/*
 * General notes: (ToDo stuff)
 *
41
 * Support the ident protocol... the standard log format supports it.
42
 *
deuce's avatar
deuce committed
43
44
45
 * 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.
46
47
 */

48
49
//#define ONE_JS_RUNTIME

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
#include "sbbs.h"
63
#include "sbbsdefs.h"
64
#include "sockwrap.h"		/* sendfilesocket() */
65
#include "threadwrap.h"
66
#include "semwrap.h"
67
#include "websrvr.h"
deuce's avatar
deuce committed
68
#include "base64.h"
69
#include "md5.h"
70
#include "js_rtpool.h"
71

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

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

91
92
93
enum {
	 CLEANUP_SSJS_TMP_FILE
	,CLEANUP_POST_DATA
94
	,MAX_CLEANUPS
95
};
96

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

122
static named_string_t** mime_types;
123
124
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
125

126
/* Logging stuff */
127
link_list_t	log_list;
128
129
130
131
132
133
134
struct log_data {
	char	*hostname;
	char	*ident;
	char	*user;
	char	*request;
	char	*referrer;
	char	*agent;
135
	char	*vhost;
136
137
138
139
140
	int		status;
	unsigned int	size;
	struct tm completed;
};

141
142
143
144
145
146
enum auth_type {
	 AUTHENTICATION_UNKNOWN
	,AUTHENTICATION_BASIC
	,AUTHENTICATION_DIGEST
};

147
148
149
150
151
152
153
char *auth_type_names[4] = {
	 "Unknown"
	,"Basic"
	,"Digest"
	,NULL
};

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
enum algorithm {
	 ALGORITHM_UNKNOWN
	,ALGORITHM_MD5
	,ALGORITHM_MD5_SESS
};

enum qop_option {
	 QOP_NONE
	,QOP_AUTH
	,QOP_AUTH_INT
	,QOP_UNKNOWN
};

typedef struct {
	enum auth_type	type;
	char			username[(LEN_ALIAS > LEN_NAME ? LEN_ALIAS : LEN_NAME)+1];
	char			password[LEN_PASS+1];
	char			*digest_uri;
	char			*realm;
	char			*nonce;
	enum algorithm	algorithm;
	enum qop_option	qop_value;
	char			*cnonce;
	char			*nonce_count;
178
	unsigned char	digest[16];		/* MD5 digest */
179
	BOOL			stale;
180
181
} authentication_request_t;

182
typedef struct  {
183
	int			method;
184
185
186
187
188
189
	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];
190
	authentication_request_t	auth;
191
192
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
193
	int			send_location;
194
	const char*	mime_type;
195
	str_list_t	headers;
196
	char		status[MAX_REQUEST_LINE+1];
197
198
	char *		post_data;
	size_t		post_len;
199
	int			dynamic;
200
	char		xjs_handler[MAX_PATH+1];
201
	struct log_data	*ld;
202
	char		request_line[MAX_REQUEST_LINE+1];
203
	BOOL		finished;				/* Done processing request. */
204
205
	BOOL		read_chunked;
	BOOL		write_chunked;
206
207
	long		range_start;
	long		range_end;
208
	BOOL		accept_ranges;
deuce's avatar
deuce committed
209
	time_t		if_range;
210
	BOOL		path_info_index;
211

212
213
214
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
215
216
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
217

218
219
	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;
220
	char		*cleanup_file[MAX_CLEANUPS];
221
222
	BOOL	sent_headers;
	BOOL	prev_write;
223
224
225
226

	/* webconfig.ini overrides */
	char	*error_dir;
	char	*cgi_dir;
227
	char	*auth_list;
228
	char	*realm;
229
	char	*digest_realm;
230
231
232
} http_request_t;

typedef struct  {
233
234
	SOCKET			socket;
	SOCKADDR_IN		addr;
235
236
	SOCKET			socket6;
	SOCKADDR_IN		addr6;
237
	http_request_t	req;
238
239
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
240
241
	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 */
242
243
244
	user_t			user;
	int				last_user_num;
	time_t			logon_time;
245
	char			username[LEN_NAME+1];
246
	int				last_js_user_num;
247
248
249
250
251

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
252
253
	JSObject*		js_query;
	JSObject*		js_header;
254
	JSObject*		js_cookie;
255
	JSObject*		js_request;
256
	js_branch_t		js_branch;
deuce's avatar
deuce committed
257
	subscan_t		*subscan;
258

259
260
261
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
262
263
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
264

265
266
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
267
268
269

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
270
271
272
273
274
} http_session_t;

enum { 
	 HTTP_0_9
	,HTTP_1_0
275
	,HTTP_1_1
276
277
278
279
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
280
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
281
	,NULL	/* terminator */
282
283
284
285
286
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
287
288
	,HTTP_POST
	,HTTP_OPTIONS
289
};
290

rswindell's avatar
rswindell committed
291
292
293
static char* methods[] = {
	 "HEAD"
	,"GET"
294
	,"POST"
295
	,"OPTIONS"
rswindell's avatar
rswindell committed
296
297
	,NULL	/* terminator */
};
298

299
enum {
300
301
302
303
304
305
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

306
enum { 
307
308
309
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
310
311
	,HEAD_LENGTH
	,HEAD_TYPE
312
313
314
315
316
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
317
318
319
320
321
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
322
323
	,HEAD_REFERER
	,HEAD_AGENT
324
	,HEAD_TRANSFER_ENCODING
325
326
327
	,HEAD_ACCEPT_RANGES
	,HEAD_CONTENT_RANGE
	,HEAD_RANGE
deuce's avatar
deuce committed
328
	,HEAD_IFRANGE
329
	,HEAD_COOKIE
330
331
332
333
334
335
};

static struct {
	int		id;
	char*	text;
} headers[] = {
336
337
338
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
339
340
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
341
342
343
344
345
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
346
347
348
349
350
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
351
352
	{ HEAD_REFERER,			"Referer"				},
	{ HEAD_AGENT,			"User-Agent"			},
353
	{ HEAD_TRANSFER_ENCODING,			"Transfer-Encoding"			},
354
355
356
	{ HEAD_ACCEPT_RANGES,	"Accept-Ranges"			},
	{ HEAD_CONTENT_RANGE,	"Content-Range"			},
	{ HEAD_RANGE,			"Range"					},
deuce's avatar
deuce committed
357
	{ HEAD_IFRANGE,			"If-Range"				},
358
	{ HEAD_COOKIE,			"Cookie"				},
359
	{ -1,					NULL /* terminator */	},
360
361
};

362
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
363
enum  {
364
	 NO_LOCATION
365
	,MOVED_PERM
366
	,MOVED_TEMP
367
	,MOVED_STAT
368
369
};

370
371
372
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"};

373
static void respond(http_session_t * session);
374
static BOOL js_setup(http_session_t* session);
375
static char *find_last_slash(char *str);
376
static BOOL check_extra_path(http_session_t * session);
377
static BOOL exec_ssjs(http_session_t* session, char* script);
378
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
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
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
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);
}
481

482
static int lprintf(int level, const char *fmt, ...)
483
484
485
486
487
488
489
490
491
492
493
{
	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);
494
    return(startup->lputs(startup->cbdata,level,sbuf));
495
496
}

497
498
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
499
500
	size_t	sent=0;
	size_t	avail;
501

502
	while(sent < len) {
503
		avail=RingBufFree(&session->outbuf);
deuce's avatar
deuce committed
504
		if(!avail) {
505
			SLEEP(1);
deuce's avatar
deuce committed
506
507
			continue;
		}
508
509
		if(avail > len-sent)
			avail=len-sent;
510
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
511
512
513
514
	}
	return(sent);
}

515
static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
516
517
518
{
	size_t sent=0;
	int result;
519
	int sel;
520
521
	fd_set	wr_set;
	struct timeval tv;
522

523
	while(sent<len && *sock!=INVALID_SOCKET) {
524
525
526
527
528
		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;
529
530
		sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
		switch(sel) {
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
			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:
546
				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
547
548
549
550
				if(failed)
					*failed=TRUE;
				return(sent);
			case -1:
551
				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
552
553
554
				if(failed)
					*failed=TRUE;
				return(sent);
555
556
557
558
559
560
561
562
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

563
564
565
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
566
#define SOCKLIB_DESC WSAData.szDescription
567
568
569
570
571
572
573
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
574
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
575
576
577
578
		WSAInitialized=TRUE;
		return (TRUE);
	}

579
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
580
581
582
583
584
585
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
586
#define SOCKLIB_DESC NULL
587
588
589
590
591
592

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
593
	    startup->status(startup->cbdata,str);
594
595
596
597
598
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
599
		startup->clients(startup->cbdata,active_clients);
600
601
602
603
604
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
605
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
606
607
608
609
610
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
611
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
612
613
614
615
616
617
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
618
		startup->thread_up(startup->cbdata,TRUE, setuid);
619
620
621
622
623
624
625
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
626
		startup->thread_up(startup->cbdata,FALSE, FALSE);
627
628
}

deuce's avatar
deuce committed
629
630
631
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
632
static void add_env(http_session_t *session, const char *name,const char *value)  {
633
	char	newname[129];
634
	char	*p;
635

636
	if(name==NULL || value==NULL)  {
637
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
638
639
640
641
642
643
644
645
646
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
647
	p=(char *)alloca(strlen(name)+strlen(value)+2);
648
649
650
651
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
652
#if 0	/* this is way too verbose for every request */
653
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
654
#endif
655
	sprintf(p,"%s=%s",newname,value);
656
	strListPush(&session->req.cgi_env,p);
657
658
}

deuce's avatar
deuce committed
659
660
661
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
662
663
664
665
666
667
668
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");
669
	if(!strcmp(session->host_name,session->host_ip))
670
671
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
672
	add_env(session,"REQUEST_URI",session->req.request_line);
673
674
}

675
/*
deuce's avatar
deuce committed
676
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
677
678
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
679
static int bufprint(http_session_t *session, const char *str)
680
{
681
682
683
	int len;

	len=strlen(str);
684
	return(writebuf(session,str,len));
685
686
}

deuce's avatar
deuce committed
687
688
689
690
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
691
692
693
694
695
696
697
698
699
700
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
701
702
703
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
704
705
706
static time_t decode_date(char *date)
{
	struct	tm	ti;
707
	char	*token;
708
	char	*last;
709
	time_t	t;
710
711
712
713
714
715
716
717
718

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

719
	token=strtok_r(date,",",&last);
720
721
	if(token==NULL)
		return(0);
722
723
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
724
		/* asctime() */
725
		/* Toss away week day */
726
		token=strtok_r(date," ",&last);
727
728
		if(token==NULL)
			return(0);
729
		token=strtok_r(NULL," ",&last);
730
731
732
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
733
		token=strtok_r(NULL," ",&last);
734
735
736
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
737
		token=strtok_r(NULL,":",&last);
738
739
740
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
741
		token=strtok_r(NULL,":",&last);
742
743
744
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
745
		token=strtok_r(NULL," ",&last);
746
747
748
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
749
		token=strtok_r(NULL,"",&last);
750
751
752
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token)-1900;
753
754
755
	}
	else  {
		/* RFC 1123 or RFC 850 */
756
		token=strtok_r(NULL," -",&last);
757
758
759
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
760
		token=strtok_r(NULL," -",&last);
761
762
763
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
764
		token=strtok_r(NULL," ",&last);
765
766
767
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token);
768
		token=strtok_r(NULL,":",&last);
769
770
771
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
772
		token=strtok_r(NULL,":",&last);
773
774
775
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
776
		token=strtok_r(NULL," ",&last);
777
778
779
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
780
781
782
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
783

784
	t=time_gm(&ti);
785
	return(t);
786
787
788
789
790
791
792
793
794
}

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) 
795
		startup->socket_open(startup->cbdata,TRUE);
796
	if(sock!=INVALID_SOCKET) {
797
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
798
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
799
800
801
802
803
804

		sockets++;
	}
	return(sock);
}

805
static int close_socket(SOCKET *sock)
806
807
808
{
	int		result;

809
	if(sock==NULL || *sock==INVALID_SOCKET)
810
811
		return(-1);

deuce's avatar
deuce committed
812
813
	/* required to ensure all data is send when SO_LINGER is off (Not functional on Win32) */
	shutdown(*sock,SHUT_RDWR);
814
815
	result=closesocket(*sock);
	*sock=INVALID_SOCKET;
816
	if(startup!=NULL && startup->socket_open!=NULL) {
817
		startup->socket_open(startup->cbdata,FALSE);
818
819
820
821
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
822
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE);
823
824
825
826
827
	}

	return(result);
}

828
829
830
/* Waits for the outbuf to drain */
static void drain_outbuf(http_session_t * session)
{
831
832
	if(session->socket==INVALID_SOCKET)
		return;
833
834
835
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));
	/* ToDo: This should probobly timeout eventually... */
836
	while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET)
837
838
		SLEEP(1);
	/* Lock the mutex to ensure data has been sent */
839
	while(session->socket!=INVALID_SOCKET && !session->outbuf_write_initialized)
840
		SLEEP(1);
841
	if(session->socket==INVALID_SOCKET)
842
		return;
843
	pthread_mutex_lock(&session->outbuf_write);		/* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */
844
845
846
	pthread_mutex_unlock(&session->outbuf_write);
}

deuce's avatar
deuce committed
847
848
849
850
851
852
853
854
/**************************************************/
/* 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 */
/**************************************************/
855
856
static void close_request(http_session_t * session)
{
857
	time_t		now;
858
	int			i;
859

860
	if(session->req.write_chunked) {
861
		drain_outbuf(session);
862
863
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
864
		if(session->req.dynamic==IS_SSJS)
865
			ssjs_send_headers(session,FALSE);
866
867
		else
			/* Non-ssjs isn't capable of generating headers during execution */
868
			writebuf(session, newline, 2);
869
870
	}

871
872
873
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

874
875
876
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
877
		listPushNode(&log_list,session->req.ld);
878
879
		session->req.ld=NULL;
	}
880

881
882
883
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
884
	FREE_AND_NULL(session->req.post_data);
885
886
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
887
	FREE_AND_NULL(session->req.auth_list);
888
	FREE_AND_NULL(session->req.realm);
889
	FREE_AND_NULL(session->req.digest_realm);
890

891
	FREE_AND_NULL(session->req.auth_list);
892
893
894
895
896
897
	FREE_AND_NULL(session->req.auth.digest_uri);
	FREE_AND_NULL(session->req.auth.cnonce);
	FREE_AND_NULL(session->req.auth.realm);
	FREE_AND_NULL(session->req.auth.nonce);
	FREE_AND_NULL(session->req.auth.nonce_count);

898
899
900
901
	/*
	 * This causes all active http_session_threads to terminate.
	 */
	if((!session->req.keep_alive) || terminate_server) {
902
		drain_outbuf(session);
903
		close_socket(&session->socket);
904
	}
905
906
907
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
908
	if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
909
		JS_BeginRequest(session->js_cx);
deuce's avatar
deuce committed
910
		JS_GC(session->js_cx);
911
		JS_EndRequest(session->js_cx);
deuce's avatar
deuce committed
912
	}
deuce's avatar
deuce committed
913
914
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
915

916
917
918
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

919
920
921
922
923
924
	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]);
		}
925
926
	}

927
	memset(&session->req,0,sizeof(session->req));
928
929
930
931
}

static int get_header_type(char *header)
{
932
	int i;
933
934
935
936
937
938
939
940
	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
941
/* Opposite of get_header_type() */
942
943
static char *get_header(int id) 
{
944
	int i;
945
946
	if(headers[id].id==id)
		return(headers[id].text);
947
948
949
950
951
952
953
954
955

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

956
957
static const char* unknown_mime_type="application/octet-stream";

958
static const char* get_mime_type(char *ext)
959
960
961
{
	uint i;

962
	if(ext==NULL || mime_types==NULL)
963
964
		return(unknown_mime_type);

965
	for(i=0;mime_types[i]!=NULL;i++)
966
		if(stricmp(ext+1,mime_types[i]->name)==0)
967
			return(mime_types[i]->value);
968
969

	return(unknown_mime_type);
970
971
}

972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
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
995
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
		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);
}

1010
1011
/* 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) {
1012
	size_t dstlen,appendlen;
1013
1014
1015
1016
1017
1018
1019
1020
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
1021
1022
1023
1024
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
1025
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
1026
{
1027
	int		ret;
1028
	BOOL	send_file=TRUE;
1029
	time_t	ti;
1030
	size_t	idx;
1031
	const char	*status_line;
1032
	struct stat	stats;
1033
	struct tm	tm;
1034
	char	*headers;
1035
	char	header[MAX_REQUEST_LINE+1];
1036

deuce's avatar
deuce committed
1037
1038
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
1039
		return(FALSE);
deuce's avatar
deuce committed
1040
	}
1041
1042
1043
	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
1044
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
1045
1046
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
1047
		return(TRUE);
1048
	}
1049
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1050
1051
1052
1053
1054
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
1055