websrvr.c 105 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
127
static named_string_t** mime_types;

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

144
typedef struct  {
145
	int			method;
146
147
148
149
150
151
152
153
	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 */
154
155
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
156
	int			send_location;
157
	const char*	mime_type;
158
	str_list_t	headers;
159
	char		status[MAX_REQUEST_LINE+1];
160
161
	char *		post_data;
	size_t		post_len;
162
	int			dynamic;
163
	struct log_data	*ld;
164
	char		request_line[MAX_REQUEST_LINE+1];
165
	BOOL		finished;				/* Done processing request. */
166

167
168
169
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
170
171
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
172

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

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

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
198
199
200
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
201
	js_branch_t		js_branch;
deuce's avatar
deuce committed
202
	subscan_t		*subscan;
203
204
205

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

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
224

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

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

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

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

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

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

294
static void respond(http_session_t * session);
295
static BOOL js_setup(http_session_t* session);
296
static char *find_last_slash(char *str);
297
static BOOL check_extra_path(http_session_t * session);
298
static BOOL exec_ssjs(http_session_t* session, char *script);
299
static BOOL ssjs_send_headers(http_session_t* session);
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
398
399
400
401
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);
}
402

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

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
421
#define SOCKLIB_DESC WSAData.szDescription
422
423
424
425
426
427
428
static BOOL WSAInitialized=FALSE;

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

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

434
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
435
436
437
438
439
440
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
441
#define SOCKLIB_DESC NULL
442
443
444
445
446
447

#endif

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

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
454
		startup->clients(startup->cbdata,active_clients);
455
456
457
458
459
}

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

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

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

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
481
		startup->thread_up(startup->cbdata,FALSE, FALSE);
482
483
}

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

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

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
502
503
504
505
506
507
	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);
508
	strListPush(&session->req.cgi_env,p);
509
	free(p);
510
511
}

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

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

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

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

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

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

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

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

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

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

	return(result);
}

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

715
716
717
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
718
		listPushNode(&log_list,session->req.ld);
719
720
721
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
722

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

deuce's avatar
deuce committed
734
735
736
	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
737
738
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
deuce's avatar
deuce committed
739

740
741
742
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

743
744
745
746
747
748
	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]);
		}
749
750
	}

751
	memset(&session->req,0,sizeof(session->req));
752
753
754
755
}

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

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

780
781
static const char* unknown_mime_type="application/octet-stream";

782
static const char* get_mime_type(char *ext)
783
784
785
786
{
	uint i;

	if(ext==NULL)
787
788
		return(unknown_mime_type);

789
790
791
	for(i=0;mime_types[i]!=NULL;i++)
		if(!stricmp(ext+1,mime_types[i]->name))
			return(mime_types[i]->value);
792
793

	return(unknown_mime_type);
794
795
}

796
797
/* 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) {
798
	size_t dstlen,appendlen;
799
800
801
802
803
804
805
806
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
807
808
809
810
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
811
static BOOL send_headers(http_session_t *session, const char *status)
812
{
813
	int		ret;
814
	BOOL	send_file=TRUE;
815
	time_t	ti;
816
	size_t	idx;
817
	const char	*status_line;
818
	struct stat	stats;
819
	struct tm	tm;
820
	char	*headers;
821
	char	header[MAX_REQUEST_LINE+1];
822

823
824
	if(session->socket==INVALID_SOCKET)
		return(FALSE);
825
826
827
	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
828
829
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
830
		return(TRUE);
831
	}
deuce's avatar
deuce committed
832

833
	status_line=status;
834
	ret=stat(session->req.physical_path,&stats);
835
	if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
836
		status_line="304 Not Modified";
837
		ret=-1;
838
		send_file=FALSE;
839
	}
840
	if(session->req.send_location==MOVED_PERM)  {
841
		status_line=error_301;
842
843
844
		ret=-1;
		send_file=FALSE;
	}
845
	if(session->req.send_location==MOVED_TEMP)  {
846
		status_line=error_302;
847
848
849
		ret=-1;
		send_file=FALSE;
	}
850

851
852
853
	if(session->req.ld!=NULL)
		session->req.ld->status=atoi(status_line);

854
855
	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
856
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
857
858
859
		return(FALSE);
	}
	*headers=0;
860
	/* Status-Line */
