websrvr.c 79.1 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
#include "link_list.h"
100
101
#include "sbbs.h"
#include "sockwrap.h"		/* sendfilesocket() */
102
#include "semwrap.h"
103
#include "websrvr.h"
deuce's avatar
deuce committed
104
#include "base64.h"
105

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

extern const uchar* nular;
rswindell's avatar
rswindell committed
114
115

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

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

138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/* Logging stuff */
sem_t	log_sem;
pthread_mutex_t	log_mutex;
link_list_t	*log_list;
struct log_data {
	char	*hostname;
	char	*ident;
	char	*user;
	char	*request;
	char	*referrer;
	char	*agent;
	int		status;
	unsigned int	size;
	struct tm completed;
};

154
typedef struct  {
155
	char	*val;
156
157
158
159
	void	*next;
} linked_list;

typedef struct  {
160
	int			method;
161
162
163
164
165
166
167
168
169
170
	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;
171
172
173
	const char*	mime_type;

	/* CGI parameters */
deuce's avatar
deuce committed
174
	char		query_str[MAX_REQUEST_LINE];
175
	char		extra_path_info[MAX_REQUEST_LINE];
deuce's avatar
deuce committed
176

177
	linked_list*	cgi_env;
178
	linked_list*	dynamic_heads;
179
	char		status[MAX_REQUEST_LINE+1];
180
181
	char *		post_data;
	size_t		post_len;
182
	int			dynamic;
183
	struct log_data	*ld;
184
185
186
187

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

188
189
190
} http_request_t;

typedef struct  {
191
192
	SOCKET			socket;
	SOCKADDR_IN		addr;
193
	http_request_t	req;
194
195
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
196
197
	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 */
198
	user_t			user;	
199
200
201
202
203

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
204
205
206
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
207
208
209
210
211
212
213
} http_session_t;

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

214
static mime_types_t		mime_types[MAX_MIME_TYPES];
215
216
217
218

enum { 
	 HTTP_0_9
	,HTTP_1_0
219
	,HTTP_1_1
220
221
222
223
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
224
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
225
	,NULL	/* terminator */
226
227
228
229
230
231
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
232

rswindell's avatar
rswindell committed
233
234
235
static char* methods[] = {
	 "HEAD"
	,"GET"
236
	,"POST"
rswindell's avatar
rswindell committed
237
238
	,NULL	/* terminator */
};
239

240
241
242
243
244
245
246
enum { 
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

247
enum { 
248
249
250
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
251
252
	,HEAD_LENGTH
	,HEAD_TYPE
253
254
255
256
257
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
258
259
260
261
262
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
263
264
	,HEAD_REFERER
	,HEAD_AGENT
265
266
267
268
269
270
};

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

291
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
292
293
294
enum  {
	NO_LOCATION
	,MOVED_PERM
295
	,MOVED_TEMP
296
	,MOVED_STAT
297
298
};

299
300
301
/* Max. times to follow internal redirects for a single request */
#define MAX_REDIR_LOOPS	20

302
303
304
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"};

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

307
static void respond(http_session_t * session);
308
static BOOL js_setup(http_session_t* session);
309
static char *find_last_slash(char *str);
310

311
312
313
314
315
316
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];
317
318
	if(ti->tm_mon >= 2 
		&& ti->tm_year+1900%400 ? (ti->tm_year+1900%100 ? (ti->tm_year+1900%4 ? 0:1):0):1)
319
320
321
322
323
324
325
326
327
		++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;
}

328
static int lprintf(int level, char *fmt, ...)
329
330
331
332
333
334
335
336
337
338
339
{
	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);
340
    return(startup->lputs(startup->cbdata,level,sbuf));
341
342
343
344
345
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
346
#define SOCKLIB_DESC WSAData.szDescription
347
348
349
350
351
352
353
static BOOL WSAInitialized=FALSE;

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

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

359
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
360
361
362
363
364
365
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
366
#define SOCKLIB_DESC NULL
367
368
369
370
371
372

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
373
	    startup->status(startup->cbdata,str);
374
375
376
377
378
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
379
		startup->clients(startup->cbdata,active_clients);
380
381
}

382
#if 0	/* These will be used later ToDo */
383
384
385
386
387
388
389
390
391
392
393
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);
}
394
#endif
395
396
397
398
399

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
400
		startup->thread_up(startup->cbdata,TRUE, setuid);
401
402
403
404
405
406
407
}

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

411
412
static linked_list *add_list(linked_list *list,const char *value)  {
	linked_list*	entry;
413

414
415
	entry=malloc(sizeof(linked_list));
	if(entry==NULL)  {
416
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
417
		return(list);
418
	}
419
420
421
	entry->val=malloc(strlen(value)+1);
	if(entry->val==NULL)  {
		FREE_AND_NULL(entry);
422
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
423
424
425
426
427
		return(list);
	}
	strcpy(entry->val,value);
	entry->next=list;
	return(entry);
428
429
430
}

