websrvr.c 69.5 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 2003 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
58
59
/*
 * 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.
 */

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

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

70
71
72
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
#include "websrvr.h"
deuce's avatar
deuce committed
73
#include "base64.h"
74

75
76
static const char*	server_name="Synchronet Web Server";
static const char*	newline="\r\n";
77
78
static const char*	http_scheme="http://";
static const size_t	http_scheme_len=7;
79
80

extern const uchar* nular;
rswindell's avatar
rswindell committed
81
82

#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
83
84
#define MAX_MIME_TYPES			128
#define MAX_REQUEST_LINE		1024
85
86
#define MAX_HEADERS_SIZE		16384	/* Maximum total size of all headers */

87
88
89
90
91
92
93
94
95
96
static scfg_t	scfg;
static BOOL		scfg_reloaded=TRUE;
static uint 	http_threads_running=0;
static ulong	active_clients=0;
static ulong	sockets=0;
static BOOL		recycle_server=FALSE;
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
static ulong	mime_count=0;
static char		revision[16];
97
98
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
99
static time_t	uptime=0;
100
static DWORD	served=0;
101
102
103
static web_startup_t* startup=NULL;

typedef struct  {
104
	char	*val;
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
	void	*next;
} linked_list;

typedef struct  {
	BOOL		method;
	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 */
	char		host[128];				/* The requested host. (virtual hosts) */
	int			send_location;
120
121
122
	const char*	mime_type;

	/* CGI parameters */
deuce's avatar
deuce committed
123
124
	char		query_str[MAX_REQUEST_LINE];

125
	linked_list*	cgi_env;
126
	linked_list*	dynamic_heads;
127
	char		status[MAX_REQUEST_LINE+1];
128
129
	char *		post_data;
	size_t		post_len;
130
	int			dynamic;
131
132
133
134

	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;

135
136
137
} http_request_t;

typedef struct  {
138
139
	SOCKET			socket;
	SOCKADDR_IN		addr;
140
	http_request_t	req;
141
142
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
143
144
	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 */
145
146
147
148
149
150
	user_t			user;

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
151
152
153
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
154
155
156
157
158
159
160
} http_session_t;

typedef struct {
	char	ext[16];
	char	type[128];
} mime_types_t;

161
static mime_types_t		mime_types[MAX_MIME_TYPES];
162
163
164
165

enum { 
	 HTTP_0_9
	,HTTP_1_0
166
	,HTTP_1_1
167
168
169
170
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
171
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
172
	,NULL	/* terminator */
173
174
175
176
177
178
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
179

rswindell's avatar
rswindell committed
180
181
182
static char* methods[] = {
	 "HEAD"
	,"GET"
183
	,"POST"
rswindell's avatar
rswindell committed
184
185
	,NULL	/* terminator */
};
186

187
188
189
190
191
192
193
enum { 
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

194
enum { 
195
196
197
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
198
199
	,HEAD_LENGTH
	,HEAD_TYPE
200
201
202
203
204
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
205
206
207
208
209
210
211
212
213
214
215
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
};

static struct {
	int		id;
	char*	text;
} headers[] = {
216
217
218
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
219
220
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
221
222
223
224
225
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
226
227
228
229
230
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
231
	{ -1,					NULL /* terminator */	},
232
233
};

234
235
236
237
enum  {
	NO_LOCATION
	,MOVED_TEMP
	,MOVED_PERM
238
	,MOVED_STAT
239
240
};

241
242
243
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"};

244
245
static DWORD monthdays[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};

246
static void respond(http_session_t * session);
247
static BOOL js_setup(http_session_t* session);
248

249
250
251
252
253
254
static time_t time_gm( struct tm* ti )  {
	time_t t;

	t=(ti->tm_year-70)*365;
	t+=(ti->tm_year-69)/4;
	t+=monthdays[ti->tm_mon];
255
256
	if(ti->tm_mon >= 2 
		&& ti->tm_year+1900%400 ? (ti->tm_year+1900%100 ? (ti->tm_year+1900%4 ? 0:1):0):1)
257
258
259
260
261
262
263
264
265
		++t;
	t += ti->tm_mday - 1;
	t = t * 24 + ti->tm_hour;
	t = t * 60 + ti->tm_min;
	t = t * 60 + ti->tm_sec;

	return t;
}

266
267
268
269
270
271
272
273
274
275
276
277
static int lprintf(char *fmt, ...)
{
	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);
278
    return(startup->lputs(startup->cbdata,LOG_INFO,sbuf));
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
static BOOL WSAInitialized=FALSE;

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

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