861
	safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
862
863
864

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

865
	safecat(headers,header,MAX_HEADERS_SIZE);
866
867
868

	/* General Headers */
	ti=time(NULL);
869
870
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
871
	safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
872
873
874
		,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);
875
876
	safecat(headers,header,MAX_HEADERS_SIZE);
	if(session->req.keep_alive) {
877
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
878
879
880
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
881
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
882
883
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
884
885

	/* Response Headers */
886
	safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
887
	safecat(headers,header,MAX_HEADERS_SIZE);
888
889
	
	/* Entity Headers */
890
	if(session->req.dynamic) {
891
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
892
893
894
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
895
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
896
897
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
898

899
	if(session->req.send_location) {
900
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
901
		safecat(headers,header,MAX_HEADERS_SIZE);
902
	}
903
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
904
		if(ret)  {
905
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
906
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
907
		}
908
		else  {
909
			safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
910
			safecat(headers,header,MAX_HEADERS_SIZE);
911
		}
912
	}
913

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

925
926
	if(session->req.dynamic)  {
		/* Dynamic headers */
927
		/* Set up environment */
928
929
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
930
	}
931

932
	safecat(headers,"",MAX_HEADERS_SIZE);
933
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
934
	FREE_AND_NULL(headers);
935
	return(send_file);
936
937
}

938
static int sock_sendfile(SOCKET socket,char *path)
939
940
{
	int		file;
941
	long	offset=0;
942
	int		ret=0;
943

944
	if(startup->options&WEB_OPT_DEBUG_TX)
945
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
946
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
947
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
948
	else {
949
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 0) {
950
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
951
				, socket, errno, path);
952
953
			ret=0;
		}
954
955
		close(file);
	}
956
	return(ret);
957
958
}

deuce's avatar
deuce committed
959
960
961
962
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
963
static void send_error(http_session_t * session, const char* message)
964
965
{
	char	error_code[4];
966
	struct stat	sb;
967
	char	sbuf[1024];
968
	BOOL	sent_ssjs=FALSE;
969

970
971
	if(session->socket==INVALID_SOCKET)
		return;
972
	session->req.if_modified_since=0;
973
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
974
	session->req.keep_alive=FALSE;
975
	session->req.send_location=NO_LOCATION;
976
	SAFECOPY(error_code,message);
977
978
979
980
981
982
983
984
985
986
987
	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);
988
			session->req.dynamic=IS_SSJS;
989
990
			if(js_setup(session)) {
				sent_ssjs=exec_ssjs(session,sbuf);
991
				if(sent_ssjs) {
992
993
994
995
996
997
998
999
1000
					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;
				}
1001
1002
				else
					 session->req.dynamic=IS_STATIC;
1003
			}
1004
1005
			else
				session->req.dynamic=IS_STATIC;
1006
		}
1007
	}
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
	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);
		}
1034
	}
1035
	session->req.finished=TRUE;
1036
1037
}

1038
1039
void http_logon(http_session_t * session, user_t *usr)
{
1040
1041
	char	str[128];

1042
1043
	if(usr==NULL)
		getuserdat(&scfg, &session->user);
1044
1045
	else
		session->user=*usr;
1046
1047
1048

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

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

1052
1053
1054
	if(session->subscan!=NULL)
		getmsgptrs(&scfg,session->user.number,session->subscan);

deuce's avatar
deuce committed
1055
	session->logon_time=time(NULL);
1056
	if(session->user.number==0)
1057
		SAFECOPY(session->username,unknown);
deuce's avatar
deuce committed
1058
	else {
1059
		SAFECOPY(session->username,session->user.alias);