websrvr.c 109 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
43
44
45
46
47
48
49
/*
 * General notes: (ToDo stuff)
 *
 * Currently, all SSJS requests for a session are ran in the same context without clearing the context in
 * any way.  This behaviour should not be relied on as it may disappear in the future... this will require
 * some thought as it COULD be handy in some circumstances and COULD cause weird bugs in others.
 *
 * Dynamic content is always resent on an If-Modified-Since request... this may not be optimal behaviour
 * for GET requests...
 *
 * Should support RFC2617 Digest auth.
 *
50
 * Support the ident protocol... the standard log format supports it.
51
 *
deuce's avatar
deuce committed
52
53
 * SSJS stuff should support chunked transfer for HTTP/1.1 and fast mode.
 *		(Would allow keep-alives to stay erm... alive)
deuce's avatar
deuce committed
54
55
56
57
 *
 * 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.
58
59
 */

deuce's avatar
deuce committed
60
/* Headers for CGI stuff */
61
62
#if defined(__unix__)
	#include <sys/wait.h>		/* waitpid() */
rswindell's avatar
rswindell committed
63
64
	#include <sys/types.h>
	#include <signal.h>			/* kill() */
65
66
#endif

67
#ifndef JAVASCRIPT
68
#define JAVASCRIPT
69
70
#endif

71
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
72
73
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
74
#include "threadwrap.h"
75
#include "semwrap.h"
76
#include "websrvr.h"
deuce's avatar
deuce committed
77
#include "base64.h"
78

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

89
/* Is this not in a header somewhere? */
90
extern const uchar* nular;
rswindell's avatar
rswindell committed
91
92

#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
deuce's avatar
deuce committed
93
94
95
#define MAX_REQUEST_LINE		1024	/* NOT including terminator */
#define MAX_HEADERS_SIZE		16384	/* Maximum total size of all headers 
										   (Including terminator )*/
96
#define MAX_REDIR_LOOPS			20		/* Max. times to follow internal redirects for a single request */
97
#define MAX_POST_LEN			1048576	/* Max size of body for POSTS */
98

99
100
101
enum {
	 CLEANUP_SSJS_TMP_FILE
	,CLEANUP_POST_DATA
102
	,MAX_CLEANUPS
103
};
104

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

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

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

146
typedef struct  {
147
	int			method;
148
149
150
151
152
153
154
155
	char		virtual_path[MAX_PATH+1];
	char		physical_path[MAX_PATH+1];
	BOOL		parsed_headers;
	BOOL    	expect_go_ahead;
	time_t		if_modified_since;
	BOOL		keep_alive;
	char		ars[256];
	char    	auth[128];				/* UserID:Password */
156
157
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
158
	int			send_location;
159
	const char*	mime_type;
160
	str_list_t	headers;
161
	char		status[MAX_REQUEST_LINE+1];
162
163
	char *		post_data;
	size_t		post_len;
164
	int			dynamic;
165
	char		xjs_handler[MAX_PATH+1];
166
	struct log_data	*ld;
167
	char		request_line[MAX_REQUEST_LINE+1];
168
	BOOL		finished;				/* Done processing request. */
169

170
171
172
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
173
174
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
175

176
177
	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;
178
	char		*cleanup_file[MAX_CLEANUPS];
179
180
	BOOL	sent_headers;
	BOOL	prev_write;
181
182
183
} http_request_t;

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

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
201
202
203
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
204
	js_branch_t		js_branch;
deuce's avatar
deuce committed
205
	subscan_t		*subscan;
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
226
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
227

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

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

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

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

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

294
295
296
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"};

297
static void respond(http_session_t * session);
298
static BOOL js_setup(http_session_t* session);
299
static char *find_last_slash(char *str);
300
static BOOL check_extra_path(http_session_t * session);
301
static BOOL exec_ssjs(http_session_t* session, char* script);
302
static BOOL ssjs_send_headers(http_session_t* session);
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
403
404
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);
}
405

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

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
424
#define SOCKLIB_DESC WSAData.szDescription
425
426
427
428
429
430
431
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
432
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
433
434
435
436
		WSAInitialized=TRUE;
		return (TRUE);
	}

437
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
438
439
440
441
442
443
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
444
#define SOCKLIB_DESC NULL
445
446
447
448
449
450

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
451
	    startup->status(startup->cbdata,str);
452
453
454
455
456
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
457
		startup->clients(startup->cbdata,active_clients);
458
459
460
461
462
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
463
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
464
465
466
467
468
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
469
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
470
471
472
473
474
475
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
476
		startup->thread_up(startup->cbdata,TRUE, setuid);
