websrvr.c 163 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 2011 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
#include "xpmap.h"
73

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

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

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

99
static scfg_t	scfg;
100
static volatile BOOL	http_logging_thread_running=FALSE;
101
static protected_int32_t active_clients;
102
103
104
105
static volatile ulong	sockets=0;
static volatile BOOL	terminate_server=FALSE;
static volatile BOOL	terminate_http_logging_thread=FALSE;
static volatile	ulong	thread_count=0;
106
static SOCKET	server_socket=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
115
static volatile	time_t	uptime=0;
static volatile	ulong	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 str_list_t cgi_env;
121
static volatile ulong 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
	char *		post_data;
199
	struct xpmapping *post_map;
200
	size_t		post_len;
201
	int			dynamic;
202
	char		xjs_handler[MAX_PATH+1];
203
	struct log_data	*ld;
204
	char		request_line[MAX_REQUEST_LINE+1];
205
	BOOL		finished;				/* Done processing request. */
206
207
	BOOL		read_chunked;
	BOOL		write_chunked;
208
209
	long		range_start;
	long		range_end;
210
	BOOL		accept_ranges;
deuce's avatar
deuce committed
211
	time_t		if_range;
212
	BOOL		path_info_index;
213

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

494
	if(level <= LOG_ERR) {
495
		errorlog(&scfg,startup==NULL ? NULL:startup->host_name, sbuf);
496
497
498
		if(startup!=NULL && startup->errormsg!=NULL)
			startup->errormsg(startup->cbdata,level,sbuf);
	}
499
500
501
502
503
504
505
506
507

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

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

508
    return(startup->lputs(startup->cbdata,level,sbuf));
509
510
}

511
512
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
513
514
	size_t	sent=0;
	size_t	avail;
515

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

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

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

581
582
583
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
584
#define SOCKLIB_DESC WSAData.szDescription
585
586
587
588
589
590
591
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
592
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
593
594
595
596
		WSAInitialized=TRUE;
		return (TRUE);
	}

597
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
598
599
600
601
602
603
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
604
#define SOCKLIB_DESC NULL
605
606
607
608
609
610

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
611
	    startup->status(startup->cbdata,str);
612
613
614
615
616
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
617
		startup->clients(startup->cbdata,active_clients.value);
618
619
620
621
622
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
623
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
624
625
626
627
628
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
629
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
630
631
632
633
634
635
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
636
		startup->thread_up(startup->cbdata,TRUE, setuid);
637
638
639
640
641
642
643
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
644
		startup->thread_up(startup->cbdata,FALSE, FALSE);
645
646
}

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

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

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

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

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

	len=strlen(str);
702
	return(writebuf(session,str,len));
703
704
}

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

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

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

802
	t=time_gm(&ti);
803
	return(t);
804
805
806
807
808
809
810
811
812
}

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

		sockets++;
	}
	return(sock);
}

823
static int close_socket(SOCKET *sock)
824
825
826
{
	int		result;

827
	if(sock==NULL || *sock==INVALID_SOCKET)
828
829
		return(-1);

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

	return(result);
}

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

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

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

889
890
891
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

892
893
894
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
895
		listPushNode(&log_list,session->req.ld);
896
897
		session->req.ld=NULL;
	}
898

899
900
901
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
902
903
904
905
906
	if(session->req.post_map != NULL) {
		xpunmap(session->req.post_map);
		session->req.post_data=NULL;
		session->req.post_map=NULL;
	}
907
	FREE_AND_NULL(session->req.post_data);
908
909
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
910
	FREE_AND_NULL(session->req.auth_list);
911
	FREE_AND_NULL(session->req.realm);
912
	FREE_AND_NULL(session->req.digest_realm);
913

914
	FREE_AND_NULL(session->req.auth_list);
915
916
917
918
919
920
	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);

921
922
923
924
	/*
	 * This causes all active http_session_threads to terminate.
	 */
	if((!session->req.keep_alive) || terminate_server) {
925
		drain_outbuf(session);
926
		close_socket(&session->socket);
927
	}
