websrvr.c 114 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
} http_request_t;

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

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
189
190
191
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
192
	js_branch_t		js_branch;
deuce's avatar
deuce committed
193
	subscan_t		*subscan;
194
195
196

	/* Client info */
	client_t		client;
197
198
199
200
201
} http_session_t;

enum { 
	 HTTP_0_9
	,HTTP_1_0
202
	,HTTP_1_1
203
204
205
206
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
207
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
208
	,NULL	/* terminator */
209
210
211
212
213
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
214
215
	,HTTP_POST
	,HTTP_OPTIONS
216
};
217

rswindell's avatar
rswindell committed
218
219
220
static char* methods[] = {
	 "HEAD"
	,"GET"
221
	,"POST"
222
	,"OPTIONS"
rswindell's avatar
rswindell committed
223
224
	,NULL	/* terminator */
};
225

226
enum {
227
228
229
230
231
232
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

279
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
280
enum  {
281
	 NO_LOCATION
282
	,MOVED_PERM
283
	,MOVED_TEMP
284
	,MOVED_STAT
285
286
};

287
288
289
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"};

290
static void respond(http_session_t * session);
291
static BOOL js_setup(http_session_t* session);
292
static char *find_last_slash(char *str);
293
static BOOL check_extra_path(http_session_t * session);
294
static BOOL exec_ssjs(http_session_t* session, char* script);
295
static BOOL ssjs_send_headers(http_session_t* session);
296

297
298
299
300
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
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);
}
398

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

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
417
#define SOCKLIB_DESC WSAData.szDescription
418
419
420
421
422
423
424
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
425
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
426
427
428
429
		WSAInitialized=TRUE;
		return (TRUE);
	}

430
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
431
432
433
434
435
436
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
437
#define SOCKLIB_DESC NULL
438
439
440
441
442
443

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
444
	    startup->status(startup->cbdata,str);
445
446
447
448
449
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
450
		startup->clients(startup->cbdata,active_clients);
451
452
453
454
455
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
456
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
457
458
459
460
461
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
462
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
463
464
465
466
467
468
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
469
		startup->thread_up(startup->cbdata,TRUE, setuid);
470
471
472
473
474
475
476
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
477
		startup->thread_up(startup->cbdata,FALSE, FALSE);
478
479
}

deuce's avatar
deuce committed
480
481
482
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
483
static void add_env(http_session_t *session, const char *name,const char *value)  {
484
	char	newname[129];
485
	char	*p;
486

487
	if(name==NULL || value==NULL)  {
488
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
489
490
491
492
493
494
495
496
497
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
498
499
500
501
502
	p=(char *)malloc(strlen(name)+strlen(value)+2);
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
503
#if 0	/* this is way too verbose for every request */
504
	lprintf(LOG_DEBUG,"%04d Adding CGI environment variable %s=%s",session->socket,newname,value);
505
#endif
506
	sprintf(p,"%s=%s",newname,value);
507
	strListPush(&session->req.cgi_env,p);
508
	free(p);
509
510
}

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

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

	if(sock==INVALID_SOCKET)
		return(0);
539
	if(startup->options&WEB_OPT_DEBUG_TX)
540
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
541
	len=strlen(str);
542

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

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

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

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

659
	t=time_gm(&ti);
660
	return(t);
661
662
663
664
665
666
667
668
669
}

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

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

	return(result);
}

deuce's avatar
deuce committed
701
702
703
704
705
706
707
708
/**************************************************/
/* 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 */
/**************************************************/
709
710
static void close_request(http_session_t * session)
{
711
	time_t		now;
712
	int			i;
713

714
	if(session->req.write_chunked) {
715
716
717
718
719
720
		sendsocket(session->socket, "0\r\n",3);
		if(session->req.dynamic==IS_SSJS)
			ssjs_send_headers(session);
		else
			/* Non-ssjs isn't capable of generating headers during execution */
			sendsocket(session->socket, newline,2);
721
722
	}

723
724
725
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
726
		listPushNode(&log_list,session->req.ld);
727
728
729
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
730

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

deuce's avatar
deuce committed
742
743
744
	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
745
746
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
747

748
749
750
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

751
752
753
754
755
756
	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]);
		}
757
758
	}

759
	memset(&session->req,0,sizeof(session->req));
760
761
762
763
}

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

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

788
789
static const char* unknown_mime_type="application/octet-stream";