    lprintf("!WinSock startup ERROR %d", status);
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
309
	    startup->status(startup->cbdata,str);
310
311
312
313
314
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
315
		startup->clients(startup->cbdata,active_clients);
316
317
}

318
#if 0	/* These will be used later ToDo */
319
320
321
322
323
324
325
326
327
328
329
static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(TRUE,sock,client,update);
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(FALSE,sock,NULL,FALSE);
}
330
#endif
331
332
333
334
335

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
336
		startup->thread_up(startup->cbdata,TRUE, setuid);
337
338
339
340
341
342
343
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
344
		startup->thread_up(startup->cbdata,FALSE, FALSE);
345
346
}

347
348
static linked_list *add_list(linked_list *list,const char *value)  {
	linked_list*	entry;
349

350
351
352
353
	entry=malloc(sizeof(linked_list));
	if(entry==NULL)  {
		lprintf("Could not allocate memory for \"%s\" in linked list.",&value);
		return(list);
354
	}
355
356
357
358
359
360
361
362
363
	entry->val=malloc(strlen(value)+1);
	if(entry->val==NULL)  {
		FREE_AND_NULL(entry);
		lprintf("Could not allocate memory for \"%s\" in linked list.",&value);
		return(list);
	}
	strcpy(entry->val,value);
	entry->next=list;
	return(entry);
364
365
366
}

static void add_env(http_session_t *session, const char *name,const char *value)  {
367
368
	char	newname[129];
	char	fullname[387];
369
370
371
372
373
374
375
376
377
378
379
380
381
	char	*p;
	
	if(name==NULL || value==NULL)  {
		lprintf("%04d Attempt to set NULL env variable", session->socket);
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
382
383
384

	sprintf(fullname,"%s=%s",newname,value);
	session->req.cgi_env=add_list(session->req.cgi_env,fullname);
385
386
387
388
389
390
391
392
393
394
395
396
397
398
}

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");
	if(!strcmp(session->host_name,"<no name>"))
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
}

399
400
401
402
403
404
/*
 * Sets string str to socket sock... returns number of bytes written, or 0 on an error
 * (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?
 */
405
406
static int sockprint(SOCKET sock, const char *str)
{
407
408
409
	int len;
	int	result;
	int written=0;
410
	BOOL	wr;
411
412
413

	if(sock==INVALID_SOCKET)
		return(0);
414
415
	if(startup->options&WEB_OPT_DEBUG_TX)
		lprintf("%04d TX: %s", sock, str);
416
	len=strlen(str);
417

418
	while(socket_check(sock,NULL,&wr,60000) && wr && written<len)  {
419
		result=sendsocket(sock,str+written,len-written);
420
421
422
423
424
425
426
427
428
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
				lprintf("%04d Connection reset by peer on send",sock);
			else if(ERROR_VALUE==ECONNABORTED) 
				lprintf("%04d Connection aborted by peer on send",sock);
			else
				lprintf("%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
			return(0);
		}
429
430
431
432
433
434
435
436
437
		written+=result;
	}
	if(written != len) {
		lprintf("%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
		return(0);
	}
	return(len);
}

438
439
440
441
442
443
444
445
446
447
448
449
450
static int getmonth(char *mon)
{
	int	i;
	for(i=0;i<12;i++)
		if(!stricmp(mon,months[i]))
			return(i);

	return 0;
}

static time_t decode_date(char *date)
{
	struct	tm	ti;
451
452
	char	*token;
	time_t	t;
453
454
455
456
457
458
459
460
461
462
463
464
465

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

#if 0	/* non-standard */
	ti.tm_zone="UTC";	/* abbreviation of timezone name */
	ti.tm_gmtoff=0;		/* offset from UTC in seconds */
#endif
466
467

	token=strtok(date,",");
468
469
	if(token==NULL)
		return(0);
470
471
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
472
		/* asctime() */
473
474
		/* Toss away week day */
		token=strtok(date," ");
475
476
		if(token==NULL)
			return(0);
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
		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;
501
502
503
	}
	else  {
		/* RFC 1123 or RFC 850 */
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
		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);
528
529
530
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
531

532
	t=time_gm(&ti);
533
	return(t);
534
535
536
537
538
539
540
541
542
}

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) 
543
		startup->socket_open(startup->cbdata,TRUE);
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
	if(sock!=INVALID_SOCKET) {
		if(set_socket_options(&scfg, sock,error))
			lprintf("%04d !ERROR %s",sock,error);

		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) {
564
		startup->socket_open(startup->cbdata,FALSE);
565
566
567
568
569
570
571
572
573
574
575
576
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
			lprintf("%04d !ERROR %d closing socket",sock, ERROR_VALUE);
	}

	return(result);
}

