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

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

51
52
//#define ONE_JS_RUNTIME

deuce's avatar
deuce committed
53
/* Headers for CGI stuff */
54
55
#if defined(__unix__)
	#include <sys/wait.h>		/* waitpid() */
rswindell's avatar
rswindell committed
56
57
	#include <sys/types.h>
	#include <signal.h>			/* kill() */
58
59
#endif

60
#ifndef JAVASCRIPT
61
#define JAVASCRIPT
62
63
#endif

64
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
65
#include "sbbs.h"
66
#include "sbbsdefs.h"
67
#include "sockwrap.h"		/* sendfilesocket() */
68
#include "threadwrap.h"
69
#include "semwrap.h"
70
#include "websrvr.h"
deuce's avatar
deuce committed
71
#include "base64.h"
72
#include "md5.h"
73
#include "js_rtpool.h"
74
#include "js_request.h"
75
#include "xpmap.h"
76
#include "xpprintf.h"
77

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

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

97
98
99
enum {
	 CLEANUP_SSJS_TMP_FILE
	,CLEANUP_POST_DATA
100
	,MAX_CLEANUPS
101
};
102

103
static scfg_t	scfg;
104
static volatile BOOL	http_logging_thread_running=FALSE;
105
static protected_uint32_t active_clients;
106
static protected_uint32_t thread_count;
107
108
109
static volatile ulong	sockets=0;
static volatile BOOL	terminate_server=FALSE;
static volatile BOOL	terminate_http_logging_thread=FALSE;
110
static SOCKET	server_socket=INVALID_SOCKET;
111
static char		revision[16];
112
113
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
114
static char		temp_dir[MAX_PATH+1];
115
static char		cgi_dir[MAX_PATH+1];
116
static char		cgi_env_ini[MAX_PATH+1];
117
static char		default_auth_list[MAX_PATH+1];
118
119
static volatile	time_t	uptime=0;
static volatile	ulong	served=0;
120
static web_startup_t* startup=NULL;
121
static js_server_props_t js_server_props;
122
123
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
124
static str_list_t cgi_env;
125

126
static named_string_t** mime_types;
127
128
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
129

130
/* Logging stuff */
131
link_list_t	log_list;
132
133
134
135
136
137
138
struct log_data {
	char	*hostname;
	char	*ident;
	char	*user;
	char	*request;
	char	*referrer;
	char	*agent;
139
	char	*vhost;
140
141
142
143
144
	int		status;
	unsigned int	size;
	struct tm completed;
};

145
146
147
148
149
150
enum auth_type {
	 AUTHENTICATION_UNKNOWN
	,AUTHENTICATION_BASIC
	,AUTHENTICATION_DIGEST
};

151
152
153
154
155
156
157
char *auth_type_names[4] = {
	 "Unknown"
	,"Basic"
	,"Digest"
	,NULL
};

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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;
182
	unsigned char	digest[16];		/* MD5 digest */
183
	BOOL			stale;
184
185
} authentication_request_t;

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

217
218
219
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
220
221
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
222

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

229
	/* webctrl.ini overrides */
230
231
	char	*error_dir;
	char	*cgi_dir;
232
	char	*auth_list;
233
	char	*realm;
234
	char	*digest_realm;
235
236
237
} http_request_t;

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

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

264
265
266
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
267
268
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
269

270
271
	/* Client info */
	client_t		client;
deuce's avatar
deuce committed
272
273
274

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
275
276
277
278
279
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
292
293
	,HTTP_POST
	,HTTP_OPTIONS
294
};
295

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

304
enum {
305
306
307
308
309
310
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

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

375
376
377
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"};

378
static void respond(http_session_t * session);
379
static BOOL js_setup(http_session_t* session);
380
static char *find_last_slash(char *str);
381
static BOOL check_extra_path(http_session_t * session);
382
static BOOL exec_ssjs(http_session_t* session, char* script);
383
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
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
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;

431
432
        tm2 = gmtime(&t);	/* why not use gmtime_r instead? */
        if (tm2 == NULL || (t2 = sub_mkgmt(tm2)) == (time_t) -1)
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
                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);
}
486

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

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

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

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

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

