websrvr.c 75.2 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
/*
 * General notes: (ToDo stuff)
 * strtok() is used a LOT in here... notice that there is a strtok_r() for reentrant...
 * this may imply that strtok() is NOT thread-safe... if in fact it isn't this HAS
 * to be fixed before any real production-level quality is achieved with this web server
 * however, strtok_r() may not be a standard function.
 *
 * RE: not sending the headers if an nph scrpit is detected.  (The headers buffer could
 * just be free()ed and NULLed)
 *
 * Currently, all SSJS requests for a session are ran in the same context without clearing the context in
 * any way.  This behaviour should not be relied on as it may disappear in the future... this will require
 * some thought as it COULD be handy in some circumstances and COULD cause weird bugs in others.
 *
 * Dynamic content is always resent on an If-Modified-Since request... this may not be optimal behaviour
 * for GET requests...
 *
 * Should support RFC2617 Digest auth.
 *
 * Fix up all the logging stuff.
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
 *
 * SSJS stuff could work using three different methods:
 * 1) Temporary file as happens currently
 *		Advantages:
 *			Allows to keep current connection (keep-alive works)
 *			write() doesn't need to be "special"
 *		Disadvantages:
 *			Depends on the temp dir being writable and capable of holding
 *				the full reply
 *			Everything goes throug the disk, so probobly some performance
 *				penalty is involved
 *			No way of sending directly to the remote system
 * 2) nph- style
 *		Advantages:
 *			No file I/O involved
 *			Can do magic tricks (ala my perl web wrapper)
 *		Disadvantages:
 *			Pretty much everything needs to be handled by the script.
 * 3) Return body in http_reply object
 *		All the advantages of 1)
 *		Could use a special write() to make everything just great.
 *		Still doesn't allow page to be sent until fully composed (ie: long
 *			delays)
 * 4) Type three with a callback that sends the header and current body, then
 *		converts write() to send directly to remote.
deuce's avatar
deuce committed
83
84
85
86
 *
 * Add in support to pass connections through to a different webserver...
 *      probobly in access.ars... with like a simplified mod_rewrite.
 *      This would allow people to run apache and Synchronet as the same site.
87
88
 */

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

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

99
100
101
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
#include "websrvr.h"
deuce's avatar
deuce committed
102
#include "base64.h"
103

104
105
static const char*	server_name="Synchronet Web Server";
static const char*	newline="\r\n";
106
107
static const char*	http_scheme="http://";
static const size_t	http_scheme_len=7;
108
109
static const char*	error_404="404 Not Found";
static const char*	error_500="500 Internal Server Error";
110
111

extern const uchar* nular;
rswindell's avatar
rswindell committed
112
113

#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
114
115
#define MAX_MIME_TYPES			128
#define MAX_REQUEST_LINE		1024
116
117
#define MAX_HEADERS_SIZE		16384	/* Maximum total size of all headers */

118
119
120
121
122
static scfg_t	scfg;
static BOOL		scfg_reloaded=TRUE;
static uint 	http_threads_running=0;
static ulong	active_clients=0;
static ulong	sockets=0;
123
static BOOL		terminate_server=FALSE;
124
125
126
127
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
static ulong	mime_count=0;
static char		revision[16];
128
129
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
130
static char		cgi_dir[MAX_PATH+1];
131
static time_t	uptime=0;
132
static DWORD	served=0;
133
static web_startup_t* startup=NULL;
134
static js_server_props_t js_server_props;
135
136

typedef struct  {
137
	char	*val;
138
139
140
141
	void	*next;
} linked_list;

typedef struct  {
142
	int			method;
143
144
145
146
147
148
149
150
151
152
	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;
153
154
155
	const char*	mime_type;

	/* CGI parameters */
deuce's avatar
deuce committed
156
	char		query_str[MAX_REQUEST_LINE];
157
	char		extra_path_info[MAX_REQUEST_LINE];
deuce's avatar
deuce committed
158

159
	linked_list*	cgi_env;
160
	linked_list*	dynamic_heads;
161
	char		status[MAX_REQUEST_LINE+1];
162
163
	char *		post_data;
	size_t		post_len;
164
	int			dynamic;
165
166
167
168

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

169
170
171
} http_request_t;