static void close_request(http_session_t * session)
{
577
578
579
580
581
582
	linked_list	*p;
	while(session->req.dynamic_heads != NULL)  {
		FREE_AND_NULL(session->req.dynamic_heads->val);
		p=session->req.dynamic_heads->next;
		FREE_AND_NULL(session->req.dynamic_heads);
		session->req.dynamic_heads=p;
583
	}
584
585
586
587
588
589
590
	while(session->req.cgi_env != NULL)  {
		FREE_AND_NULL(session->req.cgi_env->val);
		p=session->req.cgi_env->next;
		FREE_AND_NULL(session->req.cgi_env);
		session->req.cgi_env=p;
	}
	FREE_AND_NULL(session->req.post_data);
deuce's avatar
deuce committed
591
	if(!session->req.keep_alive || session->socket==INVALID_SOCKET) {
592
		close_socket(session->socket);
593
		session->socket=INVALID_SOCKET;
594
595
596
597
598
599
		session->finished=TRUE;
	}
}

static int get_header_type(char *header)
{
600
	int i;
601
602
603
604
605
606
607
608
609
610
	for(i=0; headers[i].text!=NULL; i++) {
		if(!stricmp(header,headers[i].text)) {
			return(headers[i].id);
		}
	}
	return(-1);
}

static char *get_header(int id) 
{
611
	int i;
612
613
	if(headers[id].id==id)
		return(headers[id].text);
614
615
616
617
618
619
620
621
622

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

623
624
static const char* unknown_mime_type="application/octet-stream";

625
static const char* get_mime_type(char *ext)
626
627
628
629
{
	uint i;

	if(ext==NULL)
630
631
632
		return(unknown_mime_type);

	for(i=0;i<mime_count;i++)
633
		if(!stricmp(ext+1,mime_types[i].ext))
634
635
636
			return(mime_types[i].type);

	return(unknown_mime_type);
637
638
}

639
640
/* 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) {
641
	size_t dstlen,appendlen;
642
643
644
645
646
647
648
649
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

650
static BOOL send_headers(http_session_t *session, const char *status)
651
{
652
	int		ret;
653
	BOOL	send_file=TRUE;
654
	time_t	ti;
655
	const char	*status_line;
656
	struct stat	stats;
657
	struct tm	tm;
658
	linked_list	*p;
659
660
	char	*headers;
	char	header[MAX_REQUEST_LINE];
661

662
	status_line=status;
663
	ret=stat(session->req.physical_path,&stats);
664
665
666
667
668
	/*
	 * ToDo this always resends dynamic content... although this makes complete sense to me,
	 * I can't help but feel that this may not be required for GET requests.
	 * Look into this and revisit this section - ToDo
	 */
669
	if(!ret && (stats.st_mtime < session->req.if_modified_since) && !session->req.dynamic) {
670
		status_line="304 Not Modified";
671
		ret=-1;
672
		send_file=FALSE;
673
	}
674
	if(session->req.send_location==MOVED_PERM)  {
675
		status_line="301 Moved Permanently";
676
677
678
		ret=-1;
		send_file=FALSE;
	}
679
	if(session->req.send_location==MOVED_TEMP)  {
680
		status_line="302 Moved Temporarily";
681
682
683
		ret=-1;
		send_file=FALSE;
	}
684
685
686
687
688
689
690

	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
		lprintf("Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
691
	/* Status-Line */
692
693
	snprintf(header,MAX_REQUEST_LINE,"%s %s",http_vers[session->http_ver],status_line);
	safecat(headers,header,MAX_HEADERS_SIZE);
694
695
696

	/* General Headers */
	ti=time(NULL);
697
698
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
699
	snprintf(header,MAX_REQUEST_LINE,"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
700
701
702
		,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);
703
704
705
706
707
708
709
710
711
	safecat(headers,header,MAX_HEADERS_SIZE);
	if(session->req.keep_alive) {
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_CONNECTION),"Close");
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
712
713

	/* Response Headers */
714
715
	snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
	safecat(headers,header,MAX_HEADERS_SIZE);
716
717
	
	/* Entity Headers */
718
719
720
721
722
723
724
725
	if(session->req.dynamic) {
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
726

727
	if(session->req.send_location) {
728
729
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
		safecat(headers,header,MAX_HEADERS_SIZE);
730
	}
731
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
732
		if(ret)  {
733
734
			snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_LENGTH),"0");
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
735
		}