790
static const char* get_mime_type(char *ext)
791
792
793
{
	uint i;

794
	if(ext==NULL || mime_types==NULL)
795
796
		return(unknown_mime_type);

797
	for(i=0;mime_types[i]!=NULL;i++)
798
		if(stricmp(ext+1,mime_types[i]->name)==0)
799
			return(mime_types[i]->value);
800
801

	return(unknown_mime_type);
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
838
839
840
841
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);
}

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

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

deuce's avatar
deuce committed
869
870
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
871
		return(FALSE);
deuce's avatar
deuce committed
872
	}
873
874
875
	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
876
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
877
878
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
879
		return(TRUE);
880
	}
deuce's avatar
deuce committed
881
882
883
884
885
886
	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
887
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
888
		session->req.sent_headers=TRUE;
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
		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;
		}
908

909
910
		if(session->req.ld!=NULL)
			session->req.ld->status=atoi(status_line);
911

912
913
914
915
		/* 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);
916

917
		safecat(headers,header,MAX_HEADERS_SIZE);
918

919
920
921
922
923
924
925
926
		/* 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);
927
		safecat(headers,header,MAX_HEADERS_SIZE);
928
929
930
931
932
933
934
935
		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);
		}
936

937
938
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
939
940
		safecat(headers,header,MAX_HEADERS_SIZE);

941
942
943
		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
944
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
945
		}
946
947
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
948
			safecat(headers,header,MAX_HEADERS_SIZE);
949
		}
950

951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
		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 */
		if(session->req.keep_alive && (!session->req.write_chunked)) {
			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
984

985
986
	if(session->req.dynamic)  {
		/* Dynamic headers */
987
		/* Set up environment */
988
989
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
990
991
		/* 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);
992
	}
993

994
	safecat(headers,"",MAX_HEADERS_SIZE);
995
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
996
	FREE_AND_NULL(headers);
997
	return(send_file);
998
999
}

1000
static int sock_sendfile(SOCKET socket,char *path)
1001
1002
{
	int		file;
1003
	long	offset=0;
1004
	int		ret=0;
1005

1006
	if(startup->options&WEB_OPT_DEBUG_TX)
1007
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
1008
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
1009
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
1010
	else {
1011
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 0) {
1012
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
1013
				, socket, errno, path);
1014
1015
			ret=0;
		}
1016
1017
		close(file);
	}
1018
	return(ret);
1019
1020
}

deuce's avatar
deuce committed
1021
1022
1023
1024
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
1025
static void send_error(http_session_t * session, const char* message)
1026
1027
{
	char	error_code[4];
1028
	struct stat	sb;
1029
	char	sbuf[1024];
1030
	BOOL	sent_ssjs=FALSE;
1031

1032
1033
	if(session->socket==INVALID_SOCKET)
		return;
1034
	session->req.if_modified_since=0;
1035
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1036
	session->req.keep_alive=FALSE;
1037
	session->req.send_location=NO_LOCATION;
1038
	SAFECOPY(error_code,message);
1039
1040
1041
1042
1043
1044
1045
1046
	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
		 */

1047
		sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext);
1048
1049
		if(!stat(sbuf,&sb)) {
			lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket);
1050
			session->req.dynamic=IS_SSJS;
1051
1052
			if(js_setup(session)) {
				sent_ssjs=exec_ssjs(session,sbuf);
1053
				if(sent_ssjs) {
1054
1055
1056
1057
1058
1059
1060
1061
1062
					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;
				}
1063
1064
				else
					 session->req.dynamic=IS_STATIC;
1065
			}
1066
1067
			else
				session->req.dynamic=IS_STATIC;
1068
		}
1069
	}
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
	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);
		}
1096
	}
1097
	session->req.finished=TRUE;
1098
1099
}

1100
1101
void http_logon(http_session_t * session, user_t *usr)
{
1102
1103
	char	str[128];

1104
1105
	if(usr==NULL)
		getuserdat(&scfg, &session->user);
1106
1107
	else
		session->user=*usr;
1108
1109
1110

	if(session->user.number==session->last_user_num)
		return;
1111

1112
	lprintf(LOG_DEBUG,"%04d HTTP Logon (user #%d)",session->socket,session->user.number);
1113

1114
1115
1116
	if(session->subscan!=NULL)
		getmsgptrs(&scfg,session->user.number,session->subscan);

deuce's avatar
deuce committed
1117
	session->logon_time=time(NULL);
1118
	if(session->user.number==0)
1119
		SAFECOPY(session->username,unknown);
deuce's avatar
deuce committed
1120
	else {
1121
		SAFECOPY(session->username,session->user.alias);