static void add_env(http_session_t *session, const char *name,const char *value)  {
431
432
	char	newname[129];
	char	fullname[387];
433
434
435
	char	*p;
	
	if(name==NULL || value==NULL)  {
436
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
437
438
439
440
441
442
443
444
445
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
446
447
448

	sprintf(fullname,"%s=%s",newname,value);
	session->req.cgi_env=add_list(session->req.cgi_env,fullname);
449
450
451
452
453
454
455
456
457
}

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");
458
	if(!strcmp(session->host_name,session->host_ip))
459
460
461
462
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
}

463
464
465
466
467
468
/*
 * 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?
 */
469
470
static int sockprint(SOCKET sock, const char *str)
{
471
472
473
	int len;
	int	result;
	int written=0;
474
	BOOL	wr;
475
476
477

	if(sock==INVALID_SOCKET)
		return(0);
478
	if(startup->options&WEB_OPT_DEBUG_TX)
479
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
480
	len=strlen(str);
481

482
	while(socket_check(sock,NULL,&wr,60000) && wr && written<len)  {
483
		result=sendsocket(sock,str+written,len-written);
484
485
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
486
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
487
			else if(ERROR_VALUE==ECONNABORTED) 
488
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
489
			else
490
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
491
492
			return(0);
		}
493
494
495
		written+=result;
	}
	if(written != len) {
496
		lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
497
498
499
500
501
		return(0);
	}
	return(len);
}

502
503
504
505
506
507
508
509
510
511
512
513
514
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;
515
516
	char	*token;
	time_t	t;
517
518
519
520
521
522
523
524
525
526
527
528
529

	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
530
531

	token=strtok(date,",");
532
533
	if(token==NULL)
		return(0);
534
535
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
536
		/* asctime() */
537
538
		/* Toss away week day */
		token=strtok(date," ");
539
540
		if(token==NULL)
			return(0);
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
		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;
565
566
567
	}
	else  {
		/* RFC 1123 or RFC 850 */
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
		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);
592
593
594
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
595

596
	t=time_gm(&ti);
597
	return(t);
598
599
600
601
602
603
604
605
606
}

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) 
607
		startup->socket_open(startup->cbdata,TRUE);
608
609
	if(sock!=INVALID_SOCKET) {
		if(set_socket_options(&scfg, sock,error))
610
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627

		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) {
628
		startup->socket_open(startup->cbdata,FALSE);
629
630
631
632
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
633
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
634
635
636
637
638
639
640
	}

	return(result);
}

static void close_request(http_session_t * session)
{
641
	linked_list	*p;
642
643
644
645
646
647
648
649
650
	time_t		now;

	now=time(NULL);
	localtime_r(&now,&session->req.ld->completed);
	pthread_mutex_lock(&log_mutex);
	listPushNode(log_list,session->req.ld);
	pthread_mutex_unlock(&log_mutex);
	sem_post(&log_sem);

651
652
653
654
655
	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;
656
	}
657
658
659
660
661
662
663
	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
664
	if(!session->req.keep_alive || session->socket==INVALID_SOCKET) {
665
		close_socket(session->socket);
666
		session->socket=INVALID_SOCKET;
667
668
669
670
671
672
		session->finished=TRUE;
	}
}

static int get_header_type(char *header)
{
673
	int i;
674
675
676
677
678
679
680
681
682
683
	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) 
{
684
	int i;
685
686
	if(headers[id].id==id)
		return(headers[id].text);
687
688
689
690
691
692
693
694
695

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

696
697
static const char* unknown_mime_type="application/octet-stream";

698
static const char* get_mime_type(char *ext)
699
700
701
702
{
	uint i;

	if(ext==NULL)
703
704
705
		return(unknown_mime_type);

	for(i=0;i<mime_count;i++)
706
		if(!stricmp(ext+1,mime_types[i].ext))
707
708
709
			return(mime_types[i].type);

	return(unknown_mime_type);
710
711
}

712
713
/* 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) {
714
	size_t dstlen,appendlen;
715
716
717
718
719
720
721
722
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

723
static BOOL send_headers(http_session_t *session, const char *status)
724
{
725
	int		ret;
726
	BOOL	send_file=TRUE;
727
	time_t	ti;
728
	const char	*status_line;
729
	struct stat	stats;
730
	struct tm	tm;
731
	linked_list	*p;
732
733
	char	*headers;
	char	header[MAX_REQUEST_LINE];
734

735
	status_line=status;
736
	ret=stat(session->req.physical_path,&stats);
737
738
739
740
741
	/*
	 * 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
	 */
742
	if(!ret && (stats.st_mtime < session->req.if_modified_since) && !session->req.dynamic) {
743
		status_line="304 Not Modified";
744
		ret=-1;
745
		send_file=FALSE;
746
	}
747
	if(session->req.send_location==MOVED_PERM)  {
748
		status_line="301 Moved Permanently";
749
750
751
		ret=-1;
		send_file=FALSE;
	}
752
	if(session->req.send_location==MOVED_TEMP)  {
753
		status_line="302 Moved Temporarily";
754
755
756
		ret=-1;
		send_file=FALSE;
	}
757
758
759

	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
760
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
761
762
763
		return(FALSE);
	}
	*headers=0;