736
		else  {
737
738
			snprintf(header,MAX_REQUEST_LINE,"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
			safecat(headers,header,MAX_HEADERS_SIZE);
739
		}
740
	}
741

742
	if(!ret && !session->req.dynamic)  {
743
744
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
		safecat(headers,header,MAX_HEADERS_SIZE);
745
		gmtime_r(&stats.st_mtime,&tm);
746
		snprintf(header,MAX_REQUEST_LINE,"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
747
			,get_header(HEAD_LASTMODIFIED)
748
749
			,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
			,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
750
		safecat(headers,header,MAX_HEADERS_SIZE);
751
	} 
rswindell's avatar
rswindell committed
752

753
754
	if(session->req.dynamic)  {
		/* Dynamic headers */
755
		/* Set up environment */
756
		p=session->req.dynamic_heads;
757
		while(p != NULL)  {
758
			safecat(headers,p->val,MAX_HEADERS_SIZE);
759
			p=p->next;
760
		}
761
	}
762

763
	safecat(headers,"",MAX_HEADERS_SIZE);
764
765
	send_file = (sockprint(session->socket,headers) && send_file);
	free(headers);
766
	return(send_file);
767
768
769
770
771
}

static void sock_sendfile(SOCKET socket,char *path)
{
	int		file;
772
	long	offset=0;
773

774
775
	if(startup->options&WEB_OPT_DEBUG_TX)
		lprintf("%04d Sending %s",socket,path);
776
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
777
778
		lprintf("%04d !ERROR %d opening %s",socket,errno,path);
	else {
779
		if(sendfilesocket(socket, file, &offset, 0) < 1)
deuce's avatar
deuce committed
780
781
			lprintf("%04d !ERROR %d sending %s"
				, socket, errno, path);
782
783
784
785
		close(file);
	}
}

786
static void send_error(http_session_t * session, char *message)
787
788
{
	char	error_code[4];
789
	struct stat	sb;
790
	char	sbuf[1024];
791

792
	lprintf("%04d !ERROR %s",session->socket,message);
793
	session->req.keep_alive=FALSE;
794
	session->req.send_location=NO_LOCATION;
795
	SAFECOPY(error_code,message);
796
797
	sprintf(session->req.physical_path,"%s/%s.html",error_dir,error_code);
	if(session->http_ver > HTTP_0_9)  {
798
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
799
		send_headers(session,message);
800
	}
801
	if(!stat(session->req.physical_path,&sb))
802
		sock_sendfile(session->socket,session->req.physical_path);
803
	else {
804
805
		lprintf("%04d Error message file %s doesn't exist."
			,session->socket,session->req.physical_path);
806
		snprintf(sbuf,1024
807
808
809
810
811
			,"<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\">"
			"The SysOp</a></BODY></HTML>"
812
			,error_code,error_code,error_code,scfg.sys_inetaddr);
813
		sockprint(session->socket,sbuf);
814
	}
815
816
817
	close_request(session);
}

