websrvr.c 152 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 2006 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
41
42
/*
 * General notes: (ToDo stuff)
 *
 * Should support RFC2617 Digest auth.
 *
43
 * Support the ident protocol... the standard log format supports it.
44
 *
deuce's avatar
deuce committed
45
46
47
 * 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.
48
49
 */

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

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

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

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

96
97
static scfg_t	scfg;
static BOOL		scfg_reloaded=TRUE;
98
static BOOL		http_logging_thread_running=FALSE;
deuce's avatar
deuce committed
99
static DWORD	active_clients=0;
100
static ulong	sockets=0;
101
static BOOL		terminate_server=FALSE;
102
static BOOL		terminate_http_logging_thread=FALSE;
103
104
105
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
static char		revision[16];
106
107
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
108
static char		temp_dir[MAX_PATH+1];
109
static char		cgi_dir[MAX_PATH+1];
110
static char		cgi_env_ini[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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
enum auth_type {
	 AUTHENTICATION_UNKNOWN
	,AUTHENTICATION_BASIC
	,AUTHENTICATION_DIGEST
};

enum algorithm {
	 ALGORITHM_UNKNOWN
	,ALGORITHM_MD5
	,ALGORITHM_MD5_SESS
};

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

typedef struct {
	/* Realm and domain are specified in webctl.ini */
	char			nonce[128];
	BOOL			stale;
} authentication_response_t;

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;
	unsigned char			digest[16];		/* MD5 digest */
} authentication_request_t;

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

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

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

	/* webconfig.ini overrides */
	char	*error_dir;
	char	*cgi_dir;
	char	*realm;
223
224
225
} http_request_t;

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

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
243
244
	JSObject*		js_query;
	JSObject*		js_header;
245
	JSObject*		js_cookie;
246
	JSObject*		js_request;
247
	js_branch_t		js_branch;
deuce's avatar
deuce committed
248
	subscan_t		*subscan;
249

250
251
252
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
253
254
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
255

256
257
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
258
259
260

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
261
262
263
264
265
} http_session_t;

enum { 
	 HTTP_0_9
	,HTTP_1_0
266
	,HTTP_1_1
267
268
269
270
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
271
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
272
	,NULL	/* terminator */
273
274
275
276
277
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
278
279
	,HTTP_POST
	,HTTP_OPTIONS
280
};
281

rswindell's avatar
rswindell committed
282
283
284
static char* methods[] = {
	 "HEAD"
	,"GET"
285
	,"POST"
286
	,"OPTIONS"
rswindell's avatar
rswindell committed
287
288
	,NULL	/* terminator */
};
289

290
enum {
291
292
293
294
295
296
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

353
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
354
enum  {
355
	 NO_LOCATION
356
	,MOVED_PERM
357
	,MOVED_TEMP
358
	,MOVED_STAT
359
360
};

361
362
363
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"};

364
static void respond(http_session_t * session);
365
static BOOL js_setup(http_session_t* session);
366
static char *find_last_slash(char *str);
367
static BOOL check_extra_path(http_session_t * session);
368
static BOOL exec_ssjs(http_session_t* session, char* script);
369
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
370

371
372
373
374
375
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
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);
}
472

473
static int lprintf(int level, char *fmt, ...)
474
475
476
477
478
479
480
481
482
483
484
{
	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);
485
    return(startup->lputs(startup->cbdata,level,sbuf));
486
487
}

488
489
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
490
491
	size_t	sent=0;
	size_t	avail;
492

493
	while(sent < len) {
494
		avail=RingBufFree(&session->outbuf);
deuce's avatar
deuce committed
495
		if(!avail) {
496
			SLEEP(1);
deuce's avatar
deuce committed
497
498
			continue;
		}
499
500
		if(avail > len-sent)
			avail=len-sent;
501
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
502
503
504
505
	}
	return(sent);
}

506
static int sock_sendbuf(SOCKET *sock, const char *buf, size_t len, BOOL *failed)
507
508
509
{
	size_t sent=0;
	int result;
510
	int sel;
511
512
	fd_set	wr_set;
	struct timeval tv;
513

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

554
555
556
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
557
#define SOCKLIB_DESC WSAData.szDescription
558
559
560
561
562
563
564
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
565
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
566
567
568
569
		WSAInitialized=TRUE;
		return (TRUE);
	}

570
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
571
572
573
574
575
576
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
577
#define SOCKLIB_DESC NULL
578
579
580
581
582
583

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
584
	    startup->status(startup->cbdata,str);
585
586
587
588
589
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
590
		startup->clients(startup->cbdata,active_clients);
591
592
593
594
595
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
596
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
597
598
599
600
601
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
602
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
603
604
605
606
607
608
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
609
		startup->thread_up(startup->cbdata,TRUE, setuid);
