websrvr.c 95.8 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 2004 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
50
51
52
53
54
55
56
57
/*
 * General notes: (ToDo stuff)
 * strtok() is used a LOT in here... notice that there is a strtok_r() for reentrant...
 * this may imply that strtok() is NOT thread-safe... if in fact it isn't this HAS
 * to be fixed before any real production-level quality is achieved with this web server
 * however, strtok_r() may not be a standard function.
 *
 * RE: not sending the headers if an nph scrpit is detected.  (The headers buffer could
 * just be free()ed and NULLed)
 *
 * 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.
 *
 * Fix up all the logging stuff.
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
 *
 * SSJS stuff could work using three different methods:
 * 1) Temporary file as happens currently
 *		Advantages:
 *			Allows to keep current connection (keep-alive works)
 *			write() doesn't need to be "special"
 *		Disadvantages:
 *			Depends on the temp dir being writable and capable of holding
 *				the full reply
 *			Everything goes throug the disk, so probobly some performance
 *				penalty is involved
 *			No way of sending directly to the remote system
 * 2) nph- style
 *		Advantages:
 *			No file I/O involved
 *			Can do magic tricks (ala my perl web wrapper)
 *		Disadvantages:
 *			Pretty much everything needs to be handled by the script.
 * 3) Return body in http_reply object
 *		All the advantages of 1)
 *		Could use a special write() to make everything just great.
 *		Still doesn't allow page to be sent until fully composed (ie: long
 *			delays)
 * 4) Type three with a callback that sends the header and current body, then
 *		converts write() to send directly to remote.
deuce's avatar
deuce committed
83
84
85
86
 *
 * 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.
87
88
 */

deuce's avatar
deuce committed
89
/* Headers for CGI stuff */
90
91
#if defined(__unix__)
	#include <sys/wait.h>		/* waitpid() */
rswindell's avatar
rswindell committed
92
93
	#include <sys/types.h>
	#include <signal.h>			/* kill() */
94
95
#endif

96
#ifndef JAVASCRIPT
97
#define JAVASCRIPT
98
99
#endif

100
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
101
102
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
103
#include "threadwrap.h"
104
#include "semwrap.h"
105
#include "websrvr.h"
deuce's avatar
deuce committed
106
#include "base64.h"
107

108
109
static const char*	server_name="Synchronet Web Server";
static const char*	newline="\r\n";
110
111
static const char*	http_scheme="http://";
static const size_t	http_scheme_len=7;
112
113
static const char*	error_404="404 Not Found";
static const char*	error_500="500 Internal Server Error";
114
static const char*	unknown="<unknown>";
115

116
/* Is this not in a header somewhere? */
117
extern const uchar* nular;
rswindell's avatar
rswindell committed
118
119

#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
deuce's avatar
deuce committed
120
121
122
#define MAX_REQUEST_LINE		1024	/* NOT including terminator */
#define MAX_HEADERS_SIZE		16384	/* Maximum total size of all headers 
										   (Including terminator )*/
123
#define MAX_REDIR_LOOPS			20		/* Max. times to follow internal redirects for a single request */
124

125
126
static scfg_t	scfg;
static BOOL		scfg_reloaded=TRUE;
127
static BOOL		http_logging_thread_running=FALSE;
128
129
static ulong	active_clients=0;
static ulong	sockets=0;
130
static BOOL		terminate_server=FALSE;
131
static BOOL		terminate_http_logging_thread=FALSE;
132
133
134
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
static char		revision[16];
135
136
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
137
static char		temp_dir[MAX_PATH+1];
138
static char		cgi_dir[MAX_PATH+1];
139
static time_t	uptime=0;
140
static DWORD	served=0;
141
static web_startup_t* startup=NULL;
142
static js_server_props_t js_server_props;
143
144
static link_list_t recycle_semfiles;
static link_list_t shutdown_semfiles;
145

146
147
static named_string_t** mime_types;

148
149
/* Logging stuff */
sem_t	log_sem;
150
link_list_t	log_list;
151
152
153
154
155
156
157
struct log_data {
	char	*hostname;
	char	*ident;
	char	*user;
	char	*request;
	char	*referrer;
	char	*agent;
158
	char	*vhost;
159
160
161
162
163
	int		status;
	unsigned int	size;
	struct tm completed;
};

164
typedef struct  {
165
	int			method;
166
167
168
169
170
171
172
173
	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 */
174
175
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
176
	int			send_location;
177
	const char*	mime_type;
178
	link_list_t	headers;
179
	char		status[MAX_REQUEST_LINE+1];
180
181
	char *		post_data;
	size_t		post_len;
182
	int			dynamic;
183
	struct log_data	*ld;
184
	char		request_line[MAX_REQUEST_LINE+1];
185

186
187
188
189
190
191
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
	link_list_t	cgi_env;
	link_list_t	dynamic_heads;

192
193
	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;
194
	char		*cleanup_file;
195
196
197
} http_request_t;