818
static BOOL check_ars(http_session_t * session)
819
820
821
822
{
	char	*username;
	char	*password;
	uchar	*ar;
823
	BOOL	authorized;
824

825
	if(session->req.auth[0]==0) {
826
827
		if(startup->options&WEB_OPT_DEBUG_RX)
			lprintf("%04d !No authentication information",session->socket);
828
		return(FALSE);
829
	}
830
831

	username=strtok(session->req.auth,":");
832
833
	if(username==NULL)
		username="";
834
835
836
	password=strtok(NULL,":");
	/* Require a password */
	if(password==NULL)
837
		password="";
838
839
	session->user.number=matchuser(&scfg, username, FALSE);
	if(session->user.number==0) {
840
841
842
843
844
845
846
847
		if(scfg.sys_misc&SM_ECHO_PW)
			lprintf("%04d !UNKNOWN USER: %s, Password: %s"
				,session->socket,username,password);
		else
			lprintf("%04d !UNKNOWN USER: %s"
				,session->socket,username);
		return(FALSE);
	}
848
849
	getuserdat(&scfg, &session->user);
	if(session->user.pass[0] && stricmp(session->user.pass,password)) {
850
		/* Should go to the hack log? */
851
852
		if(scfg.sys_misc&SM_ECHO_PW)
			lprintf("%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
853
				,session->socket,username,password,session->user.pass);
854
855
856
		else
			lprintf("%04d !PASSWORD FAILURE for user %s"
				,session->socket,username);
857
		session->user.number=0;
858
		return(FALSE);
859
	}
860
	ar = arstr(NULL,session->req.ars,&scfg);
861
	authorized=chk_ar(&scfg,ar,&session->user);
862
863
864
	if(ar!=NULL && ar!=nular)
		free(ar);

deuce's avatar
deuce committed
865
866
867
868
869
870
	if(session->req.dynamic==IS_SSJS)  {
		if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
			,NULL /* ftp index file */, NULL /* subscan */)) 
			lprintf("%04d !JavaScript ERROR creating user objects",session->socket);
	}

871
	if(authorized)  {
872
873
874
875
876
877
		if(session->req.dynamic==IS_CGI)  {
			add_env(session,"AUTH_TYPE","Basic");
			/* Should use real name if set to do so somewhere ToDo */
			add_env(session,"REMOTE_USER",session->user.alias);
		}

878
		return(TRUE);
879
	}
880
881

	/* Should go to the hack log? */
882
883
	lprintf("%04d !AUTHORIZATION FAILURE for user %s, ARS: %s"
		,session->socket,username,session->req.ars);
884

885
886
887
	return(FALSE);
}

888
static BOOL read_mime_types(char* fname)
889
890
891
892
893
894
{
	char	str[1024];
	char *	ext;
	char *	type;
	FILE*	mime_config;

895
	if((mime_config=fopen(fname,"r"))==NULL)
896
		return(FALSE);
897

898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
	while (!feof(mime_config)&&mime_count<MAX_MIME_TYPES) {
		if(fgets(str,sizeof(str),mime_config)!=NULL) {
			truncsp(str);
			ext=strtok(str," \t");
			if(ext!=NULL) {
				while(*ext && *ext<=' ') ext++;
				if(*ext!=';') {
					type=strtok(NULL," \t");
					if(type!=NULL) {
						while(*type && *type<=' ') type++;
						if(strlen(ext)>0 && strlen(type)>0) {
							SAFECOPY((mime_types[mime_count]).ext,ext);
							SAFECOPY((mime_types[mime_count++]).type,type);
						}
					}
				}
			}
		}
	}
	fclose(mime_config);
918
	lprintf("Loaded %d mime types",mime_count);
919
920
921
	return(mime_count>0);
}