610
611
612
613
614
615
616
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
617
		startup->thread_up(startup->cbdata,FALSE, FALSE);
618
619
}

deuce's avatar
deuce committed
620
621
622
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
623
static void add_env(http_session_t *session, const char *name,const char *value)  {
624
	char	newname[129];
625
	char	*p;
626

627
	if(name==NULL || value==NULL)  {
628
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
629
630
631
632
633
634
635
636
637
		return;
	}
	SAFECOPY(newname,name);

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

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

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

	len=strlen(str);
675
	return(writebuf(session,str,len));
676
677
}

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

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

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

775
	t=time_gm(&ti);
776
	return(t);
777
778
779
780
781
782
783
784
785
}

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) 
786
		startup->socket_open(startup->cbdata,TRUE);
787
	if(sock!=INVALID_SOCKET) {
788
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
789
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
790
791
792
793
794
795

		sockets++;
	}
	return(sock);
}

796
static int close_socket(SOCKET *sock)
797
798
799
{
	int		result;

800
	if(sock==NULL || *sock==INVALID_SOCKET)
801
802
		return(-1);

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

	return(result);
}

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

deuce's avatar
deuce committed
838
839
840
841
842
843
844
845
/**************************************************/
/* 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 */
/**************************************************/
846
847
static void close_request(http_session_t * session)
{
848
	time_t		now;
849
	int			i;
850

851
	if(session->req.write_chunked) {
852
		drain_outbuf(session);
853
854
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
855
		if(session->req.dynamic==IS_SSJS)
856
			ssjs_send_headers(session,FALSE);
857
858
		else
			/* Non-ssjs isn't capable of generating headers during execution */
859
			writebuf(session, newline, 2);
860
861
	}

862
863
864
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

865
866
867
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
868
		listPushNode(&log_list,session->req.ld);
869
870
		session->req.ld=NULL;
	}
871

872
873
874
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
875
	FREE_AND_NULL(session->req.post_data);
876
877
878
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
	FREE_AND_NULL(session->req.realm);
879
880
881
882
883
884
885

	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);

886
887
888
889
	/*
	 * This causes all active http_session_threads to terminate.
	 */
	if((!session->req.keep_alive) || terminate_server) {
890
		drain_outbuf(session);
891
		close_socket(&session->socket);
892
	}
893
894
895
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
896
897
898
	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
899
900
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
901

902
903
904
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

905
906
907
908
909
910
	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]);
		}
911
912
	}

913
	memset(&session->req,0,sizeof(session->req));
914
915
916
917
}

static int get_header_type(char *header)
{
918
	int i;
919
920
921
922
923
924
925
926
	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
927
/* Opposite of get_header_type() */
928
929
static char *get_header(int id) 
{
930
	int i;
931
932
	if(headers[id].id==id)
		return(headers[id].text);
933
934
935
936
937
938
939
940
941

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

942
943
static const char* unknown_mime_type="application/octet-stream";

944
static const char* get_mime_type(char *ext)
945
946
947
{
	uint i;

948
	if(ext==NULL || mime_types==NULL)
949
950
		return(unknown_mime_type);

951
	for(i=0;mime_types[i]!=NULL;i++)
952
		if(stricmp(ext+1,mime_types[i]->name)==0)
953
			return(mime_types[i]->value);
954
955

	return(unknown_mime_type);
956
957
}

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
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
981
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
982
983
984
985
986
987
988
989
990
991
992
993
994
995
		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);
}

996
997
/* 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) {
998
	size_t dstlen,appendlen;
999
1000
1001
1002
1003
1004
1005
1006
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
1007
1008
1009
1010
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
1011
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
1012
{
1013
	int		ret;
1014
	BOOL	send_file=TRUE;
1015
	time_t	ti;
1016
	size_t	idx;
1017
	const char	*status_line;
1018
	struct stat	stats;
1019
	struct tm	tm;
1020
	char	*headers;
1021
	char	header[MAX_REQUEST_LINE+1];
1022

deuce's avatar
deuce committed
1023
1024
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
1025
		return(FALSE);
deuce's avatar
deuce committed
1026
	}
1027
1028
1029
	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
1030
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
1031
1032
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
1033
		return(TRUE);
1034
	}
1035
	headers=alloca(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
1036
1037
1038
1039
1040
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
1041
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
1042
		session->req.sent_headers=TRUE;
1043
1044
1045
1046
1047
1048
1049
1050
1051
		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
1052
1053
1054
1055
1056
		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;
		}
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
		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;
		}
1067

1068
1069
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
1070

1071
1072
1073
1074
		/* 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);
1075

1076
		safecat(headers,header,MAX_HEADERS_SIZE);
1077

1078
1079
1080
1081
1082
1083
1084
1085
		/* 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);
1086
		safecat(headers,header,MAX_HEADERS_SIZE);