websrvr.c 68.7 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
278
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
309
310
311
312
313
314
315
316
317
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);
    return(startup->lputs(sbuf));
}

#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)
	    startup->status(str);
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
		startup->clients(active_clients);
}

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
336
337
338
339
340
341
342
343
344
345
346

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(TRUE, setuid);
}

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

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
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
}

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) 
		startup->socket_open(TRUE);
	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) {
		startup->socket_open(FALSE);
	}
	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
641
642
643
644
645
646
647
648
649
/* 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) {
	int dstlen,appendlen;
	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);
rswindell's avatar
rswindell committed
1197
			return(FALSE);
1198
		}
1199
	}
1200
	if(session->req.dynamic==IS_CGI)
1201
		add_env(session,"SERVER_NAME",session->req.host[0] ? session->req.host : startup->host_name );
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
	return TRUE;
}

static int get_version(char *p)
{
	int		i;
	if(p==NULL)
		return(0);
	while(*p && *p<' ') p++;
	if(*p==0)
		return(0);
	for(i=1;http_vers[i]!=NULL;i++) {
		if(!stricmp(p,http_vers[i])) {
			return(i);
		}
	}
	return(i-1);
}

1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261