typedef struct  {
198
199
	SOCKET			socket;
	SOCKADDR_IN		addr;
200
	http_request_t	req;
201
202
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
203
204
	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 */
205
206
207
	user_t			user;
	int				last_user_num;
	time_t			logon_time;
208
	char			username[LEN_NAME+1];
209
	int				last_js_user_num;
210
211
212
213
214

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
215
216
217
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
218
	js_branch_t		js_branch;
deuce's avatar
deuce committed
219
	subscan_t		*subscan;
220
221
222

	/* Client info */
	client_t		client;
223
224
225
226
227
} http_session_t;

enum { 
	 HTTP_0_9
	,HTTP_1_0
228
	,HTTP_1_1
229
230
231
232
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
233
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
234
	,NULL	/* terminator */
235
236
237
238
239
240
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
241

rswindell's avatar
rswindell committed
242
243
244
static char* methods[] = {
	 "HEAD"
	,"GET"
245
	,"POST"
rswindell's avatar
rswindell committed
246
247
	,NULL	/* terminator */
};
248

249
enum {
250
251
252
253
254
255
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

256
enum { 
257
258
259
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
260
261
	,HEAD_LENGTH
	,HEAD_TYPE
262
263
264
265
266
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
267
268
269
270
271
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
272
273
	,HEAD_REFERER
	,HEAD_AGENT
274
275
276
277
278
279
};

static struct {
	int		id;
	char*	text;
} headers[] = {
280
281
282
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
283
284
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
285
286
287
288
289
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
290
291
292
293
294
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
295
296
	{ HEAD_REFERER,			"Referer"				},
	{ HEAD_AGENT,			"User-Agent"			},
297
	{ -1,					NULL /* terminator */	},
298
299
};

300
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
301
enum  {
302
	 NO_LOCATION
303
	,MOVED_PERM
304
	,MOVED_TEMP
305
	,MOVED_STAT
306
307
};

308
309
310
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"};

311
static void respond(http_session_t * session);
312
static BOOL js_setup(http_session_t* session);
313
static char *find_last_slash(char *str);
314
static BOOL check_extra_path(http_session_t * session);
315
static BOOL exec_ssjs(http_session_t* session, char *script);
316

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
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);
}
418

419
static int lprintf(int level, char *fmt, ...)
420
421
422
423
424
425
426
427
428
429
430
{
	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);
431
    return(startup->lputs(startup->cbdata,level,sbuf));
432
433
434
435
436
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
437
#define SOCKLIB_DESC WSAData.szDescription
438
439
440
441
442
443
444
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
445
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
446
447
448
449
		WSAInitialized=TRUE;
		return (TRUE);
	}

450
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
451
452
453
454
455
456
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
457
#define SOCKLIB_DESC NULL
458
459
460
461
462
463

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
464
	    startup->status(startup->cbdata,str);
465
466
467
468
469
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
470
		startup->clients(startup->cbdata,active_clients);
471
472
473
474
475
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
476
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
477
478
479
480
481
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
482
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
483
484
485
486
487
488
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
489
		startup->thread_up(startup->cbdata,TRUE, setuid);
490
491
492
493
494
495
496
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
497
		startup->thread_up(startup->cbdata,FALSE, FALSE);
498
499
}

deuce's avatar
deuce committed
500
501
502
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
503
static void add_env(http_session_t *session, const char *name,const char *value)  {
504
	char	newname[129];
505
	char	*p;
506

507
	if(name==NULL || value==NULL)  {
508
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
509
510
511
512
513
514
515
516
517
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
518
519
520
521
522
523
524
525
	p=(char *)malloc(strlen(name)+strlen(value)+2);
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
	sprintf(p,"%s=%s",newname,value);
	listPushNodeString(&session->req.cgi_env,p);
	free(p);
526
527
}

deuce's avatar
deuce committed
528
529
530
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
531
532
533
534
535
536
537
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");
538
	if(!strcmp(session->host_name,session->host_ip))
539
540
541
542
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
}

543
/*
deuce's avatar
deuce committed
544
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
545
546
547
548
 * (Should it be -1 on an error?)
 * Can not close the socket since it can not set it to INVALID_SOCKET
 * ToDo - Decide error behaviour, should a SOCKET * be passed around rather than a socket?
 */
549
550
static int sockprint(SOCKET sock, const char *str)
{
551
552
553
	int len;
	int	result;
	int written=0;
554
	BOOL	wr;
555
556
557

	if(sock==INVALID_SOCKET)
		return(0);
558
	if(startup->options&WEB_OPT_DEBUG_TX)
559
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
560
	len=strlen(str);
561

562
	while(socket_check(sock,NULL,&wr,startup->max_inactivity*1000) && wr && written<len)  {
563
		result=sendsocket(sock,str+written,len-written);
564
565
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
566
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
567
			else if(ERROR_VALUE==ECONNABORTED) 
568
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
569
			else
570
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
571
572
			return(0);
		}
573
574
575
		written+=result;
	}
	if(written != len) {
576
		lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
577
578
579
580
581
		return(0);
	}
	return(len);
}

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

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

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