764
	/* Status-Line */
765
766
	snprintf(header,MAX_REQUEST_LINE,"%s %s",http_vers[session->http_ver],status_line);
	safecat(headers,header,MAX_HEADERS_SIZE);
767
768
769

	/* General Headers */
	ti=time(NULL);
770
771
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
772
	snprintf(header,MAX_REQUEST_LINE,"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
773
774
775
		,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);
776
777
778
779
780
781
782
783
784
	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);
	}
785
786

	/* Response Headers */
787
788
	snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
	safecat(headers,header,MAX_HEADERS_SIZE);
789
790
	
	/* Entity Headers */
791
792
793
794
795
796
797
798
	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);
	}
799

800
	if(session->req.send_location) {
801
802
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
		safecat(headers,header,MAX_HEADERS_SIZE);
803
	}
804
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
805
		if(ret)  {
806
807
			snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_LENGTH),"0");
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
808
		}
809
		else  {
810
811
			snprintf(header,MAX_REQUEST_LINE,"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
			safecat(headers,header,MAX_HEADERS_SIZE);
812
		}
813
	}
814

815
	if(!ret && !session->req.dynamic)  {
816
817
		snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
		safecat(headers,header,MAX_HEADERS_SIZE);
818
		gmtime_r(&stats.st_mtime,&tm);
819
		snprintf(header,MAX_REQUEST_LINE,"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
820
			,get_header(HEAD_LASTMODIFIED)
821
822
			,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
			,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
823
		safecat(headers,header,MAX_HEADERS_SIZE);
824
	} 
rswindell's avatar
rswindell committed
825

826
827
	if(session->req.dynamic)  {
		/* Dynamic headers */
828
		/* Set up environment */
829
		p=session->req.dynamic_heads;
830
		while(p != NULL)  {
831
			safecat(headers,p->val,MAX_HEADERS_SIZE);
832
			p=p->next;
833
		}
834
	}
835

836
	safecat(headers,"",MAX_HEADERS_SIZE);
837
838
	send_file = (sockprint(session->socket,headers) && send_file);
	free(headers);
839
	return(send_file);
840
841
}

842
static int sock_sendfile(SOCKET socket,char *path)
843
844
{
	int		file;
845
	long	offset=0;
846
	int		ret=0;
847

848
	if(startup->options&WEB_OPT_DEBUG_TX)
849
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
850
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
851
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
852
	else {
853
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 1) {
854
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
855
				, socket, errno, path);
856
857
			ret=0;
		}
858
859
		close(file);
	}
860
	return(ret);
861
862
}

863
static void send_error(http_session_t * session, const char* message)
864
865
{
	char	error_code[4];
866
	struct stat	sb;
867
	char	sbuf[1024];
868

869
	lprintf(LOG_INFO,"%04d !ERROR %s",session->socket,message);
870
	session->req.keep_alive=FALSE;
871
	session->req.send_location=NO_LOCATION;
872
	SAFECOPY(error_code,message);
873
	sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
874
	session->req.ld->status=atoi(message);
875
	if(session->http_ver > HTTP_0_9)  {
876
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
877
		send_headers(session,message);
878
	}
879
880
881
882
883
	if(!stat(session->req.physical_path,&sb)) {
		session->req.ld->size=sock_sendfile(session->socket,session->req.physical_path);
		if(session->req.ld->size<0)
			session->req.ld->size=0;
	}
884
	else {
885
		lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist."
886
			,session->socket,session->req.physical_path);
887
		snprintf(sbuf,1024
888
889
890
			,"<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>"
891
892
893
			"please notify <a href=\"mailto:sysop@%s\">"
			"%s</a></BODY></HTML>"
			,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
894
		sockprint(session->socket,sbuf);
895
896
		session->req.ld->size=strlen(sbuf);
	}
897
898
899
	close_request(session);
}