922
static int sockreadline(http_session_t * session, char *buf, size_t length)
923
924
925
926
{
	char	ch;
	DWORD	i;
	BOOL	rd;
927
#if 0
928
929
930
931
	time_t	start;

	start=time(NULL);
	for(i=0;TRUE;) {
932
		if(!socket_check(session->socket,&rd,NULL,1000)) {
933
934
			session->req.keep_alive=FALSE;
			close_request(session);
935
			session->socket=INVALID_SOCKET;
936
937
938
939
			return(-1);
		}

		if(!rd) {
940
			if(time(NULL)-start>startup->max_inactivity) {
941
942
				session->req.keep_alive=FALSE;
				close_request(session);
943
				session->socket=INVALID_SOCKET;
944
945
946
947
948
				return(-1);        /* time-out */
			}
			continue;       /* no data */
		}

949
		if(recv(session->socket, &ch, 1, 0)!=1)
950
951
952
953
954
955
956
957
			break;

		if(ch=='\n')
			break;

		if(i<length)
			buf[i++]=ch;
	}
958
959
#else
	for(i=0;TRUE;) {
960
		if(!socket_check(session->socket,&rd,NULL,60000) || !rd || recv(session->socket, &ch, 1, 0)!=1)  {
961
962
963
964
965
966
967
968
969
970
971
972
973
			session->req.keep_alive=FALSE;
			close_request(session);
			session->socket=INVALID_SOCKET;
			return(-1);        /* time-out */
		}

		if(ch=='\n')
			break;

		if(i<length)
			buf[i++]=ch;
	}
#endif
974
975
976
977
978
979

	/* Terminate at length if longer */
	if(i>length)
		i=length;
		
	if(i>0 && buf[i-1]=='\r')
rswindell's avatar
rswindell committed
980
		buf[--i]=0;
981
982
983
	else
		buf[i]=0;

984
985
	if(startup->options&WEB_OPT_DEBUG_RX)
		lprintf("%04d RX: %s",session->socket,buf);
rswindell's avatar
rswindell committed
986
	return(i);
987
988
989
990
991
992
993
994
}

static int pipereadline(int pipe, char *buf, size_t length)
{
	char	ch;
	DWORD	i;
	time_t	start;

rswindell's avatar
rswindell committed
995
	start=time(NULL);
996
	for(i=0;TRUE;) {
rswindell's avatar
rswindell committed
997
998
999
1000
		if(time(NULL)-start>startup->max_cgi_inactivity) {
			return(-1);
		}
		
1001
		if(read(pipe, &ch, 1)==1)  {
rswindell's avatar
rswindell committed
1002
1003
			start=time(NULL);
			
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
			if(ch=='\n')
				break;

			if(i<length)
				buf[i++]=ch;
		}
	}

	/* Terminate at length if longer */
	if(i>length)
		i=length;
		
	if(i>0 && buf[i-1]=='\r')
rswindell's avatar
rswindell committed
1017
		buf[--i]=0;
1018
1019
1020
	else
		buf[i]=0;

rswindell's avatar
rswindell committed
1021
	return(i);
1022
1023
}

1024
1025
1026
int recvbufsocket(int sock, char *buf, long count)
{
	int		rd=0;
rswindell's avatar
rswindell committed
1027
1028
	int		i;
	time_t	start;
1029
1030
1031
1032
1033
1034

	if(count<1) {
		errno=ERANGE;
		return(0);
	}

1035
	while(rd<count && socket_check(sock,NULL,NULL,60000))  {
1036
		i=recv(sock,buf,count-rd,0);
rswindell's avatar
rswindell committed
1037
1038
1039
1040
1041
1042
1043
		if(i<=0)  {
			*buf=0;
			return(0);
		}

		rd+=i;
		start=time(NULL);
1044
1045
1046
1047
1048
1049
1050
	}

	if(rd==count)  {
		return(rd);
	}

	*buf=0;
rswindell's avatar
rswindell committed
1051
	return(0);
1052
1053
}

1054
/* Wasn't this done up as a JS thing too?  ToDo */
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
static void unescape(char *p)
{
	char *	dst;
	char	code[3];
	
	dst=p;
	for(;*p;p++) {
		if(*p=='%' && isxdigit(*(p+1)) && isxdigit(*(p+2))) {
			sprintf(code,"%.2s",p+1);
			*(dst++)=(char)strtol(code,NULL,16);
			p+=2;
		}
		else  {
			if(*p=='+')  {
				*(dst++)=' ';
			}
			else  {
				*(dst++)=*p;
			}
		}
	}
	*(dst)=0;
}

