websrvr.c 120 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
204
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;

205
206
	/* Client info */
	client_t		client;
207
208
209
210
211
} http_session_t;

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
224
225
	,HTTP_POST
	,HTTP_OPTIONS
226
};
227

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

236
enum {
237
238
239
240
241
242
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

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

297
298
299
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"};

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

307
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
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);
}
408

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

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

	while(!terminate_server && sent < len) {
		avail=RingBufFree(&session->outbuf);
		if(!avail)
			SLEEP(1);
		if(avail > len-sent)
			avail=len-sent;
435
		sent+=RingBufWrite(&(session->outbuf), ((char *)buf)+sent, avail);
436
437
438
439
	}
	return(sent);
}

440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
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);
}

463
464
465
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
466
#define SOCKLIB_DESC WSAData.szDescription
467
468
469
470
471
472
473
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
474
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
475
476
477
478
		WSAInitialized=TRUE;
		return (TRUE);
	}

479
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
480
481
482
483
484
485
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
486
#define SOCKLIB_DESC NULL
487
488
489
490
491
492

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
493
	    startup->status(startup->cbdata,str);
494
495
496
497
498
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
499
		startup->clients(startup->cbdata,active_clients);
500
501
502
503
504
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
505
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
506
507
508
509
510
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
511
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
512
513
514
515
516
517
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
518
		startup->thread_up(startup->cbdata,TRUE, setuid);
519
520
521
522
523
524
525
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
526
		startup->thread_up(startup->cbdata,FALSE, FALSE);
527
528
}

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

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

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

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

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

	len=strlen(str);
585
	return(writebuf(session,str,len));
586
587
}

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

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

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

684
	t=time_gm(&ti);
685
	return(t);
686
687
688
689
690
691
692
693
694
}

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

		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) {
715
		startup->socket_open(startup->cbdata,FALSE);
716
717
718
719
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
720
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
721
722
723
724
725
	}

	return(result);
}

deuce's avatar
deuce committed
726
727
728
729
730
731
732
733
/**************************************************/
/* 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 */
/**************************************************/
734
735
static void close_request(http_session_t * session)
{
736
	time_t		now;
737
	int			i;
738

739
	if(session->req.write_chunked) {
740
741
742
743
		while(RingBufFull(&session->outbuf))
			SLEEP(1);
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
744
		if(session->req.dynamic==IS_SSJS)
745
			ssjs_send_headers(session,FALSE);
746
747
		else
			/* Non-ssjs isn't capable of generating headers during execution */
748
			writebuf(session, newline, 2);
749
750
	}

751
752
753
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

754
755
756
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
757
		listPushNode(&log_list,session->req.ld);
758
759
760
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
761

762
763
764
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
765
	FREE_AND_NULL(session->req.post_data);
766
767
768
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
	FREE_AND_NULL(session->req.realm);
769
	if(!session->req.keep_alive) {
770
		close_socket(session->socket);
771
		session->socket=INVALID_SOCKET;
772
	}
773
774
775
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
776
777
778
	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
779
780
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
781

782
783
784
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

785
786
787
788
789
790
	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]);
		}
791
792
	}

793
	memset(&session->req,0,sizeof(session->req));
794
795
796
797
}

static int get_header_type(char *header)
{
798
	int i;
799
800
801
802
803
804
805
806
	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
807
/* Opposite of get_header_type() */
808
809
static char *get_header(int id) 
{
810
	int i;
811
812
	if(headers[id].id==id)
		return(headers[id].text);
813
814
815
816
817
818
819
820
821

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

822
823
static const char* unknown_mime_type="application/octet-stream";

824
static const char* get_mime_type(char *ext)
825
826
827
{
	uint i;

828
	if(ext==NULL || mime_types==NULL)
829
830
		return(unknown_mime_type);

831
	for(i=0;mime_types[i]!=NULL;i++)
832
		if(stricmp(ext+1,mime_types[i]->name)==0)
833
			return(mime_types[i]->value);
834
835

	return(unknown_mime_type);
836
837
}

838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
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);
}

876
877
/* 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) {
878
	size_t dstlen,appendlen;
879
880
881
882
883
884
885
886
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
887
888
889
890
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
891
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
892
{
893
	int		ret;
894
	BOOL	send_file=TRUE;
895
	time_t	ti;
896
	size_t	idx;
897
	const char	*status_line;
898
	struct stat	stats;
899
	struct tm	tm;
900
	char	*headers;
901
	char	header[MAX_REQUEST_LINE+1];
902

deuce's avatar
deuce committed
903
904
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
905
		return(FALSE);
deuce's avatar
deuce committed
906
	}
907
908
909
	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
910
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
911
912
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
913
		return(TRUE);
914
	}
deuce's avatar
deuce committed
915
916
917
918
919
920
	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
921
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
922
		session->req.sent_headers=TRUE;
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
		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;
		}
942

943
944
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
945

946
947
948
949
		/* 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);
950

951
		safecat(headers,header,MAX_HEADERS_SIZE);
952

953
954
955
956
957
958
959
960
		/* 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);
961
		safecat(headers,header,MAX_HEADERS_SIZE);
962
963
964
965
966
967
968
969
		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);
		}
970

971
972
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
973
974
		safecat(headers,header,MAX_HEADERS_SIZE);

975
976
977
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
978
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
979
		}
980
981
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
982
			safecat(headers,header,MAX_HEADERS_SIZE);
983
		}
984

985
986
987
988
989
		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);
		}

990
		if(chunked) {
991
992
993
994
995
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TRANSFER_ENCODING),"Chunked");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}

		/* DO NOT send a content-length for chunked */