typedef struct  {
172
173
	SOCKET			socket;
	SOCKADDR_IN		addr;
174
	http_request_t	req;
175
176
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
177
178
	int				http_ver;       /* HTTP version.  0 = HTTP/0.9, 1=HTTP/1.0, 2=HTTP/1.1 */
	BOOL			finished;		/* Do not accept any more imput from client */
179
180
181
182
183
184
	user_t			user;

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
185
186
187
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
188
189
190
191
192
193
194
} http_session_t;

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

195
static mime_types_t		mime_types[MAX_MIME_TYPES];
196
197
198
199

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
213

rswindell's avatar
rswindell committed
214
215
216
static char* methods[] = {
	 "HEAD"
	,"GET"
217
	,"POST"
rswindell's avatar
rswindell committed
218
219
	,NULL	/* terminator */
};
220

221
222
223
224
225
226
227
enum { 
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

228
enum { 
229
230
231
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
232
233
	,HEAD_LENGTH
	,HEAD_TYPE
234
235
236
237
238
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
239
240
241
242
243
244
245
246
247
248
249
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
};

static struct {
	int		id;
	char*	text;
} headers[] = {
250
251
252
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
253
254
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
255
256
257
258
259
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
260
261
262
263
264
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
265
	{ -1,					NULL /* terminator */	},
266
267
};

268
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
269
270
271
enum  {
	NO_LOCATION
	,MOVED_PERM
272
	,MOVED_TEMP
273
	,MOVED_STAT
274
275
};

276
277
278
/* Max. times to follow internal redirects for a single request */
#define MAX_REDIR_LOOPS	20

279
280
281
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"};

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

284
static void respond(http_session_t * session);
285
static BOOL js_setup(http_session_t* session);
286
static char *find_last_slash(char *str);
287

288
289
290
291
292
293
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];
294
295
	if(ti->tm_mon >= 2 
		&& ti->tm_year+1900%400 ? (ti->tm_year+1900%100 ? (ti->tm_year+1900%4 ? 0:1):0):1)
296
297
298
299
300
301
302
303
304
		++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;
}

305
static int lprintf(int level, char *fmt, ...)
306
307
308
309
310
311
312
313
314
315
316
{
	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);
317
    return(startup->lputs(startup->cbdata,level,sbuf));
318
319
320
321
322
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
323
#define SOCKLIB_DESC WSAData.szDescription
324
325
326
327
328
329
330
static BOOL WSAInitialized=FALSE;

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

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

336
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
337
338
339
340
341
342
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
343
#define SOCKLIB_DESC NULL
344
345
346
347
348
349

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
350
	    startup->status(startup->cbdata,str);
351
352
353
354
355
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
356
		startup->clients(startup->cbdata,active_clients);
357
358
}

359
#if 0	/* These will be used later ToDo */
360
361
362
363
364
365
366
367
368
369
370
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);
}
371
#endif
372
373
374
375
376

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
377
		startup->thread_up(startup->cbdata,TRUE, setuid);
378
379
380
381
382
383
384
}

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

388
389
static linked_list *add_list(linked_list *list,const char *value)  {
	linked_list*	entry;
390

391
392
	entry=malloc(sizeof(linked_list));
	if(entry==NULL)  {
393
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
394
		return(list);
395
	}
396
397
398
	entry->val=malloc(strlen(value)+1);
	if(entry->val==NULL)  {
		FREE_AND_NULL(entry);
399
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
400
401
402
403
404
		return(list);
	}
	strcpy(entry->val,value);
	entry->next=list;
	return(entry);
405
406
407
}

static void add_env(http_session_t *session, const char *name,const char *value)  {
408
409
	char	newname[129];
	char	fullname[387];
410
411
412
	char	*p;
	
	if(name==NULL || value==NULL)  {
413
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
414
415
416
417
418
419
420
421
422
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
423
424
425

	sprintf(fullname,"%s=%s",newname,value);
	session->req.cgi_env=add_list(session->req.cgi_env,fullname);
426
427
428
429
430
431
432
433
434
435
436
437
438
439
}

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);
}

440
441
442
443
444
445
/*
 * 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?
 */
446
447
static int sockprint(SOCKET sock, const char *str)
{
448
449
450
	int len;
	int	result;
	int written=0;
451
	BOOL	wr;
452
453
454

	if(sock==INVALID_SOCKET)
		return(0);
455
	if(startup->options&WEB_OPT_DEBUG_TX)
456
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
457
	len=strlen(str);
458

459
	while(socket_check(sock,NULL,&wr,60000) && wr && written<len)  {
460
		result=sendsocket(sock,str+written,len-written);
461
462
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
463
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
464
			else if(ERROR_VALUE==ECONNABORTED) 
465
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
466
			else
467
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
468
469
			return(0);
		}
470
471
472
		written+=result;
	}
	if(written != len) {
473
		lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
474
475
476
477
478
		return(0);
	}
	return(len);
}