1079
1080
static void js_parse_post(http_session_t * session)  
{
1081
1082
1083
1084
1085
	char		*p;
	char		*key;
	char		*value;
	JSString*	js_str;

1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
	if(session->req.post_data == NULL)
		return;

	p=session->req.post_data;
	while((key=strtok(p,"="))!=NULL)  {
		p=NULL;
		if(key == NULL)
			continue;
		value=strtok(NULL,"&");
		if(value == NULL)
			continue;

		unescape(value);
		unescape(key);
		if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL)
			return;
		JS_DefineProperty(session->js_cx, session->js_query, key, STRING_TO_JSVAL(js_str)
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
1104
1105
1106
	}
}

1107
1108
static void js_add_header(http_session_t * session, char *key, char *value)  
{
1109
1110
1111
1112
1113
1114
1115
1116
	JSString*	js_str;

	if((js_str=JS_NewStringCopyZ(session->js_cx, value))==NULL)
		return;
	JS_DefineProperty(session->js_cx, session->js_header, key, STRING_TO_JSVAL(js_str)
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
}

1117
1118
1119
1120
1121
1122
1123
static BOOL parse_headers(http_session_t * session)
{
	char	req_line[MAX_REQUEST_LINE];
	char	next_char[2];
	char	*value;
	char	*p;
	int		i;
1124
	size_t	content_len=0;
1125
	char	env_name[128];
1126

rswindell's avatar
rswindell committed
1127
	while(sockreadline(session,req_line,sizeof(req_line))>0) {
deuce's avatar
deuce committed
1128
		/* Multi-line headers */
1129
1130
		while((recvfrom(session->socket,next_char,1,MSG_PEEK,NULL,0)>0) 
			&& (next_char[0]=='\t' || next_char[0]==' ')) {
1131
			i=strlen(req_line);
1132
			sockreadline(session,req_line+i,sizeof(req_line)-i);
1133
1134
		}
		strtok(req_line,":");
1135
		if((value=strtok(NULL,""))!=NULL) {
1136
1137
			i=get_header_type(req_line);
			while(*value && *value<=' ') value++;
1138
1139
			if(session->req.dynamic==IS_SSJS)
				js_add_header(session,req_line,value);
1140
1141
1142
1143
1144
1145
1146
			switch(i) {
				case HEAD_AUTH:
					strtok(value," ");
					p=strtok(NULL," ");
					if(p==NULL)
						break;
					while(*p && *p<' ') p++;
1147
					b64_decode(session->req.auth,sizeof(session->req.auth),p,strlen(p));
1148
1149
					break;
				case HEAD_LENGTH:
1150
					if(session->req.dynamic==IS_CGI)
1151
						add_env(session,"CONTENT_LENGTH",value);
1152
					content_len=atoi(value);
1153
1154
					break;
				case HEAD_TYPE:
1155
					if(session->req.dynamic==IS_CGI)
1156
						add_env(session,"CONTENT_TYPE",value);
1157
1158
1159
1160
1161
1162
1163
1164
					break;
				case HEAD_IFMODIFIED:
					session->req.if_modified_since=decode_date(value);
					break;
				case HEAD_CONNECTION:
					if(!stricmp(value,"Keep-Alive")) {
						session->req.keep_alive=TRUE;
					}
1165
1166
1167
					if(!stricmp(value,"Close")) {
						session->req.keep_alive=FALSE;
					}
1168
1169
					break;
				case HEAD_HOST:
1170
1171
					if(session->req.host[0]==0) {
						SAFECOPY(session->req.host,value);
1172
1173
1174
						if(startup->options&WEB_OPT_DEBUG_RX)
							lprintf("%04d Grabbing from virtual host: %s"
								,session->socket,value);
1175
					}
1176
					break;
1177
1178
1179
				default:
					break;
			}
1180
1181
1182
1183
			if(session->req.dynamic==IS_CGI)  {
				sprintf(env_name,"HTTP_%s",req_line);
				add_env(session,env_name,value);
			}
1184
1185
		}
	}
1186
	if(content_len)  {
1187
		if((session->req.post_data=malloc(content_len+1)) != NULL)  {
1188
			recvbufsocket(session->socket,session->req.post_data,content_len);
1189
			session->req.post_len=content_len;
1190
1191
1192
			if(session->req.dynamic==IS_SSJS)  {
				js_parse_post(session);
			}
1193
			session->req.post_data[content_len]=0;
1194
1195
1196
		}
		else  {
			lprintf("%04d !ERROR Allocating %d bytes of memory",session->socket,content_len);