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

/* Synchronet Web Server */

/* $Id$ */

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

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

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

55
#ifndef JAVASCRIPT
56
#define JAVASCRIPT
57
58
#endif

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

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

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

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

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

119
static named_string_t** mime_types;
120
121
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
122

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

138
139
140
141
142
143
enum auth_type {
	 AUTHENTICATION_UNKNOWN
	,AUTHENTICATION_BASIC
	,AUTHENTICATION_DIGEST
};

144
145
146
147
148
149
150
char *auth_type_names[4] = {
	 "Unknown"
	,"Basic"
	,"Digest"
	,NULL
};

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

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

209
210
211
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
212
213
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
214

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

	/* webconfig.ini overrides */
	char	*error_dir;
	char	*cgi_dir;
224
	char	*auth_list;
225
	char	*realm;
226
	char	*digest_realm;
227
228
229
} http_request_t;

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

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

256
257
258
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
259
260
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
261

262
263
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
264
265
266

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
267
268
269
270
271
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
284
285
	,HTTP_POST
	,HTTP_OPTIONS
286
};
287

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

296
enum {
297
298
299
300
301
302
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

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

367
368
369
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"};

370
static void respond(http_session_t * session);
371
static BOOL js_setup(http_session_t* session);
372
static char *find_last_slash(char *str);
373
static BOOL check_extra_path(http_session_t * session);
374
static BOOL exec_ssjs(http_session_t* session, char* script);
375
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
376

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
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
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);
}
478

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

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

	va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
491
    return(startup->lputs(startup->cbdata,level,sbuf));
492
493
}

494
495
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
496
497
	size_t	sent=0;
	size_t	avail;
498

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

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

520
	while(sent<len && *sock!=INVALID_SOCKET) {
521
522
523
524
525
		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;
526
527
		sel=select(*sock+1,NULL,&wr_set,NULL,&tv);
		switch(sel) {
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
			case 1:
				result=sendsocket(*sock,buf+sent,len-sent);
				if(result==SOCKET_ERROR) {
					if(ERROR_VALUE==ECONNRESET) 
						lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",*sock);
					else if(ERROR_VALUE==ECONNABORTED) 
						lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",*sock);
					else
						lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",*sock,ERROR_VALUE);
					if(failed)
						*failed=TRUE;
					return(sent);
				}
				break;
			case 0:
543
				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",*sock);
544
545
546
547
				if(failed)
					*failed=TRUE;
				return(sent);
			case -1:
548
				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",*sock,ERROR_VALUE);
549
550
551
				if(failed)
					*failed=TRUE;
				return(sent);
552
553
554
555
556
557
558
559
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

560
561
562
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
563
#define SOCKLIB_DESC WSAData.szDescription
564
565
566
567
568
569
570
static BOOL WSAInitialized=FALSE;

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

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

576
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
577
578
579
580
581
582
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
583
#define SOCKLIB_DESC NULL
584
585
586
587
588
589

#endif

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

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
596
		startup->clients(startup->cbdata,active_clients);
597
598
599
600
601
}

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

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

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

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
623
		startup->thread_up(startup->cbdata,FALSE, FALSE);
624
625
}

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

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

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

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

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

	len=strlen(str);
681
	return(writebuf(session,str,len));
682
683
}

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

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

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

781
	t=time_gm(&ti);
782
	return(t);
783
784
785
786
787
788
789
790
791
}

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

		sockets++;
	}
	return(sock);
}

802
static int close_socket(SOCKET *sock)
803
804
805
{
	int		result;

806
	if(sock==NULL || *sock==INVALID_SOCKET)
807
808
		return(-1);

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

	return(result);
}

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

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

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

868
869
870
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

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

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

888
	FREE_AND_NULL(session->req.auth_list);
889
890
891
892
893
894
	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);

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

deuce's avatar
deuce committed
905
906
907
	if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
		JS_GC(session->js_cx);
	}
deuce's avatar
deuce committed
908
909
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
910

911
912
913
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

914
915
916
917
918
919
	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]);
		}
920
921
	}

922
	memset(&session->req,0,sizeof(session->req));
923
924
925
926
}

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

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

951
952
static const char* unknown_mime_type="application/octet-stream";

953
static const char* get_mime_type(char *ext)
954
955
956
{
	uint i;

957
	if(ext==NULL || mime_types==NULL)
958
959
		return(unknown_mime_type);

960
	for(i=0;mime_types[i]!=NULL;i++)
961
		if(stricmp(ext+1,mime_types[i]->name)==0)
962
			return(mime_types[i]->value);
963
964

	return(unknown_mime_type);
965
966
}

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

1005
1006
/* 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) {
1007
	size_t dstlen,appendlen;
1008
1009
1010
1011
1012
1013
1014
1015
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

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

deuce's avatar
deuce committed
1032
1033
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
1034
		return(FALSE);
deuce's avatar
deuce committed
1035
	}
1036
1037
1038
	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
1039
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
1040
1041
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
1042
		return(TRUE);
1043
	}
1044
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1045
1046
1047
1048
1049
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
1050
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
1051
		session->req.sent_headers=TRUE;
1052
1053
1054
1055
1056
1057
1058
1059
1060
		status_line=status;
		ret=stat(session->req.physical_path,&stats);
		if(session->req.method==HTTP_OPTIONS)
			ret=-1;
		if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
			status_line="304 Not Modified";
			ret=-1;
			send_file=FALSE;
		}
deuce's avatar
deuce committed
1061
1062
1063
1064
1065
		if(!ret && session->req.if_range && (stats