900
static BOOL check_ars(http_session_t * session)
901
902
903
904
{
	char	*username;
	char	*password;
	uchar	*ar;
905
	BOOL	authorized;
906
	char	auth_req[MAX_REQUEST_LINE];
907

908
	if(session->req.auth[0]==0) {
909
		if(startup->options&WEB_OPT_DEBUG_RX)
910
			lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket);
911
		return(FALSE);
912
	}
913
	SAFECOPY(auth_req,session->req.auth);
914

915
	username=strtok(auth_req,":");
916
917
	if(username==NULL)
		username="";
918
919
920
	password=strtok(NULL,":");
	/* Require a password */
	if(password==NULL)
921
		password="";
922
923
	session->user.number=matchuser(&scfg, username, FALSE);
	if(session->user.number==0) {
924
		if(scfg.sys_misc&SM_ECHO_PW)
925
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s"
926
927
				,session->socket,username,password);
		else
928
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s"
929
930
931
				,session->socket,username);
		return(FALSE);
	}
932
933
	getuserdat(&scfg, &session->user);
	if(session->user.pass[0] && stricmp(session->user.pass,password)) {
934
		/* Should go to the hack log? */
935
		if(scfg.sys_misc&SM_ECHO_PW)
936
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
937
				,session->socket,username,password,session->user.pass);
938
		else
939
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
940
				,session->socket,username);
941
		session->user.number=0;
942
		return(FALSE);
943
	}
944
	ar = arstr(NULL,session->req.ars,&scfg);
945
	authorized=chk_ar(&scfg,ar,&session->user);
946
947
948
	if(ar!=NULL && ar!=nular)
		free(ar);

deuce's avatar
deuce committed
949
950
951
	if(session->req.dynamic==IS_SSJS)  {
		if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
			,NULL /* ftp index file */, NULL /* subscan */)) 
952
			lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
deuce's avatar
deuce committed
953
954
	}

955
	if(authorized)  {
956
		if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)  {
957
958
959
960
			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);
		}
961
		session->req.ld->user=strdup(username);
962

963
		return(TRUE);
964
	}
965
966

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

970
971
972
	return(FALSE);
}

973
static BOOL read_mime_types(char* fname)
974
975
976
977
978
979
{
	char	str[1024];
	char *	ext;
	char *	type;
	FILE*	mime_config;

980
	if((mime_config=fopen(fname,"r"))==NULL)
981
		return(FALSE);
982

983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
	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);
1003
	lprintf(LOG_DEBUG,"Loaded %d mime types",mime_count);
1004
1005
1006
	return(mime_count>0);
}

1007
static int sockreadline(http_session_t * session, char *buf, size_t length)
1008
1009
1010
1011
{
	char	ch;
	DWORD	i;
	BOOL	rd;
1012
#if 0
1013
1014
1015
1016
	time_t	start;

	start=time(NULL);
	for(i=0;TRUE;) {
1017
		if(!socket_check(session->socket,&rd,NULL,1000)) {
1018
1019
			session->req.keep_alive=FALSE;
			close_request(session);
1020
			session->socket=INVALID_SOCKET;
1021
1022
1023
1024
			return(-1);
		}

		if(!rd) {
1025
			if(time(NULL)-start>startup->max_inactivity) {
1026
1027
				session->req.keep_alive=FALSE;
				close_request(session);
1028
				session->socket=INVALID_SOCKET;
1029
1030
1031
1032
1033
				return(-1);        /* time-out */
			}
			continue;       /* no data */
		}

1034
		if(recv(session->socket, &ch, 1, 0)!=1)
1035
1036
1037
1038
1039
1040
1041
1042
			break;

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

		if(i<length)
			buf[i++]=ch;
	}
1043
1044
#else
	for(i=0;TRUE;) {
1045
		if(!socket_check(session->socket,&rd,NULL,60000) || !rd || recv(session->socket, &ch, 1, 0)!=1)  {
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
			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
1059
1060
1061
1062
1063
1064

	/* Terminate at length if longer */
	if(i>length)
		i=length;
		
	if(i>0 && buf[i-1]=='\r')
rswindell's avatar
rswindell committed
1065
		buf[--i]=0;
1066
1067
1068
	else
		buf[i]=0;

1069
	if(startup->options&WEB_OPT_DEBUG_RX)
1070
		lprintf(LOG_DEBUG,"%04d RX: %s",session->socket,buf);
rswindell's avatar
rswindell committed
1071
	return(i);
1072
1073
1074
1075
1076
1077
1078
1079
}

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

rswindell's avatar
rswindell committed
1080
	start=time(NULL);
1081
	for(i=0;TRUE;) {
rswindell's avatar
rswindell committed
1082
1083
1084
1085
		if(time(NULL)-start>startup->max_cgi_inactivity) {
			return(-1);
		}