websrvr.c 125 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 2005 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
63
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
64
#include "threadwrap.h"
65
#include "semwrap.h"
66
#include "websrvr.h"
deuce's avatar
deuce committed
67
#include "base64.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
76
static const char*	error_404="404 Not Found";
static const char*	error_500="500 Internal Server Error";
77
static const char*	unknown="<unknown>";
78

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

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

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

114
static named_string_t** mime_types;
115
116
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
117

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

134
typedef struct  {
135
	int			method;
136
137
138
139
140
141
142
	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];
	char    	auth[128];				/* UserID:Password */
143
144
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
145
	int			send_location;
146
	const char*	mime_type;
147
	str_list_t	headers;
148
	char		status[MAX_REQUEST_LINE+1];
149
150
	char *		post_data;
	size_t		post_len;
151
	int			dynamic;
152
	char		xjs_handler[MAX_PATH+1];
153
	struct log_data	*ld;
154
	char		request_line[MAX_REQUEST_LINE+1];
155
	BOOL		finished;				/* Done processing request. */
156
157
	BOOL		read_chunked;
	BOOL		write_chunked;
158

159
160
161
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
162
163
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
164

165
166
	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;
167
	char		*cleanup_file[MAX_CLEANUPS];
168
169
	BOOL	sent_headers;
	BOOL	prev_write;
170
171
172
173
174

	/* webconfig.ini overrides */
	char	*error_dir;
	char	*cgi_dir;
	char	*realm;
175
176
177
} http_request_t;

typedef struct  {
178
179
	SOCKET			socket;
	SOCKADDR_IN		addr;
180
	http_request_t	req;
181
182
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
183
184
	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 */
185
186
187
	user_t			user;
	int				last_user_num;
	time_t			logon_time;
188
	char			username[LEN_NAME+1];
189
	int				last_js_user_num;
190
191
192
193
194

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
195
196
197
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
198
	js_branch_t		js_branch;
deuce's avatar
deuce committed
199
	subscan_t		*subscan;
200

201
202
203
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
204
205
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
206

207
208
	/* Client info */
	client_t		client;
209
210
211
212
213
} http_session_t;

enum { 
	 HTTP_0_9
	,HTTP_1_0
214
	,HTTP_1_1
215
216
217
218
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
219
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
220
	,NULL	/* terminator */
221
222
223
224
225
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
226
227
	,HTTP_POST
	,HTTP_OPTIONS
228
};
229

rswindell's avatar
rswindell committed
230
231
232
static char* methods[] = {
	 "HEAD"
	,"GET"
233
	,"POST"
234
	,"OPTIONS"
rswindell's avatar
rswindell committed
235
236
	,NULL	/* terminator */
};
237

238
enum {
239
240
241
242
243
244
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

245
enum { 
246
247
248
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
249
250
	,HEAD_LENGTH
	,HEAD_TYPE
251
252
253
254
255
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
256
257
258
259
260
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
261
262
	,HEAD_REFERER
	,HEAD_AGENT
263
	,HEAD_TRANSFER_ENCODING
264
265
266
267
268
269
};

static struct {
	int		id;
	char*	text;
} headers[] = {
270
271
272
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
273
274
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
275
276
277
278
279
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
280
281
282
283
284
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
285
286
	{ HEAD_REFERER,			"Referer"				},
	{ HEAD_AGENT,			"User-Agent"			},
287
	{ HEAD_TRANSFER_ENCODING,			"Transfer-Encoding"			},
288
	{ -1,					NULL /* terminator */	},
289
290
};

291
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
292
enum  {
293
	 NO_LOCATION
294
	,MOVED_PERM
295
	,MOVED_TEMP
296
	,MOVED_STAT
297
298
};

299
300
301
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"};

302
static void respond(http_session_t * session);
303
static BOOL js_setup(http_session_t* session);
304
static char *find_last_slash(char *str);
305
static BOOL check_extra_path(http_session_t * session);
306
static BOOL exec_ssjs(http_session_t* session, char* script);
307
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
308

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
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
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);
}
410

411
static int lprintf(int level, char *fmt, ...)
412
413
414
415
416
417
418
419
420
421
422
{
	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);
423
    return(startup->lputs(startup->cbdata,level,sbuf));
424
425
}

426
427
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
428
429
	size_t	sent=0;
	size_t	avail;
