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
103
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
static char		revision[16];
104
105
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
106
static char		temp_dir[MAX_PATH+1];
107
static char		cgi_dir[MAX_PATH+1];
108
static char		cgi_env_ini[MAX_PATH+1];
109
static char		default_auth_list[MAX_PATH+1];
110
static time_t	uptime=0;
111
static DWORD	served=0;
112
static web_startup_t* startup=NULL;
113
static js_server_props_t js_server_props;
114
115
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
116
static int session_threads=0;
117

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

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

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

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

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

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

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

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

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

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

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

253
254
255
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
256
257
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
258

259
260
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
261
262
263

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
264
265
266
267
268
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
281
282
	,HTTP_POST
	,HTTP_OPTIONS
283
};
284

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

293
enum {
294
295
296
297
298
299
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

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

364
365
366
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"};

367
static void respond(http_session_t * session);
368
static BOOL js_setup(http_session_t* session);
369
static char *find_last_slash(char *str);
370
static BOOL check_extra_path(http_session_t * session);
371
static BOOL exec_ssjs(http_session_t* session, char* script);
372
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
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
472
473
474
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);
}
475

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

491
492
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
493
494
	size_t	sent=0;
	size_t	avail;
495

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

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

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

557
558
559
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
560
#define SOCKLIB_DESC WSAData.szDescription
561
562
563
564
565
566
567
static BOOL WSAInitialized=FALSE;

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

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

573
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
574
575
576
577
578
579
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
580
#define SOCKLIB_DESC NULL
581
582
583
584
585
586

#endif

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

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
593
		startup->clients(startup->cbdata,active_clients);
594
595
596
597
598
}

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

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

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

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
620
		startup->thread_up(startup->cbdata,FALSE, FALSE);
621
622
}

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

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

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

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

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

	len=strlen(str);
678
	return(writebuf(session,str,len));
679
680
}

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

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

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

778
	t=time_gm(&ti);
779
	return(t);
780
781
782
783
784
785
786
787
788
}

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

		sockets++;
	}
	return(sock);
}

799
static int close_socket(SOCKET *sock)
800
801
802
{
	int		result;

803
	if(sock==NULL || *sock==INVALID_SOCKET)
804
805
		return(-1);

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

	return(result);
}

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

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

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

865
866
867
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

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

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

885
	FREE_AND_NULL(session->req.auth_list);
886
887
888
889
890
891
	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);

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

deuce's avatar
deuce committed
902
903
904
	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
905
906
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
907

908
909
910
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

911
912
913
914
915
916
	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]);
		}
917
918
	}

919
	memset(&session->req,0,sizeof(session->req));
920
921
922
923
}

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

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

948
949
static const char* unknown_mime_type="application/octet-stream";

950
static const char* get_mime_type(char *ext)
951
952
953
{
	uint i;

954
	if(ext==NULL || mime_types==NULL)
955
956
		return(unknown_mime_type);

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

	return(unknown_mime_type);
962
963
}

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

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

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

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