479
480
481
482
483
484
485
486
487
488
489
490
491
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;
492
493
	char	*token;
	time_t	t;
494
495
496
497
498
499
500
501
502
503
504
505
506

	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
507
508

	token=strtok(date,",");
509
510
	if(token==NULL)
		return(0);
511
512
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
513
		/* asctime() */
514
515
		/* Toss away week day */
		token=strtok(date," ");
516
517
		if(token==NULL)
			return(0);
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
		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;
542
543
544
	}
	else  {
		/* RFC 1123 or RFC 850 */
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
		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);
569
570
571
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
572

573
	t=time_gm(&ti);
574
	return(t);
575
576
577
578
579
580
581
582
583
}

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) 
584
		startup->socket_open(startup->cbdata,TRUE);
585
586
	if(sock!=INVALID_SOCKET) {
		if(set_socket_options(&scfg, sock,error))
587
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604

		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) {
605
		startup->socket_open(startup->cbdata,FALSE);
606
607
608
609
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
610
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
611
612
613
614
615
616
617
	}

	return(result);
}

static void close_request(http_session_t * session)
{
618
619
620
621
622
623
	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;
624
	}
625
626
627
628
629
630
631
	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
632
	if(!session->req.keep_alive || session->socket==INVALID_SOCKET) {
633
		close_socket(session->socket);
634
		session->socket=INVALID_SOCKET;
635
636
637
638
639
640
		session->finished=TRUE;
	}
}

static int get_header_type(char *header)
{
641
	int i;
642
643
644
645
646
647
648
649
650
651
	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) 
{
652
	int i;
653
654
	if(headers[id].id==id)
		return(headers[id].text);
655
656
657
658
659
660
661
662
663

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

664
665
static const char* unknown_mime_type="application/octet-stream";

666
static const char* get_mime_type(char *ext)
667
668
669
670
{
	uint i;

	if(ext==NULL)
671
672
673
		return(unknown_mime_type);

	for(i=0;i<mime_count;i++)
674
		if(!stricmp(ext+1,mime_types[i].ext))
675
676
677
			return(mime_types[i].type);

	return(unknown_mime_type);
678
679
}

680
681
/* 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) {
682
	size_t dstlen,appendlen;
683
684
685
686
687
688
689
690
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

691
static BOOL send_headers(http_session_t *session, const char *status)
692
{
693
	int		ret;
694
	BOOL	send_file=TRUE;
695
	time_t	ti;
696
	const char	*status_line;
697
	struct stat	stats;
698
	struct tm	tm;
699
	linked_list	*p;
700
701
	char	*headers;
	char	header[MAX_REQUEST_LINE];
702

703
	status_line=status;
704
	ret=stat(session->req.physical_path,&stats);
705
706
707
708
709
	/*
	 * 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
	 */
710
	if(!ret && (stats.st_mtime < session->req.if_modified_since) && !session->req.dynamic) {
711
		status_line="304 Not Modified";
712
		ret=-1;
713
		send_file=FALSE;
714
	}
715
	if(session->req.send_location==MOVED_PERM)  {
716
		status_line="301 Moved Permanently";
717
718
719
		ret=-1;
		send_file=FALSE;
	}
720
	if(session->req.send_location==MOVED_TEMP)  {
721
		status_line="302 Moved Temporarily";
722
723
724
		ret=-1;
		send_file=FALSE;
	}
725
726
727

	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
728
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
729
730
731
		return(FALSE);
	}
	*headers=0;
732
	/* Status-Line */
733
734
	snprintf(header,MAX_REQUEST_LINE,"%s %s",http_vers[session->http_ver],status_line);
	safecat(headers,header,MAX_HEADERS_SIZE);
735
736
737

	/* General Headers */
	ti=time(NULL);
738
739
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
740
	snprintf(header,MAX_REQUEST_LINE,"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
741
742
743
		,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);
744
745
746
747
748
749
750
751
752
	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);
	}
753
754

	/* Response Headers */
755
756
	snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
	safecat(headers,header,MAX_HEADERS_SIZE);
757
758
	
	/* Entity Headers */