928
929
930
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
931
	if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
932
		JS_BEGINREQUEST(session->js_cx);
deuce's avatar
deuce committed
933
		JS_GC(session->js_cx);
934
		JS_ENDREQUEST(session->js_cx);
deuce's avatar
deuce committed
935
	}
deuce's avatar
deuce committed
936
937
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
938

939
940
941
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

942
943
944
945
946
947
	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]);
		}
948
949
	}

950
	memset(&session->req,0,sizeof(session->req));
951
952
953
954
}

static int get_header_type(char *header)
{
955
	int i;
956
957
958
959
960
961
962
963
	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
964
/* Opposite of get_header_type() */
965
966
static char *get_header(int id) 
{
967
	int i;
968
969
	if(headers[id].id==id)
		return(headers[id].text);
970
971
972
973
974
975
976
977
978

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

979
980
static const char* unknown_mime_type="application/octet-stream";

981
static const char* get_mime_type(char *ext)
982
983
984
{
	uint i;

985
	if(ext==NULL || mime_types==NULL)
986
987
		return(unknown_mime_type);

988
	for(i=0;mime_types[i]!=NULL;i++)
989
		if(stricmp(ext+1,mime_types[i]->name)==0)
990
			return(mime_types[i]->value);
991
992

	return(unknown_mime_type);
993
994
}

995
static char* get_cgi_handler(const char* fname)
996
997
998
999
{
	char*	ext;
	size_t	i;

1000
1001
	if(cgi_handlers==NULL || (ext=getfext(fname))==NULL)
		return(NULL);
1002
	for(i=0;cgi_handlers[i]!=NULL;i++) {
1003
1004
		if(stricmp(cgi_handlers[i]->name, ext+1)==0)
			return(cgi_handlers[i]->value);
1005
	}
1006
	return(NULL);
1007
1008
1009
1010
1011
1012
}

static BOOL get_xjs_handler(char* ext, http_session_t* session)
{
	size_t	i;

deuce's avatar
deuce committed
1013
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
		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);
}

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

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

deuce's avatar
deuce committed
1057
1058
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
1059
		return(FALSE);
deuce's avatar
deuce committed
1060
	}
1061
1062
1063
	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
1064
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
1065
1066
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
1067
		return(TRUE);
1068
	}
1069
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1070
1071
1072
1073
1074
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
1075
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
1076
		session->req.sent_headers=TRUE;
1077
1078
1079
1080
1081
1082
1083
1084
		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;
1085
			send_entity=FALSE;
1086
		}
deuce's avatar
deuce committed
1087
1088
1089
1090
1091
		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;
		}
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
		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;
		}
1102

1103
		stat_code=atoi(status_line);
1104
		if(session->req.ld!=NULL)
1105
			session->req.ld->status=stat_code;
1106

1107
		if(stat_code==304 || stat_code==204 || (stat_code >= 100 && stat_code<=199)) {
1108
1109
1110
1111
			send_file=FALSE;
			chunked=FALSE;
		}

1112
1113
1114
1115
		/* 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);
1116

1117
		safecat(headers,header,MAX_HEADERS_SIZE);
1118

1119
1120
1121
1122
1123
1124
1125
1126
		/* 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);
1127
		safecat(headers,header,MAX_HEADERS_SIZE);
1128
1129
1130
1131
1132
1133
1134
1135
		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);
		}
1136

1137
1138
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
1139
1140
		safecat(headers,header,MAX_HEADERS_SIZE);

1141
1142
1143
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
1144
			safecat(headers,header,MAX_HEADERS_SIZE);
1145
1146
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"none");
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1147
		}
1148
1149
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
1150
			safecat(headers,header,MAX_HEADERS_SIZE);
1151
1152
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"bytes");
			safecat(headers,header,MAX_HEADERS_SIZE);
1153
		}
1154

1155
1156
1157
1158
1159
		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);
		}

1160
		if(chunked) {
1161
1162
1163
1164
1165
			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 */
1166
1167
1168
1169
		if(send_entity) {
			if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
				if(ret)  {
					safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");