511
    return(startup->lputs(startup->cbdata,level,sbuf));
512
513
}

514
515
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
516
517
	size_t	sent=0;
	size_t	avail;
518

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

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

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

584
585
586
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
587
#define SOCKLIB_DESC WSAData.szDescription
588
589
590
591
592
593
594
static BOOL WSAInitialized=FALSE;

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

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

600
    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
601
602
603
604
605
606
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
607
#define SOCKLIB_DESC NULL
608
609
610
611
612
613

#endif

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

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
620
		startup->clients(startup->cbdata,protected_uint32_value(active_clients));
621
622
623
624
625
}

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

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

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

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

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

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

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
deuce's avatar
deuce committed
666
	p=xp_asprintf("%s=%s",newname,value);
667
668
669
670
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
671
	strListPush(&session->req.cgi_env,p);
deuce's avatar
deuce committed
672
	free(p);
673
674
}

deuce's avatar
deuce committed
675
676
677
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
678
679
680
681
682
683
684
static void init_enviro(http_session_t *session)  {
	char	str[128];

	add_env(session,"SERVER_SOFTWARE",VERSION_NOTICE);
	sprintf(str,"%d",startup->port);
	add_env(session,"SERVER_PORT",str);
	add_env(session,"GATEWAY_INTERFACE","CGI/1.1");
685
	if(!strcmp(session->host_name,session->host_ip))
686
687
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
688
	add_env(session,"REQUEST_URI",session->req.request_line);
689
690
}

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

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

deuce's avatar
deuce committed
703
704
705
706
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
707
708
709
710
711
712
713
714
715
716
static int getmonth(char *mon)
{
	int	i;
	for(i=0;i<12;i++)
		if(!stricmp(mon,months[i]))
			return(i);

	return 0;
}

deuce's avatar
deuce committed
717
718
719
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
720
721
722
static time_t decode_date(char *date)
{
	struct	tm	ti;
723
	char	*token;
724
	char	*last;
725
	time_t	t;
726
727
728
729
730
731
732
733
734

	ti.tm_sec=0;		/* seconds (0 - 60) */
	ti.tm_min=0;		/* minutes (0 - 59) */
	ti.tm_hour=0;		/* hours (0 - 23) */
	ti.tm_mday=1;		/* day of month (1 - 31) */
	ti.tm_mon=0;		/* month of year (0 - 11) */
	ti.tm_year=0;		/* year - 1900 */
	ti.tm_isdst=0;		/* is summer time in effect? */

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

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

static SOCKET open_socket(int type)
{
	char	error[256];
	SOCKET	sock;

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

		sockets++;
	}
	return(sock);
}

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

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

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

	return(result);
}

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

deuce's avatar
deuce committed
863
864
865
866
867
868
869
870
/**************************************************/
/* End of a single request...					  */
/* This is called at the end of EVERY request	  */
/*  Log the request       						  */
/*  Free request-specific data ie: dynamic stuff  */
/*  Close socket unless it's being kept alive     */
/*   If the socket is closed, the session is done */
/**************************************************/
871
872
static void close_request(http_session_t * session)
{
873
	time_t		now;
874
	int			i;
875

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

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

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

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

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

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

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

937
938
939
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

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

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

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

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

977
978
static const char* unknown_mime_type="application/octet-stream";

979
static const char* get_mime_type(char *ext)
980
981
982
{
	uint i;

983
	if(ext==NULL || mime_types==NULL)
984
985
		return(unknown_mime_type);

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

	return(unknown_mime_type);
991
992
}

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

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

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

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

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

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

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

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

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

1110
1111
1112
1113
		/* 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);
1114

1115
		safecat(headers,header,MAX_HEADERS_SIZE);
1116

1117
1118
1119
1120
1121
1122
1123
1124
		/* 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);
1125
		safecat(headers,header,MAX_HEADERS_SIZE);
1126
1127
1128
1129
1130
1131
1132
1133
		if(session->req.keep_alive) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
1134

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

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

1153
1154
1155
1156
1157
		if(session->req.send_location) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
			safecat(headers,header,MAX_HEADERS_SIZE);
		}

1158
		if(chunked) {