websrvr.c 161 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

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
static scfg_t	scfg;
99
static volatile BOOL	http_logging_thread_running=FALSE;
100
static protected_int32_t active_clients;
101
102
103
104
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;
105
static SOCKET	server_socket=INVALID_SOCKET;
106
static char		revision[16];
107
108
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
109
static char		temp_dir[MAX_PATH+1];
110
static char		cgi_dir[MAX_PATH+1];
111
static char		cgi_env_ini[MAX_PATH+1];
112
static char		default_auth_list[MAX_PATH+1];
113
114
static volatile	time_t	uptime=0;
static volatile	ulong	served=0;
115
static web_startup_t* startup=NULL;
116
static js_server_props_t js_server_props;
117
118
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
119
static str_list_t cgi_env;
120
static volatile ulong 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
	/* webctrl.ini overrides */
225
226
	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
{
	va_list argptr;
	char sbuf[1024];

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

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

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

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

506
    return(startup->lputs(startup->cbdata,level,sbuf));
507
508
}

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

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

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

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

579
580
581
#ifdef _WINSOCKAPI_

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

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

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

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

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
602
#define SOCKLIB_DESC NULL
603
604
605
606
607
608

#endif

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

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

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

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

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

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

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

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

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

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

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

	len=strlen(str);
700
	return(writebuf(session,str,len));
701
702
}

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

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

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

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

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

		sockets++;
	}
	return(sock);
}

821
static int close_socket(SOCKET *sock)
822
823
824
{
	int		result;

825
	if(sock==NULL || *sock==INVALID_SOCKET)
826
827
		return(-1);

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

	return(result);
}

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

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

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

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

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

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

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

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

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

932
933
934
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

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

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

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

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

972
973
static const char* unknown_mime_type="application/octet-stream";

974
static const char* get_mime_type(char *ext)
975
976
977
{
	uint i;

978
	if(ext==NULL || mime_types==NULL)
979
980
		return(unknown_mime_type);

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

	return(unknown_mime_type);
986
987
}

988
static char* get_cgi_handler(const char* fname)
989
990
991
992
{
	char*	ext;
	size_t	i;

993
994
	if(cgi_handlers==NULL || (ext=getfext(fname))==NULL)
		return(NULL);
995
	for(i=0;cgi_handlers[i]!=NULL;i++) {
996
997
		if(stricmp(cgi_handlers[i]->name, ext+1)==0)
			return(cgi_handlers[i]->value);
998
	}
999
	return(NULL);
1000
1001
1002
1003
1004
1005
}

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

deuce's avatar
deuce committed
1006
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
		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);
}

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

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