759
760
761
762
763
764
765
766
	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);
	}
767

768
	if(session->req.send_location) {
769
770
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
		safecat(headers,header,MAX_HEADERS_SIZE);
771
	}
772
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
773
		if(ret)  {
774
775
			snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_LENGTH),"0");
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
776
		}
777
		else  {
778
779
			snprintf(header,MAX_REQUEST_LINE,"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
			safecat(headers,header,MAX_HEADERS_SIZE);
780
		}
781
	}
782

783
	if(!ret && !session->req.dynamic)  {
784
785
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
		safecat(headers,header,MAX_HEADERS_SIZE);
786
		gmtime_r(&stats.st_mtime,&tm);
787
		snprintf(header,MAX_REQUEST_LINE,"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
788
			,get_header(HEAD_LASTMODIFIED)
789
790
			,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
			,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
791
		safecat(headers,header,MAX_HEADERS_SIZE);
792
	} 
rswindell's avatar
rswindell committed
793

794
795
	if(session->req.dynamic)  {
		/* Dynamic headers */
796
		/* Set up environment */
797
		p=session->req.dynamic_heads;
798
		while(p != NULL)  {
799
			safecat(headers,p->val,MAX_HEADERS_SIZE);
800
			p=p->next;
801
		}
802
	}
803

804
	safecat(headers,"",MAX_HEADERS_SIZE);
805
806
	send_file = (sockprint(session->socket,headers) && send_file);
	free(headers);
807
	return(send_file);
808
809
810
811
812
}

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

815
	if(startup->options&WEB_OPT_DEBUG_TX)
816
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
817
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
818
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
819
	else {
820
		if(sendfilesocket(socket, file, &offset, 0) < 1)
821
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
822
				, socket, errno, path);
823
824
825
826
		close(file);
	}
}

827
static void send_error(http_session_t * session, const char* message)
828
829
{
	char	error_code[4];
830
	struct stat	sb;
831
	char	sbuf[1024];
832

833
	lprintf(LOG_INFO,"%04d !ERROR %s",session->socket,message);
834
	session->req.keep_alive=FALSE;
835
	session->req.send_location=NO_LOCATION;
836
	SAFECOPY(error_code,message);
837
	sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
838
	if(session->http_ver > HTTP_0_9)  {
839
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
840
		send_headers(session,message);
841
	}
842
	if(!stat(session->req.physical_path,&sb))
843
		sock_sendfile(session->socket,session->req.physical_path);
844
	else {
845
		lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist."
846
			,session->socket,session->req.physical_path);
847
		snprintf(sbuf,1024
848
849
850
			,"<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>"
851
852
853
			"please notify <a href=\"mailto:sysop@%s\">"
			"%s</a></BODY></HTML>"
			,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
854
		sockprint(session->socket,sbuf);
855
	}
856
857
858
	close_request(session);
}

859
static BOOL check_ars(http_session_t * session)
860
861
862
863
{
	char	*username;
	char	*password;
	uchar	*ar;
864
	BOOL	authorized;
865
	char	auth_req[MAX_REQUEST_LINE];
866

867
	if(session->req.auth[0]==0) {
868
		if(startup->options&WEB_OPT_DEBUG_RX)
869
			lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket);
870
		return(FALSE);
871
	}
872
	SAFECOPY(auth_req,session->req.auth);
873

874
	username=strtok(auth_req,":");
875
876
	if(username==NULL)
		username="";
877
878
879
	password=strtok(NULL,":");
	/* Require a password */
	if(password==NULL)
880
		password="";
881
882
	session->user.number=matchuser(&scfg, username, FALSE);
	if(session->user.number==0) {
883
		if(scfg.sys_misc&SM_ECHO_PW)
884
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s"
885
886
				,session->socket,username,password);
		else
887
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s"
888
889
890
				,session->socket,username);
		return(FALSE);
	}
891
892
	getuserdat(&scfg, &session->user);
	if(session->user.pass[0] && stricmp(session->user.pass,password)) {
893
		/* Should go to the hack log? */
894
		if(scfg.sys_misc&SM_ECHO_PW)
895
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
896
				,session->socket,username,password,session->user.pass);
897
		else
898
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
899
				,session->socket,username);
900
		session->user.number=0;
901
		return(FALSE);
902
	}
903
	ar = arstr(NULL,session->req.ars,&scfg);
904
	authorized=chk_ar(&scfg,ar,&session->user);
