websrvr.c 160 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)		*
 *																			*
rswindell's avatar
rswindell committed
11
 * Copyright 2009 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
#include "js_request.h"
72

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

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

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

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

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

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

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

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

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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;
179
	unsigned char	digest[16];		/* MD5 digest */
180
	BOOL			stale;
181
182
} authentication_request_t;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

374
static void respond(http_session_t * session);
375
static BOOL js_setup(http_session_t* session);
376
static char *find_last_slash(char *str);
377
static BOOL check_extra_path(http_session_t * session);
378
static BOOL exec_ssjs(http_session_t* session, char* script);
379
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
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
481
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);
}
482

483
static int lprintf(int level, const char *fmt, ...)
484
485
486
487
488
489
490
491
{
	va_list argptr;
	char sbuf[1024];

	va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
492
493

	if(level <= LOG_ERR)
494
		errorlog(&scfg,startup==NULL ? NULL:startup->host_name, sbuf);
495
496
497
498
499
500
501
502
503

    if(startup==NULL || startup->lputs==NULL || level > startup->log_level)
        return(0);

#if defined(_WIN32)
	if(IsBadCodePtr((FARPROC)startup->lputs))
		return(0);
#endif

504
    return(startup->lputs(startup->cbdata,level,sbuf));
505
506
}

507
508
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
509
510
	size_t	sent=0;
	size_t	avail;
511

512
	while(sent < len) {
513
		avail=RingBufFree(&session->outbuf);
deuce's avatar
deuce committed
514
		if(!avail) {
515
			SLEEP(1);
deuce's avatar
deuce committed
516
517
			continue;
		}
518
519
		if(avail > len-sent)
			avail=len-sent;
520
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
521
522
523
524
	}
	return(sent);
}

525
static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
526
527
528
{
	size_t sent=0;
	int result;
529
	int sel;
530
531
	fd_set	wr_set;
	struct timeval tv;
532

533
	while(sent<len && *sock!=INVALID_SOCKET) {
534
535
536
537
538
		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;
539
540
		sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
		switch(sel) {
541
542
543
544
545
546
547
			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);
548
549
550
551
#ifdef EPIPE
					else if(ERROR_VALUE==EPIPE) 
						lprintf(LOG_NOTICE,"%04d Unable to send to peer",*sock);
#endif
552
553
554
555
556
557
558
559
					else
						lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE);
					if(failed)
						*failed=TRUE;
					return(sent);
				}
				break;
			case 0:
560
				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
561
562
563
564
				if(failed)
					*failed=TRUE;
				return(sent);
			case -1:
565
				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
566
567
568
				if(failed)
					*failed=TRUE;
				return(sent);
569
570
571
572
573
574
575
576
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

577
578
579
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
580
#define SOCKLIB_DESC WSAData.szDescription
581
582
583
584
585
586
587
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
588
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
589
590
591
592
		WSAInitialized=TRUE;
		return (TRUE);
	}

593
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
594
595
596
597
598
599
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
600
#define SOCKLIB_DESC NULL
601
602
603
604
605
606

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
607
	    startup->status(startup->cbdata,str);
608
609
610
611
612
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
613
		startup->clients(startup->cbdata,active_clients);
614
615
616
617
618
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
619
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
620
621
622
623
624
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
625
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
626
627
628
629
630
631
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
632
		startup->thread_up(startup->cbdata,TRUE, setuid);
633
634
635
636
637
638
639
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
640
		startup->thread_up(startup->cbdata,FALSE, FALSE);
641
642
}

deuce's avatar
deuce committed
643
644
645
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
646
static void add_env(http_session_t *session, const char *name,const char *value)  {
647
	char	newname[129];
648
	char	*p;
649

650
	if(name==NULL || value==NULL)  {
651
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
652
653
654
655
656
657
658
659
660
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
661
	p=(char *)alloca(strlen(name)+strlen(value)+2);
662
663
664
665
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
666
#if 0	/* this is way too verbose for every request */
667
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
668
#endif
669
	sprintf(p,"%s=%s",newname,value);
670
	strListPush(&session->req.cgi_env,p);
671
672
}

deuce's avatar
deuce committed
673
674
675
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
676
677
678
679
680
681
682
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");
683
	if(!strcmp(session->host_name,session->host_ip))
684
685
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
686
	add_env(session,"REQUEST_URI",session->req.request_line);
687
688
}

689
/*
deuce's avatar
deuce committed
690
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
691
692
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
693
static int bufprint(http_session_t *session, const char *str)
694
{
695
696
697
	int len;

	len=strlen(str);
698
	return(writebuf(session,str,len));
699
700
}

deuce's avatar
deuce committed
701
702
703
704
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
705
706
707
708
709
710
711
712
713
714
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
715
716
717
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
718
719
720
static time_t decode_date(char *date)
{
	struct	tm	ti;
721
	char	*token;
722
	char	*last;
723
	time_t	t;
724
725
726
727
728
729
730
731
732

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

733
	token=strtok_r(date,",",&last);
734
735
	if(token==NULL)
		return(0);
736
737
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
738
		/* asctime() */
739
		/* Toss away week day */
740
		token=strtok_r(date," ",&last);
741
742
		if(token==NULL)
			return(0);
743
		token=strtok_r(NULL," ",&last);
744
745
746
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
747
		token=strtok_r(NULL," ",&last);
748
749
750
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
751
		token=strtok_r(NULL,":",&last);
752
753
754
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
755
		token=strtok_r(NULL,":",&last);
756
757
758
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
759
		token=strtok_r(NULL," ",&last);
760
761
762
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
763
		token=strtok_r(NULL,"",&last);