678
	t=time_gm(&ti);
679
	return(t);
680
681
682
683
684
685
686
687
688
}

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) 
689
		startup->socket_open(startup->cbdata,TRUE);
690
691
	if(sock!=INVALID_SOCKET) {
		if(set_socket_options(&scfg, sock,error))
692
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708

		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) {
709
		startup->socket_open(startup->cbdata,FALSE);
710
711
712
713
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
714
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
715
716
717
718
719
	}

	return(result);
}

deuce's avatar
deuce committed
720
721
722
723
724
725
726
727
/**************************************************/
/* 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 */
/**************************************************/
728
729
static void close_request(http_session_t * session)
{
730
731
	time_t		now;

732
733
734
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
735
		listPushNode(&log_list,session->req.ld);
736
737
738
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
739

740
741
742
	listFree(&session->req.headers);
	listFree(&session->req.dynamic_heads);
	listFree(&session->req.cgi_env);
743
	FREE_AND_NULL(session->req.post_data);
744
	if(!session->req.keep_alive) {
745
		close_socket(session->socket);
746
		session->socket=INVALID_SOCKET;
747
	}
748
749
750
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
751
752
753
	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
754
755
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
756

757
758
759
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

760
	if(session->req.cleanup_file!=NULL) {
761
762
		if(!(startup->options&WEB_OPT_DEBUG_SSJS))
			remove(session->req.cleanup_file);
763
764
765
		free(session->req.cleanup_file);
	}

766
	memset(&session->req,0,sizeof(session->req));
767
768
769
770
}

static int get_header_type(char *header)
{
771
	int i;
772
773
774
775
776
777
778
779
	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
780
/* Opposite of get_header_type() */
781
782
static char *get_header(int id) 
{
783
	int i;
784
785
	if(headers[id].id==id)
		return(headers[id].text);
786
787
788
789
790
791
792
793
794

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

795
796
static const char* unknown_mime_type="application/octet-stream";

797
static const char* get_mime_type(char *ext)
798
799
800
801
{
	uint i;

	if(ext==NULL)
802
803
		return(unknown_mime_type);

804
805
806
	for(i=0;mime_types[i]!=NULL;i++)
		if(!stricmp(ext+1,mime_types[i]->name))
			return(mime_types[i]->value);
807
808

	return(unknown_mime_type);
809
810
}

811
812
/* 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) {
813
	size_t dstlen,appendlen;
814
815
816
817
818
819
820
821
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
822
823
824
825
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
826
static BOOL send_headers(http_session_t *session, const char *status)
827
{
828
	int		ret;
829
	BOOL	send_file=TRUE;
830
	time_t	ti;
831
	const char	*status_line;
832
	struct stat	stats;
833
	struct tm	tm;
834
	char	*headers;
835
	char	header[MAX_REQUEST_LINE+1];
deuce's avatar
deuce committed
836
	list_node_t	*node;
837

838
839
840
	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
841
842
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
843
		return(TRUE);
844
	}
deuce's avatar
deuce committed
845

846
	status_line=status;
847
	ret=stat(session->req.physical_path,&stats);
848
	if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
849
		status_line="304 Not Modified";
850
		ret=-1;
851
		send_file=FALSE;
852
	}
853
	if(session->req.send_location==MOVED_PERM)  {
854
		status_line="301 Moved Permanently";
855
856
857
		ret=-1;
		send_file=FALSE;
	}
858
	if(session->req.send_location==MOVED_TEMP)  {
859
		status_line="302 Moved Temporarily";
860
861
862
		ret=-1;
		send_file=FALSE;
	}
863

864
865
866
	if(session->req.ld!=NULL)
		session->req.ld->status=atoi(status_line);

867
868
	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
869
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
870
871
872
		return(FALSE);
	}
	*headers=0;
873
	/* Status-Line */
874
	safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
875
876
877

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

878
	safecat(headers,header,MAX_HEADERS_SIZE);
879
880
881

	/* General Headers */
	ti=time(NULL);
882
883
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
884
	safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
885
886
887
		,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);
888
889
	safecat(headers,header,MAX_HEADERS_SIZE);
	if(session->req.keep_alive) {
890
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
891
892
893
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
894
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
895
896
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
897
898

	/* Response Headers */
899
	safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
900
	safecat(headers,header,MAX_HEADERS_SIZE);
901
902
	
	/* Entity Headers */
903
	if(session->req.dynamic) {
904
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
905
906
907
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
908
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
909
910
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
911

912
	if(session->req.send_location) {
913
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
914
		safecat(headers,header,MAX_HEADERS_SIZE);
915
	}
916
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
917
		if(ret)  {
918
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
919
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
920
		}
921
		else  {
922
			safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
923
			safecat(headers,header,MAX_HEADERS_SIZE);
924
		}
925
	}
926

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

938
939
	if(session->req.dynamic)  {
		/* Dynamic headers */
940
		/* Set up environment */
deuce's avatar
deuce committed
941
942
		for(node=listFirstNode(&session->req.dynamic_heads);node!=NULL;node=listNextNode(node))
			safecat(headers,listNodeData(node),MAX_HEADERS_SIZE);
943
	}
944

945
	safecat(headers,"",MAX_HEADERS_SIZE);
946
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
947
	FREE_AND_NULL(headers);
948
	return(send_file);
949
950
}

951
static int sock_sendfile(SOCKET socket,char *path)
952
953
{
	int		file;
954
	long	offset=0;
955
	int		ret=0;
956

957
	if(startup->options&WEB_OPT_DEBUG_TX)
958
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
959
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
960
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
961
	else {
962
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 0) {
963
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
964
				, socket, errno, path);
965
966
			ret=0;
		}
967
968
		close(file);
	}