905
906
907
	if(ar!=NULL && ar!=nular)
		free(ar);

deuce's avatar
deuce committed
908
909
910
	if(session->req.dynamic==IS_SSJS)  {
		if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
			,NULL /* ftp index file */, NULL /* subscan */)) 
911
			lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
deuce's avatar
deuce committed
912
913
	}

914
	if(authorized)  {
915
		if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)  {
916
917
918
919
920
			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);
		}

921
		return(TRUE);
922
	}
923
924

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

928
929
930
	return(FALSE);
}

931
static BOOL read_mime_types(char* fname)
932
933
934
935
936
937
{
	char	str[1024];
	char *	ext;
	char *	type;
	FILE*	mime_config;

938
	if((mime_config=fopen(fname,"r"))==NULL)
939
		return(FALSE);
940

941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
	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);
961
	lprintf(LOG_DEBUG,"Loaded %d mime types",mime_count);
962
963
964
	return(mime_count>0);
}

965
static int sockreadline(http_session_t * session, char *buf, size_t length)
966
967
968
969
{
	char	ch;
	DWORD	i;
	BOOL	rd;
970
#if 0
971
972
973
974
	time_t	start;

	start=time(NULL);
	for(i=0;TRUE;) {
975
		if(!socket_check(session->socket,&rd,NULL,1000)) {
976
977
			session->req.keep_alive=FALSE;
			close_request(session);
978
			session->socket=INVALID_SOCKET;
979
980
981
982
			return(-1);
		}

		if(!rd) {
983
			if(time(NULL)-start>startup->max_inactivity) {
984
985
				session->req.keep_alive=FALSE;
				close_request(session);
986
				session->socket=INVALID_SOCKET;
987
988
989
990
991
				return(-1);        /* time-out */
			}
			continue;       /* no data */
		}

992
		if(recv(session->socket, &ch, 1, 0)!=1)
993
994
995
996
997
998
999
1000
			break;

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

		if(i<length)
			buf[i++]=ch;
	}
1001
1002
#else
	for(i=0;TRUE;) {
1003
		if(!socket_check(session->socket,&rd,NULL,60000) || !rd || recv(session->socket, &ch, 1, 0)!=1)  {
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
			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
1017
1018
1019
1020
1021
1022

	/* Terminate at length if longer */
	if(i>length)
		i=length;
		
	if(i>0 && buf[i-1]=='\r')
rswindell's avatar
rswindell committed
1023
		buf[--i]=0;
1024
1025
1026
	else
		buf[i]=0;

1027
	if(startup->options&WEB_OPT_DEBUG_RX)
1028
		lprintf(LOG_DEBUG,"%04d RX: %s",session->socket,buf);
rswindell's avatar
rswindell committed
1029
	return(i);
1030
1031
1032
1033
1034
1035
1036
1037
}

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

rswindell's avatar
rswindell committed
1038
	start=time(NULL);
1039
	for(i=0;TRUE;) {
rswindell's avatar
rswindell committed
1040
1041
1042
1043
		if(time(NULL)-start>startup->max_cgi_inactivity) {
			return(-1);
		}
		
1044
		if(read(pipe, &ch, 1)==1)  {
rswindell's avatar
rswindell committed
1045
1046
			start=time(NULL);
			
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
			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
1060
		buf[--i]=0;
1061
1062
1063
	else
		buf[i]=0;

rswindell's avatar
rswindell committed
1064
	return(i);
1065
1066
}

1067
1068
1069
int recvbufsocket(int sock, char *buf, long count)
{
	int		rd=0;
rswindell's avatar
rswindell committed
1070
1071
	int		i;
	time_t	start;
1072
1073
1074
1075
1076
1077

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

1078
	while(rd<count && socket_check(sock,NULL,NULL,60000))  {
1079
		i=recv(sock,buf,count-rd,0);
rswindell's avatar
rswindell committed
1080
1081
1082
1083
1084
1085
1086
		if(i<=0)  {
			*buf=0;
			return(0);
		}

		rd+=i;
		start=time(NULL);
1087
1088
1089
1090
1091
1092
1093
	}

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

	*buf=0;
rswindell's avatar
rswindell committed
1094
	return(0);
1095
1096
}

1097
/* Wasn't this done up as a JS thing too?  ToDo */
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
static void unescape(char *p)
{
	char *	dst;
	char	code[3];
	
	dst=p;
	for(;