764
765
766
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token)-1900;
767
768
769
	}
	else  {
		/* RFC 1123 or RFC 850 */
770
		token=strtok_r(NULL," -",&last);
771
772
773
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
774
		token=strtok_r(NULL," -",&last);
775
776
777
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
778
		token=strtok_r(NULL," ",&last);
779
780
781
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token);
782
		token=strtok_r(NULL,":",&last);
783
784
785
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
786
		token=strtok_r(NULL,":",&last);
787
788
789
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
790
		token=strtok_r(NULL," ",&last);
791
792
793
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
794
795
796
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
797

798
	t=time_gm(&ti);
799
	return(t);
800
801
802
803
804
805
806
807
808
}

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) 
809
		startup->socket_open(startup->cbdata,TRUE);
810
	if(sock!=INVALID_SOCKET) {
811
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
812
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
813
814
815
816
817
818

		sockets++;
	}
	return(sock);
}

819
static int close_socket(SOCKET *sock)
820
821
822
{
	int		result;

823
	if(sock==NULL || *sock==INVALID_SOCKET)
824
825
		return(-1);

deuce's avatar
deuce committed
826
827
	/* required to ensure all data is send when SO_LINGER is off (Not functional on Win32) */
	shutdown(*sock,SHUT_RDWR);
828
829
	result=closesocket(*sock);
	*sock=INVALID_SOCKET;
830
	if(startup!=NULL && startup->socket_open!=NULL) {
831
		startup->socket_open(startup->cbdata,FALSE);
832
833
834
835
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
836
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE);
837
838
839
840
841
	}

	return(result);
}

842
843
844
/* Waits for the outbuf to drain */
static void drain_outbuf(http_session_t * session)
{
845
846
	if(session->socket==INVALID_SOCKET)
		return;
847
848
849
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));
	/* ToDo: This should probobly timeout eventually... */
850
	while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET)
851
852
		SLEEP(1);
	/* Lock the mutex to ensure data has been sent */
853
	while(session->socket!=INVALID_SOCKET && !session->outbuf_write_initialized)
854
		SLEEP(1);
855
	if(session->socket==INVALID_SOCKET)
856
		return;
857
	pthread_mutex_lock(&session->outbuf_write);		/* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */
858
859
860
	pthread_mutex_unlock(&session->outbuf_write);
}

deuce's avatar
deuce committed
861
862
863
864
865
866
867
868
/**************************************************/
/* 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 */
/**************************************************/
869
870
static void close_request(http_session_t * session)
{
871
	time_t		now;
872
	int			i;
873

874
	if(session->req.write_chunked) {
875
		drain_outbuf(session);
876
877
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
878
		if(session->req.dynamic==IS_SSJS)
879
			ssjs_send_headers(session,FALSE);
880
881
		else
			/* Non-ssjs isn't capable of generating headers during execution */
882
			writebuf(session, newline, 2);
883
884
	}

885
886
887
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

888
889
890
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
891
		listPushNode(&log_list,session->req.ld);
892
893
		session->req.ld=NULL;
	}
894

895
896
897
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
898
	FREE_AND_NULL(session->req.post_data);
899
900
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
901
	FREE_AND_NULL(session->req.auth_list);
902
	FREE_AND_NULL(session->req.realm);
903
	FREE_AND_NULL(session->req.digest_realm);
904

905
	FREE_AND_NULL(session->req.auth_list);
906
907
908
909
910
911
	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);

912
913
914
915
	/*
	 * This causes all active http_session_threads to terminate.
	 */
	if((!session->req.keep_alive) || terminate_server) {
916
		drain_outbuf(session);
917
		close_socket(&session->socket);
918
	}
919
920
921
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
922
	if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
923
		JS_BEGINREQUEST(session->js_cx);
deuce's avatar
deuce committed
924
		JS_GC(session->js_cx);
925
		JS_ENDREQUEST(session->js_cx);
deuce's avatar
deuce committed
926
	}
deuce's avatar
deuce committed
927
928
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
929

930
931
932
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

933
934
935
936
937
938
	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]);
		}
939
940
	}

941
	memset(&session->req,0,sizeof(session->req));
942
943
944
945
}

static int get_header_type(char *header)
{
946
	int i;
947
948
949
950
951
952
953
954
	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
955
/* Opposite of get_header_type() */
956
957
static char *get_header(int id) 
{
958
	int i;
959
960
	if(headers[id].id==id)
		return(headers[id].text);
961
962
963
964
965
966
967
968
969

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

970
971
static const char* unknown_mime_type="application/octet-stream";

972
static const char* get_mime_type(char *ext)
973
974
975
{
	uint i;

976
	if(ext==NULL || mime_types==NULL)
977
978
		return(unknown_mime_type);

979
	for(i=0;mime_types[i]!=NULL;i++)
980
		if(stricmp(ext+1,mime_types[i]->name)==0)
981
			return(mime_types[i]->value);
982
983

	return(unknown_mime_type);
984
985
}

986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
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
1009
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
		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);
}

1024
1025
/* 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) {
1026
	size_t dstlen,appendlen;
1027
1028
1029
1030
1031
1032
1033
1034
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
1035
1036
1037
1038
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
1039
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
1040
{
1041
	int		ret;
1042
	BOOL	send_file=TRUE;
1043
	time_t	ti;
1044
	size_t	idx;
1045
	const char	*status_line;
1046
	struct stat	stats;
1047
	struct tm	tm;
1048
	char	*headers;
1049
	char	header[MAX_REQUEST_LINE+1];
1050

deuce's avatar
deuce committed
1051
1052
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
1053
		return(FALSE);
deuce's avatar
deuce committed
1054
	}
1055
1056
1057
	lprintf(LOG_DEBUG,"%04d Request resolved to: %s"
		,session->socket,session->req.physical_path);
	if(session->http_ver <= HTTP_0_9) {
deuce's avatar