969
	return(ret);
970
971
}

deuce's avatar
deuce committed
972
973
974
975
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
976
static void send_error(http_session_t * session, const char* message)
977
978
{
	char	error_code[4];
979
	struct stat	sb;
980
	char	sbuf[1024];
981
	BOOL	sent_ssjs=FALSE;
982

983
	session->req.if_modified_since=0;
984
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
985
	session->req.keep_alive=FALSE;
986
	session->req.send_location=NO_LOCATION;
987
	SAFECOPY(error_code,message);
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
	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
		 */

		sprintf(sbuf,"%s%s.ssjs",error_dir,error_code);
		if(!stat(sbuf,&sb)) {
			lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket);
			if(js_setup(session)) {
				sent_ssjs=exec_ssjs(session,sbuf);
				if(sent_ssjs && send_headers(session,session->req.status)) {
					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;
				}
				else
					sent_ssjs=FALSE;
			}
		}
1015
	}
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
	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);
		}
1042
	}
1043
1044
1045
	close_request(session);
}

1046
1047
void http_logon(http_session_t * session, user_t *usr)
{
1048
1049
	char	str[128];

1050
1051
	if(usr==NULL)
		getuserdat(&scfg, &session->user);
1052
1053
	else
		session->user=*usr;
1054
1055
1056

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

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

1060
1061
1062
	if(session->subscan!=NULL)
		getmsgptrs(&scfg,session->user.number,session->subscan);

deuce's avatar
deuce committed
1063
	session->logon_time=time(NULL);
1064
	if(session->user.number==0)
1065
		SAFECOPY(session->username,unknown);
deuce's avatar
deuce committed
1066
	else {
1067
		SAFECOPY(session->username,session->user.alias);
deuce's avatar
deuce committed
1068
1069
1070
1071
		/* Adjust Connect and host */
		putuserrec(&scfg,session->user.number,U_MODEM,LEN_MODEM,"HTTP");
		putuserrec(&scfg,session->user.number,U_COMP,LEN_COMP,session->host_name);
		putuserrec(&scfg,session->user.number,U_NOTE,LEN_NOTE,session->host_ip);
1072
		putuserrec(&scfg,session->user.number,U_LOGONTIME,0,ultoa(session->logon_time,str,16));
deuce's avatar
deuce committed
1073
	}
1074
1075
1076
	session->client.user=session->username;
	client_on(session->socket, &session->client, /* update existing client record? */TRUE);

1077
1078
1079
	session->last_user_num=session->user.number;
}

1080
void http_logoff(http_session_t * session, int line)
1081
1082
1083
{
	if(session->last_user_num<=0)
		return;
1084

1085
1086
	lprintf(LOG_DEBUG,"%04d HTTP Logoff (user #%d) from line %d"
		,session->socket,session->user.number, line);
1087

1088
1089
1090
1091
1092
1093
1094
1095
	SAFECOPY(session->username,unknown);
	logoutuserdat(&scfg, &session->user, time(NULL), session->logon_time);
	memset(&session->user,0,sizeof(session->user));
	session->last_user_num=session->user.number;
}

BOOL http_checkuser(http_session_t * session)
{
1096
	if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
1097
1098
		if(session->last_js_user_num==session->user.number)
			return(TRUE);
1099
1100
1101
		lprintf(LOG_INFO,"%04d JavaScript: Initializing User Objects",session->socket);
		if(session->user.number>0) {
			if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
deuce's avatar
deuce committed
1102
				,NULL /* ftp index file */, session->subscan /* subscan */)) {