430
431
432

	while(!terminate_server && sent < len) {
		avail=RingBufFree(&session->outbuf);
deuce's avatar
deuce committed
433
		if(!avail) {
434
			SLEEP(1);
deuce's avatar
deuce committed
435
436
			continue;
		}
437
438
		if(avail > len-sent)
			avail=len-sent;
439
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
440
441
442
443
	}
	return(sent);
}

444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
static int sock_sendbuf(SOCKET sock, const char *buf, size_t len, BOOL *failed)
{
	size_t sent=0;
	int result;

	while(sent<len) {
		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);
			break;
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
	return(sent);
}

467
468
469
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
470
#define SOCKLIB_DESC WSAData.szDescription
471
472
473
474
475
476
477
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
478
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
479
480
481
482
		WSAInitialized=TRUE;
		return (TRUE);
	}

483
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
484
485
486
487
488
489
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
490
#define SOCKLIB_DESC NULL
491
492
493
494
495
496

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
497
	    startup->status(startup->cbdata,str);
498
499
500
501
502
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
503
		startup->clients(startup->cbdata,active_clients);
504
505
506
507
508
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
509
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
510
511
512
513
514
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
515
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
516
517
518
519
520
521
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
522
		startup->thread_up(startup->cbdata,TRUE, setuid);
523
524
525
526
527
528
529
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
530
		startup->thread_up(startup->cbdata,FALSE, FALSE);
531
532
}

deuce's avatar
deuce committed
533
534
535
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
536
static void add_env(http_session_t *session, const char *name,const char *value)  {
537
	char	newname[129];
538
	char	*p;
539

540
	if(name==NULL || value==NULL)  {
541
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
542
543
544
545
546
547
548
549
550
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
551
552
553
554
555
	p=(char *)malloc(strlen(name)+strlen(value)+2);
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
556
#if 0	/* this is way too verbose for every request */
557
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
558
#endif
559
	sprintf(p,"%s=%s",newname,value);
560
	strListPush(&session->req.cgi_env,p);
561
	free(p);
562
563
}

deuce's avatar
deuce committed
564
565
566
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
567
568
569
570
571
572
573
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");
574
	if(!strcmp(session->host_name,session->host_ip))
575
576
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
577
	add_env(session,"REQUEST_URI",session->req.request_line);
578
579
}

580
/*
deuce's avatar
deuce committed
581
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
582
583
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
584
static int bufprint(http_session_t *session, const char *str)
585
{
586
587
588
	int len;

	len=strlen(str);
589
	return(writebuf(session,str,len));
590
591
}

deuce's avatar
deuce committed
592
593
594
595
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
596
597
598
599
600
601
602
603
604
605
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
606
607
608
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
609
610
611
static time_t decode_date(char *date)
{
	struct	tm	ti;
612
613
	char	*token;
	time_t	t;
614
615
616
617
618
619
620
621
622

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

623
	token=strtok(date,",");
624
625
	if(token==NULL)
		return(0);
626
627
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
628
		/* asctime() */
629
630
		/* Toss away week day */
		token=strtok(date," ");
631
632
		if(token==NULL)
			return(0);
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
		token=strtok(NULL," ");
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
		token=strtok(NULL," ");
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
		token=strtok(NULL,":");
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
		token=strtok(NULL,":");
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
		token=strtok(NULL," ");
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
		token=strtok(NULL,"");
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token)-1900;
657
658
659
	}
	else  {
		/* RFC 1123 or RFC 850 */
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
		token=strtok(NULL," -");
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
		token=strtok(NULL," -");
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
		token=strtok(NULL," ");
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token);
		token=strtok(NULL,":");
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
		token=strtok(NULL,":");
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
		token=strtok(NULL," ");
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
684
685
686
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
687

688
	t=time_gm(&ti);
689
	return(t);
690
691
692
693
694
695
696
697
698
}

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) 
699
		startup->socket_open(startup->cbdata,TRUE);
700
	if(sock!=INVALID_SOCKET) {
701
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
702
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718

		sockets++;
	}
	return(sock);
}

static int close_socket(SOCKET sock)
{
	int		result;

	if(sock==INVALID_SOCKET)
		return(-1);

	shutdown(sock,SHUT_RDWR);	/* required on Unix */
	result=closesocket(sock);
	if(startup!=NULL && startup->socket_open!=NULL) {
719
		startup->socket_open(startup->cbdata,FALSE);
720
721
722
723
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
724
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
725
726
727
728
729
	}

	return(result);
}