477
478
479
480
481
482
483
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
484
		startup->thread_up(startup->cbdata,FALSE, FALSE);
485
486
}

deuce's avatar
deuce committed
487
488
489
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
490
static void add_env(http_session_t *session, const char *name,const char *value)  {
491
	char	newname[129];
492
	char	*p;
493

494
	if(name==NULL || value==NULL)  {
495
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
496
497
498
499
500
501
502
503
504
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
505
506
507
508
509
	p=(char *)malloc(strlen(name)+strlen(value)+2);
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
510
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
511
	sprintf(p,"%s=%s",newname,value);
512
	strListPush(&session->req.cgi_env,p);
513
	free(p);
514
515
}

deuce's avatar
deuce committed
516
517
518
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
519
520
521
522
523
524
525
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");
526
	if(!strcmp(session->host_name,session->host_ip))
527
528
529
530
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
}

531
/*
deuce's avatar
deuce committed
532
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
533
534
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
535
536
static int sockprint(SOCKET sock, const char *str)
{
537
538
539
	int len;
	int	result;
	int written=0;
540
	BOOL	wr;
541
542
543

	if(sock==INVALID_SOCKET)
		return(0);
544
	if(startup->options&WEB_OPT_DEBUG_TX)
545
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
546
	len=strlen(str);
547

548
	while(socket_check(sock,NULL,&wr,startup->max_inactivity*1000) && wr && written<len)  {
549
		result=sendsocket(sock,str+written,len-written);
550
551
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
552
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
553
			else if(ERROR_VALUE==ECONNABORTED) 
554
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
555
			else
556
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
557
558
			return(0);
		}
559
560
561
		written+=result;
	}
	if(written != len) {
562
		lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
563
564
565
566
567
		return(0);
	}
	return(len);
}

deuce's avatar
deuce committed
568
569
570
571
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
572
573
574
575
576
577
578
579
580
581
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
582
583
584
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
585
586
587
static time_t decode_date(char *date)
{
	struct	tm	ti;
588
589
	char	*token;
	time_t	t;
590
591
592
593
594
595
596
597
598

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

599
	token=strtok(date,",");
600
601
	if(token==NULL)
		return(0);
602
603
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
604
		/* asctime() */
605
606
		/* Toss away week day */
		token=strtok(date," ");
607
608
		if(token==NULL)
			return(0);
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
		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;
633
634
635
	}
	else  {
		/* RFC 1123 or RFC 850 */
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
		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);
660
661
662
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
663

664
	t=time_gm(&ti);
665
	return(t);
666
667
668
669
670
671
672
673
674
}

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) 
675
		startup->socket_open(startup->cbdata,TRUE);
676
677
	if(sock!=INVALID_SOCKET) {
		if(set_socket_options(&scfg, sock,error))
678
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694

		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) {
695
		startup->socket_open(startup->cbdata,FALSE);
696
697
698
699
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
700
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
701
702
703
704
705
	}

	return(result);
}

deuce's avatar
deuce committed
706
707
708
709
710
711
712
713
/**************************************************/
/* 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 */
/**************************************************/
714
715
static void close_request(http_session_t * session)
{
716
	time_t		now;
717
	int			i;
718

719
720
721
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
722
		listPushNode(&log_list,session->req.ld);
723
724
725
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
726

727
728
729
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
730
	FREE_AND_NULL(session->req.post_data);
731
	if(!session->req.keep_alive) {
732
		close_socket(session->socket);
733
		session->socket=INVALID_SOCKET;
734
	}
735
736
737
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
738
739
740
	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
741
742
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
743

744
745
746
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

747
748
749
750
751
752
	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]);
		}
753
754
	}

755
	memset(&session->req,0,sizeof(session->req));
756
757
758
759
}

static int get_header_type(char *header)
{
760
	int i;
761
762
763
764
765
766
767
768
	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
769
/* Opposite of get_header_type() */
770
771
static char *get_header(int id) 
{
772
	int i;
773
774
	if(headers[id].id==id)
		return(headers[id].text);
775
776
777
778
779
780
781
782
783

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

784
785
static const char* unknown_mime_type="application/octet-stream";

786
static const char* get_mime_type(char *ext)
787
788
789
{
	uint i;

790
	if(ext==NULL || mime_types==NULL)
791
792
		return(unknown_mime_type);

793
	for(i=0;mime_types[i]!=NULL;i++)
794
		if(stricmp(ext+1,mime_types[i]->name)==0)
795
			return(mime_types[i]->value);
796
797

	return(unknown_mime_type);
798
799
}

800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
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);
}

