websrvr.c 119 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

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

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

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

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

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

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

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

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

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

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

	/* Client info */
	client_t		client;
202
203
204
205
206
} http_session_t;

enum { 
	 HTTP_0_9
	,HTTP_1_0
207
	,HTTP_1_1
208
209
210
211
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
212
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
213
	,NULL	/* terminator */
214
215
216
217
218
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
219
220
	,HTTP_POST
	,HTTP_OPTIONS
221
};
222

rswindell's avatar
rswindell committed
223
224
225
static char* methods[] = {
	 "HEAD"
	,"GET"
226
	,"POST"
227
	,"OPTIONS"
rswindell's avatar
rswindell committed
228
229
	,NULL	/* terminator */
};
230

231
enum {
232
233
234
235
236
237
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

284
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
285
enum  {
286
	 NO_LOCATION
287
	,MOVED_PERM
288
	,MOVED_TEMP
289
	,MOVED_STAT
290
291
};

292
293
294
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"};

295
static void respond(http_session_t * session);
296
static BOOL js_setup(http_session_t* session);
297
static char *find_last_slash(char *str);
298
static BOOL check_extra_path(http_session_t * session);
299
static BOOL exec_ssjs(http_session_t* session, char* script);
300
static BOOL ssjs_send_headers(http_session_t* session);
301

302
303
304
305
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
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);
}
403

404
static int lprintf(int level, char *fmt, ...)
405
406
407
408
409
410
411
412
413
414
415
{
	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);
416
    return(startup->lputs(startup->cbdata,level,sbuf));
417
418
}

419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
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);
}

442
443
444
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
445
#define SOCKLIB_DESC WSAData.szDescription
446
447
448
449
450
451
452
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
453
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
454
455
456
457
		WSAInitialized=TRUE;
		return (TRUE);
	}

458
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
459
460
461
462
463
464
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
465
#define SOCKLIB_DESC NULL
466
467
468
469
470
471

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
472
	    startup->status(startup->cbdata,str);
473
474
475
476
477
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
478
		startup->clients(startup->cbdata,active_clients);
479
480
481
482
483
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
484
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
485
486
487
488
489
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
490
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
491
492
493
494
495
496
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
497
		startup->thread_up(startup->cbdata,TRUE, setuid);
498
499
500
501
502
503
504
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
505
		startup->thread_up(startup->cbdata,FALSE, FALSE);
506
507
}

deuce's avatar
deuce committed
508
509
510
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
511
static void add_env(http_session_t *session, const char *name,const char *value)  {
512
	char	newname[129];
513
	char	*p;
514

515
	if(name==NULL || value==NULL)  {
516
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
517
518
519
520
521
522
523
524
525
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
526
527
528
529
530
	p=(char *)malloc(strlen(name)+strlen(value)+2);
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
531
#if 0	/* this is way too verbose for every request */
532
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
533
#endif
534
	sprintf(p,"%s=%s",newname,value);
535
	strListPush(&session->req.cgi_env,p);
536
	free(p);
537
538
}

deuce's avatar
deuce committed
539
540
541
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
542
543
544
545
546
547
548
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");
549
	if(!strcmp(session->host_name,session->host_ip))
550
551
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
552
	add_env(session,"REQUEST_URI",session->req.request_line);
553
554
}

555
/*
deuce's avatar
deuce committed
556
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
557
558
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
559
560
static int sockprint(SOCKET sock, const char *str)
{
561
562
563
	int len;
	int	result;
	int written=0;
564
	BOOL	wr;
565
566
567

	if(sock==INVALID_SOCKET)
		return(0);
568
	if(startup->options&WEB_OPT_DEBUG_TX)
569
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
570
	len=strlen(str);
571

572
	while(socket_check(sock,NULL,&wr,startup->max_inactivity*1000) && wr && written<len)  {
573
		result=sendsocket(sock,str+written,len-written);
574
575
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
576
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
577
			else if(ERROR_VALUE==ECONNABORTED) 
578
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
579
			else
580
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
581
582
			return(0);
		}
583
584
585
		written+=result;
	}
	if(written != len) {
586
		lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
587
588
589
590
591
		return(0);
	}
	return(len);
}

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

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

743
	if(session->req.write_chunked) {
744
		sock_sendbuf(session->socket, "0\r\n",3,NULL);
745
746
747
748
		if(session->req.dynamic==IS_SSJS)
			ssjs_send_headers(session);
		else
			/* Non-ssjs isn't capable of generating headers during execution */
749
			sock_sendbuf(session->socket, newline,2,NULL);
750
751
	}

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

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

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

780
781
782
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

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

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

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

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

820
821
static const char* unknown_mime_type="application/octet-stream";

822
static const char* get_mime_type(char *ext)
823
824
825
{
	uint i;

826
	if(ext==NULL || mime_types==NULL)
827
828
		return(unknown_mime_type);

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

	return(unknown_mime_type);
834
835
}

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

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

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

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

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

944
945
946
947
		/* 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);
948

949
		safecat(headers,header,MAX_HEADERS_SIZE);
950

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

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

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

983
984
985
986
987
988
989
990
991
992
993
		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);
		}

		if(session->req.write_chunked) {
			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 */
994
		if(session->req.keep_alive && session->req.dynamic!=IS_CGI && (!session->req.write_chunked)) {
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
			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
1016

1017
1018
	if(session->req.dynamic)  {
		/* Dynamic headers */
1019
		/* Set up environment */
1020
1021
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
1022
1023
		/* 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);
1024
	}
1025

1026
	safecat(headers,"",MAX_HEADERS_SIZE);
1027
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
1028
	FREE_AND_NULL(headers);
1029
	return(send_file);
1030
1031
}

1032
static int sock_sendfile(SOCKET socket,char *path)
1033
1034
{
	int		file;
1035
	long	offset=0;
1036
	int		ret=0;
1037

1038
	if(startup->options&WEB_OPT_DEBUG_TX)
1039
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
1040
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
1041
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
1042
	else {
1043
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 0) {
1044
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
1045
				, socket, errno, path);
1046
1047
			ret=0;
		}
1048
1049
		close(file);
	}
1050
	return(ret);
1051
1052
}

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

1064
1065
	if(session->socket==INVALID_SOCKET)
		return;
1066
	session->req.if_modified_since=0;
1067
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1068
	session->req.keep_alive=FALSE;
1069
	session->req.send_location=NO_LOCATION;
1070
	SAFECOPY(error_code,message);
1071
1072
1073
1074
1075
1076
1077
1078
	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
		 */

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

					lprintf(LOG_INFO,"%04d Sending generated error page",session->socket);
					snt=sock_sendfile(session->socket,session->req.physical_path);
					if(snt<0)
						snt=0;
					if(session->req.ld!=NULL)
						session->req.ld->size=snt;
				}
1095
1096
				else
					 session->req.dynamic=IS_STATIC;
1097
			}
1098
1099
			else
				session->req.dynamic=IS_STATIC;
1100
		}
1101
	}
1102
	if(!sent_ssjs) {
1103
		sprintf(session->req.physical_path,"%s%s.html",session->req.error_dir?session->req.error_dir:error_dir,error_code);
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
		send_headers(session,message);
		if(!stat(session->req.physical_path,&sb)) {
			int	snt=0;
			snt=sock_sendfile(session->socket,session->req.physical_path);
			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);
			sockprint(session->socket,sbuf);
			if(session->req.ld!=NULL)
				session->req.ld->size=strlen(sbuf);
		}
1128
	}
1129
	session->req.finished=TRUE;