730
731
732
733
734
735
736
737
738
739
740
/* Waits for the outbuf to drain */
static void drain_outbuf(http_session_t * session)
{
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));
	/* ToDo: This should probobly timeout eventually... */
	while(RingBufFull(&session->outbuf))
		SLEEP(1);
	/* Lock the mutex to ensure data has been sent */
	while(!session->outbuf_write_initialized)
		SLEEP(1);
741
	pthread_mutex_lock(&session->outbuf_write);		/* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */
742
743
744
	pthread_mutex_unlock(&session->outbuf_write);
}

deuce's avatar
deuce committed
745
746
747
748
749
750
751
752
/**************************************************/
/* 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 */
/**************************************************/
753
754
static void close_request(http_session_t * session)
{
755
	time_t		now;
756
	int			i;
757

758
	if(session->req.write_chunked) {
759
		drain_outbuf(session);
760
761
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
762
		if(session->req.dynamic==IS_SSJS)
763
			ssjs_send_headers(session,FALSE);
764
765
		else
			/* Non-ssjs isn't capable of generating headers during execution */
766
			writebuf(session, newline, 2);
767
768
	}

769
770
771
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

772
773
774
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
775
		listPushNode(&log_list,session->req.ld);
776
777
778
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
779

780
781
782
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
783
	FREE_AND_NULL(session->req.post_data);
784
785
786
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
	FREE_AND_NULL(session->req.realm);
787
	if(!session->req.keep_alive) {
788
		drain_outbuf(session);
789
		close_socket(session->socket);
790
		session->socket=INVALID_SOCKET;
791
	}
792
793
794
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
795
796
797
	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
798
799
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
800

801
802
803
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

804
805
806
807
808
809
	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]);
		}
810
811
	}

812
	memset(&session->req,0,sizeof(session->req));
813
814
815
816
}

static int get_header_type(char *header)
{
817
	int i;
818
819
820
821
822
823
824
825
	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
826
/* Opposite of get_header_type() */
827
828
static char *get_header(int id) 
{
829
	int i;
830
831
	if(headers[id].id==id)
		return(headers[id].text);
832
833
834
835
836
837
838
839
840

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

841
842
static const char* unknown_mime_type="application/octet-stream";

843
static const char* get_mime_type(char *ext)
844
845
846
{
	uint i;

847
	if(ext==NULL || mime_types==NULL)
848
849
		return(unknown_mime_type);

850
	for(i=0;mime_types[i]!=NULL;i++)
851
		if(stricmp(ext+1,mime_types[i]->name)==0)
852
			return(mime_types[i]->value);
853
854

	return(unknown_mime_type);
855
856
}

857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
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;

	if(ext==NULL || xjs_handlers==NULL)
		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);
}

895
896
/* 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) {
897
	size_t dstlen,appendlen;
898
899
900
901
902
903
904
905
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
906
907
908
909
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
910
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
911
{
912
	int		ret;
913
	BOOL	send_file=TRUE;
914
	time_t	ti;
915
	size_t	idx;
916
	const char	*status_line;
917
	struct stat	stats;
918
	struct tm	tm;
919
	char	*headers;
920
	char	header[MAX_REQUEST_LINE+1];
921

deuce's avatar
deuce committed
922
923
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
924
		return(FALSE);
deuce's avatar
deuce committed
925
	}
926
927
928
	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
929
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
930
931
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
932
		return(TRUE);
933
	}
deuce's avatar
deuce committed
934
935
936
937
938
939
	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
940
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
941
		session->req.sent_headers=TRUE;
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
		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;
		}
		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;
		}
961

962
963
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
964

965
966
967
968
		/* 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);
969

970
		safecat(headers,header,MAX_HEADERS_SIZE);
971

972
973
974
975
976
977
978
979
		/* 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);
980
		safecat(headers,header,MAX_HEADERS_SIZE);
981
982
983
984
985
986
987
988
		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);
		}
989

990
991
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
992
993
		safecat(headers,header,MAX_HEADERS_SIZE);

994
995
996
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
997
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
998
		}
999
1000
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
For faster browsing, not all history is shown. View entire blame