996
		if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!chunked)) {
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
			if(ret)  {
				safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
				safecat(headers,header,MAX_HEADERS_SIZE);
			}
			else  {
				safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
				safecat(headers,header,MAX_HEADERS_SIZE);
			}
		}

		if(!ret && !session->req.dynamic)  {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
			safecat(headers,header,MAX_HEADERS_SIZE);
			gmtime_r(&stats.st_mtime,&tm);
			safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
				,get_header(HEAD_LASTMODIFIED)
				,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
				,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
	}
rswindell's avatar
rswindell committed
1018

1019
1020
	if(session->req.dynamic)  {
		/* Dynamic headers */
1021
		/* Set up environment */
1022
1023
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
1024
1025
		/* free() the headers so they don't get sent again if more are sent at the end of the request (chunked) */
		strListFreeStrings(session->req.dynamic_heads);
1026
	}
1027

1028
	safecat(headers,"",MAX_HEADERS_SIZE);
1029
	send_file = (bufprint(session,headers) && send_file);
deuce's avatar
deuce committed
1030
	FREE_AND_NULL(headers);
1031
1032
1033
	while(RingBufFull(&session->outbuf))
		SLEEP(1);
	session->req.write_chunked=chunked;
1034
	return(send_file);
1035
1036
}

1037
static int sock_sendfile(http_session_t *session,char *path)
1038
1039
{
	int		file;
1040
	int		ret=0;
1041
1042
	int		i;
	char	buf[2048];		/* Input buffer */
1043

1044
	if(startup->options&WEB_OPT_DEBUG_TX)
1045
		lprintf(LOG_DEBUG,"%04d Sending %s",session->socket,path);
1046
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
1047
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",session->socket,errno,path);
1048
	else {
1049
1050
		while((i=read(file, buf, sizeof(buf)))>0) {
			writebuf(session,buf,i);
1051
		}
1052
1053
		close(file);
	}
1054
	return(ret);
1055
1056
}

deuce's avatar
deuce committed
1057
1058
1059
1060
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
1061
static void send_error(http_session_t * session, const char* message)
1062
1063
{
	char	error_code[4];
1064
	struct stat	sb;
1065
	char	sbuf[1024];
1066
	BOOL	sent_ssjs=FALSE;
1067

1068
1069
	if(session->socket==INVALID_SOCKET)
		return;
1070
	session->req.if_modified_since=0;
1071
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1072
	session->req.keep_alive=FALSE;
1073
	session->req.send_location=NO_LOCATION;
1074
	SAFECOPY(error_code,message);
1075
1076
1077
1078
1079
1080
1081
1082
	SAFECOPY(session->req.status,message);
	if(atoi(error_code)<500) {
		/*
		 * Attempt to run SSJS error pages
		 * If this fails, do the standard error page instead,
		 * ie: Don't "upgrade" to a 500 error
		 */

1083
		sprintf(sbuf,"%s%s%s",session->req.error_dir?session->req.error_dir:error_dir,error_code,startup->ssjs_ext);
1084
1085
		if(!stat(sbuf,&sb)) {
			lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket);
1086
			session->req.dynamic=IS_SSJS;
1087
1088
			if(js_setup(session)) {
				sent_ssjs=exec_ssjs(session,sbuf);
1089
				if(sent_ssjs) {
1090
1091
1092
					int	snt=0;

					lprintf(LOG_INFO,"%04d Sending generated error page",session->socket);
1093
					snt=sock_sendfile(session,session->req.physical_path);
1094
1095
1096
1097
1098
					if(snt<0)
						snt=0;
					if(session->req.ld!=NULL)
						session->req.ld->size=snt;
				}
1099
1100
				else
					 session->req.dynamic=IS_STATIC;
1101
			}
1102
1103
			else
				session->req.dynamic=IS_STATIC;
1104
		}
1105
	}
1106
	if(!sent_ssjs) {
1107
		sprintf(session->req.physical_path,"%s%s.html",session->req.error_dir?session->req.error_dir:error_dir,error_code);
1108
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
1109
		send_headers(session,message,FALSE);
1110
1111
		if(!stat(session->req.physical_path,&sb)) {
			int	snt=0;
1112
			snt=sock_sendfile(session,session->req.physical_path);
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
			if(snt<0)
				snt=0;
			if(session->req.ld!=NULL)
				session->req.ld->size=snt;
		}
		else {
			lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
				,session->socket,session->req.physical_path);
			safe_snprintf(sbuf,sizeof(sbuf)
				,"<HTML><HEAD><TITLE>%s Error</TITLE></HEAD>"
				"<BODY><H1>%s Error</H1><BR><H3>In addition, "
				"I can't seem to find the %s error file</H3><br>"
				"please notify <a href=\"mailto:sysop@%s\">"
				"%s</a></BODY></HTML>"
				,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);