838
839
/* 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) {
840
	size_t dstlen,appendlen;
841
842
843
844
845
846
847
848
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
849
850
851
852
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
853
static BOOL send_headers(http_session_t *session, const char *status)
854
{
855
	int		ret;
856
	BOOL	send_file=TRUE;
857
	time_t	ti;
858
	size_t	idx;
859
	const char	*status_line;
860
	struct stat	stats;
861
	struct tm	tm;
862
	char	*headers;
863
	char	header[MAX_REQUEST_LINE+1];
864

865
866
	if(session->socket==INVALID_SOCKET)
		return(FALSE);
867
868
869
	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
870
871
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
872
		return(TRUE);
873
	}
deuce's avatar
deuce committed
874

875
	status_line=status;
876
	ret=stat(session->req.physical_path,&stats);
877
	if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
878
		status_line="304 Not Modified";
879
		ret=-1;
880
		send_file=FALSE;
881
	}
882
	if(session->req.send_location==MOVED_PERM)  {
883
		status_line=error_301;
884
885
886
		ret=-1;
		send_file=FALSE;
	}
887
	if(session->req.send_location==MOVED_TEMP)  {
888
		status_line=error_302;
889
890
891
		ret=-1;
		send_file=FALSE;
	}
892

893
894
895
	if(session->req.ld!=NULL)
		session->req.ld->status=atoi(status_line);

896
897
	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
898
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
899
900
901
		return(FALSE);
	}
	*headers=0;
902
	/* Status-Line */
903
	safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
904
905
906

	lprintf(LOG_DEBUG,"%04d Result: %s",session->socket,header);

907
	safecat(headers,header,MAX_HEADERS_SIZE);
908
909
910

	/* General Headers */
	ti=time(NULL);
911
912
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
913
	safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
914
915
916
		,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);
917
918
	safecat(headers,header,MAX_HEADERS_SIZE);
	if(session->req.keep_alive) {
919
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
920
921
922
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
923
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
924
925
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
926
927

	/* Response Headers */
928
	safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
929
	safecat(headers,header,MAX_HEADERS_SIZE);
930
931
	
	/* Entity Headers */
932
	if(session->req.dynamic) {
933
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
934
935
936
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
937
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
938
939
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
940

941
	if(session->req.send_location) {
942
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
943
		safecat(headers,header,MAX_HEADERS_SIZE);
944
	}
945
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
946
		if(ret)  {
947
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
948
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
949
		}
950
		else  {
951
			safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
952
			safecat(headers,header,MAX_HEADERS_SIZE);
953
		}
954
	}
955

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

967
968
	if(session->req.dynamic)  {
		/* Dynamic headers */
969
		/* Set up environment */
970
971
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
972
	}
973

974
	safecat(headers,"",MAX_HEADERS_SIZE);
975
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
976
	FREE_AND_NULL(headers);
977
	return(send_file);
978
979
}

980
static int sock_sendfile(SOCKET socket,char *path)
981
982
{
	int		file;
983
	long	offset=0;
984
	int		ret=0;
985

986
	if(startup->options&WEB_OPT_DEBUG_TX)
987
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
988
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
989
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
990
	else {
991
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 0) {
992
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
993
				, socket, errno, path);
994
995
			ret=0;
		}
996
997
		close(file);
	}
998
	return(ret);
999
1000
}

deuce's avatar
deuce committed
1001
1002
1003
1004
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
1005
static void send_error(http_session_t * session, const char* message)
1006
1007
{
	char	error_code[4];
1008
	struct stat	sb;
1009
	char	sbuf[1024];
1010
	BOOL	sent_ssjs=FALSE;
1011

1012
1013
	if(session->socket==INVALID_SOCKET)
		return;
1014
	session->req.if_modified_since=0;
1015
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1016
	session->req.keep_alive=FALSE;
1017
	session->req.send_location=NO_LOCATION;
1018
	SAFECOPY(error_code,message);
1019
1020
1021
1022
1023
1024
1025
1026
	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
		 */

1027
		sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext);
1028
1029
		if(!stat(sbuf,&sb)) {
			lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket);
1030
			session->req.dynamic=IS_SSJS;
1031
1032
			if(js_setup(session)) {
				sent_ssjs=exec_ssjs(session,sbuf);
1033
				if(sent_ssjs) {
1034
1035
1036
1037
1038
1039
1040
1041
1042
					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;
				}
1043
1044
				else
					 session->req.dynamic=IS_STATIC;
1045
			}
1046
1047
			else
				session->req.dynamic=IS_STATIC;
1048
		}
1049
	}
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
	if(!sent_ssjs) {
		sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
		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);
		}