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

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

extern const uchar* nular;
rswindell's avatar
rswindell committed
116
117

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

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

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

158
typedef struct  {
159
	char	*val;
160
161
162
163
	void	*next;
} linked_list;

typedef struct  {
164
	int			method;
165
166
167
168
169
170
171
172
173
174
	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;
175
176
177
	const char*	mime_type;

	/* CGI parameters */
178
179
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
deuce's avatar
deuce committed
180

181
	linked_list*	cgi_env;
182
	linked_list*	dynamic_heads;
183
	char		status[MAX_REQUEST_LINE+1];
184
185
	char *		post_data;
	size_t		post_len;
186
	int			dynamic;
187
	struct log_data	*ld;
188
189
190
191

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

192
193
194
} http_request_t;

typedef struct  {
195
196
	SOCKET			socket;
	SOCKADDR_IN		addr;
197
	http_request_t	req;
198
199
	char			host_ip[64];
	char			host_name[128];	/* Resolved remote host */
200
201
	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 */
202
	user_t			user;	
203
	char			username[LEN_NAME+1];
204
205
206
207
208

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
209
210
211
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
212
213
214

	/* Client info */
	client_t		client;
215
216
217
218
219
220
221
} http_session_t;

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

222
static mime_types_t		mime_types[MAX_MIME_TYPES];
223
224
225
226

enum { 
	 HTTP_0_9
	,HTTP_1_0
227
	,HTTP_1_1
228
229
230
231
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
232
	,"HTTP/1.1"
rswindell's avatar
rswindell committed
233
	,NULL	/* terminator */
234
235
236
237
238
239
};

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
240

rswindell's avatar
rswindell committed
241
242
243
static char* methods[] = {
	 "HEAD"
	,"GET"
244
	,"POST"
rswindell's avatar
rswindell committed
245
246
	,NULL	/* terminator */
};
247

248
249
250
251
252
253
254
enum { 
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

255
enum { 
256
257
258
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
259
260
	,HEAD_LENGTH
	,HEAD_TYPE
261
262
263
264
265
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
266
267
268
269
270
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
271
272
	,HEAD_REFERER
	,HEAD_AGENT
273
274
275
276
277
278
};

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

299
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
300
301
302
enum  {
	NO_LOCATION
	,MOVED_PERM
303
	,MOVED_TEMP
304
	,MOVED_STAT
305
306
};

307
308
309
/* Max. times to follow internal redirects for a single request */
#define MAX_REDIR_LOOPS	20

310
311
312
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"};

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

315
static void respond(http_session_t * session);
316
static BOOL js_setup(http_session_t* session);
317
static char *find_last_slash(char *str);
318

319
320
321
322
323
324
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];
325
326
	if(ti->tm_mon >= 2 
		&& ti->tm_year+1900%400 ? (ti->tm_year+1900%100 ? (ti->tm_year+1900%4 ? 0:1):0):1)
327
328
329
330
331
332
333
334
335
		++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;
}

336
static int lprintf(int level, char *fmt, ...)
337
338
339
340
341
342
343
344
345
346
347
{
	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);
348
    return(startup->lputs(startup->cbdata,level,sbuf));
349
350
351
352
353
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
354
#define SOCKLIB_DESC WSAData.szDescription
355
356
357
358
359
360
361
static BOOL WSAInitialized=FALSE;

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

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

367
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
368
369
370
371
372
373
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
374
#define SOCKLIB_DESC NULL
375
376
377
378
379
380

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
381
	    startup->status(startup->cbdata,str);
382
383
384
385
386
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
387
		startup->clients(startup->cbdata,active_clients);
388
389
390
391
392
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
393
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
394
395
396
397
398
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
399
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
400
401
402
403
404
405
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
406
		startup->thread_up(startup->cbdata,TRUE, setuid);
407
408
409
410
411
412
413
}

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

417
418
static linked_list *add_list(linked_list *list,const char *value)  {
	linked_list*	entry;
419

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

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

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
452
453
454

	sprintf(fullname,"%s=%s",newname,value);
	session->req.cgi_env=add_list(session->req.cgi_env,fullname);
455
456
457
458
459
460
461
462
463
}

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");
464
	if(!strcmp(session->host_name,session->host_ip))
465
466
467
468
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
}

469
470
471
472
473
474
/*
 * 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?
 */
475
476
static int sockprint(SOCKET sock, const char *str)
{
477
478
479
	int len;
	int	result;
	int written=0;
480
	BOOL	wr;
481
482
483

	if(sock==INVALID_SOCKET)
		return(0);
484
	if(startup->options&WEB_OPT_DEBUG_TX)
485
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
486
	len=strlen(str);
487

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

508
509
510
511
512
513
514
515
516
517
518
519
520
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;
521
522
	char	*token;
	time_t	t;
523
524
525
526
527
528
529
530
531
532
533
534
535

	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
536
537

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

602
	t=time_gm(&ti);
603
	return(t);
604
605
606
607
608
609
610
611
612
}

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

		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) {
634
		startup->socket_open(startup->cbdata,FALSE);
635
636
637
638
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
639
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
640
641
642
643
644
645
646
	}

	return(result);
}

static void close_request(http_session_t * session)
{
647
	linked_list	*p;
648
649
	time_t		now;

650
651
652
653
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
		pthread_mutex_lock(&log_mutex);
654
		listPushNode(&log_list,session->req.ld);
655
656
657
658
		pthread_mutex_unlock(&log_mutex);
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
659

660
661
662
663
664
	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;
665
	}
666
667
668
669
670
671
672
	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
673
	if(!session->req.keep_alive || session->socket==INVALID_SOCKET) {
674
		close_socket(session->socket);
675
		session->socket=INVALID_SOCKET;
676
677
678
679
680
681
		session->finished=TRUE;
	}
}

static int get_header_type(char *header)
{
682
	int i;
683
684
685
686
687
688
689
690
691
692
	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) 
{
693
	int i;
694
695
	if(headers[id].id==id)
		return(headers[id].text);
696
697
698
699
700
701
702
703
704

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

705
706
static const char* unknown_mime_type="application/octet-stream";

707
static const char* get_mime_type(char *ext)
708
709
710
711
{
	uint i;

	if(ext==NULL)
712
713
714
		return(unknown_mime_type);

	for(i=0;i<mime_count;i++)
715
		if(!stricmp(ext+1,mime_types[i].ext))
716
717
718
			return(mime_types[i].type);

	return(unknown_mime_type);
719
720
}

721
722
/* 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) {
723
	size_t dstlen,appendlen;
724
725
726
727
728
729
730
731
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

732
static BOOL send_headers(http_session_t *session, const char *status)
733
{
734
	int		ret;
735
	BOOL	send_file=TRUE;
736
	time_t	ti;
737
	const char	*status_line;
738
	struct stat	stats;
739
	struct tm	tm;
740
	linked_list	*p;
741
	char	*headers;
742
	char	header[MAX_REQUEST_LINE+1];
743

744
	status_line=status;
745
	ret=stat(session->req.physical_path,&stats);
746
747
748
749
750
	/*
	 * 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
	 */
751
	if(!ret && (stats.st_mtime < session->req.if_modified_since) && !session->req.dynamic) {
752
		status_line="304 Not Modified";
753
		ret=-1;
754
		send_file=FALSE;
755
	}
756
	if(session->req.send_location==MOVED_PERM)  {
757
		status_line="301 Moved Permanently";
758
759
760
		ret=-1;
		send_file=FALSE;
	}
761
	if(session->req.send_location==MOVED_TEMP)  {
762
		status_line="302 Moved Temporarily";
763
764
765
		ret=-1;
		send_file=FALSE;
	}
766
767
768

	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
769
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
770
771
772
		return(FALSE);
	}
	*headers=0;
773
	/* Status-Line */
774
	safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
775
	safecat(headers,header,MAX_HEADERS_SIZE);
776
777
778

	/* General Headers */
	ti=time(NULL);
779
780
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
781
	safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
782
783
784
		,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);
785
786
	safecat(headers,header,MAX_HEADERS_SIZE);
	if(session->req.keep_alive) {
787
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
788
789
790
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
791
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
792
793
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
794
795

	/* Response Headers */
796
	safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
797
	safecat(headers,header,MAX_HEADERS_SIZE);
798
799
	
	/* Entity Headers */
800
	if(session->req.dynamic) {
801
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
802
803
804
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
805
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
806
807
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
808

809
	if(session->req.send_location) {
810
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
811
		safecat(headers,header,MAX_HEADERS_SIZE);
812
	}
813
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
814
		if(ret)  {
815
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
816
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
817
		}
818
		else  {
819
			safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
820
			safecat(headers,header,MAX_HEADERS_SIZE);
821
		}
822
	}
823

824
	if(!ret && !session->req.dynamic)  {
825
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
826
		safecat(headers,header,MAX_HEADERS_SIZE);
827
		gmtime_r(&stats.st_mtime,&tm);
828
		safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
829
			,get_header(HEAD_LASTMODIFIED)
830
831
			,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
			,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
832
		safecat(headers,header,MAX_HEADERS_SIZE);
833
	} 
rswindell's avatar
rswindell committed
834

835
836
	if(session->req.dynamic)  {
		/* Dynamic headers */
837
		/* Set up environment */
838
		p=session->req.dynamic_heads;
839
		while(p != NULL)  {
840
			safecat(headers,p->val,MAX_HEADERS_SIZE);
841
			p=p->next;
842
		}
843
	}
844

845
	safecat(headers,"",MAX_HEADERS_SIZE);
846
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
847
	FREE_AND_NULL(headers);
848
	return(send_file);
849
850
}

851
static int sock_sendfile(SOCKET socket,char *path)
852
853
{
	int		file;
854
	long	offset=0;
855
	int		ret=0;
856

857
	if(startup->options&WEB_OPT_DEBUG_TX)
858
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
859
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
860
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
861
	else {
862
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 1) {
863
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
864
				, socket, errno, path);
865
866
			ret=0;
		}
867
868
		close(file);
	}
869
	return(ret);
870
871
}

872
static void send_error(http_session_t * session, const char* message)
873
874
{
	char	error_code[4];
875
	struct stat	sb;
876
	char	sbuf[1024];
877

878
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
879
	session->req.keep_alive=FALSE;
880
	session->req.send_location=NO_LOCATION;
881
	SAFECOPY(error_code,message);
882
	sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
883
884
	if(session->req.ld!=NULL)
		session->req.ld->status=atoi(message);
885
	if(session->http_ver > HTTP_0_9)  {
886
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
887
		send_headers(session,message);
888
	}
889
	if(!stat(session->req.physical_path,&sb)) {
890
891
892
893
894
895
		int	snt=0;
		snt=sock_sendfile(session->socket,session->req.physical_path);
		if(snt<0)
			snt=0;
		if(session->req.ld!=NULL)
			session->req.ld->size=snt;
896
	}
897
	else {
898
		lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
899
			,session->socket,session->req.physical_path);
900
		safe_snprintf(sbuf,sizeof(sbuf)
901
902
903
			,"<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>"
904
905
906
			"please notify <a href=\"mailto:sysop@%s\">"
			"%s</a></BODY></HTML>"
			,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
907
		sockprint(session->socket,sbuf);
908
909
		if(session->req.ld!=NULL)
			session->req.ld->size=strlen(sbuf);
910
	}
911
912
913
	close_request(session);
}

914
static BOOL check_ars(http_session_t * session)
915
916
917
918
{
	char	*username;
	char	*password;
	uchar	*ar;
919
	BOOL	authorized;
920
	char	auth_req[MAX_REQUEST_LINE+1];
921

922
	if(session->req.auth[0]==0) {
923
		if(startup->options&WEB_OPT_DEBUG_RX)
924
			lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket);
925
		return(FALSE);
926
	}
927
	SAFECOPY(auth_req,session->req.auth);
928

929
	username=strtok(auth_req,":");
930
931
	if(username==NULL)
		username="";
932
933
934
	password=strtok(NULL,":");
	/* Require a password */
	if(password==NULL)
935
		password="";
936
937
	session->user.number=matchuser(&scfg, username, FALSE);
	if(session->user.number==0) {
938
		SAFECOPY(session->username,unknown);
939
		if(scfg.sys_misc&SM_ECHO_PW)
940
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s"
941
942
				,session->socket,username,password);
		else
943
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s"
944
945
946
				,session->socket,username);
		return(FALSE);
	}
947
948
	getuserdat(&scfg, &session->user);
	if(session->user.pass[0] && stricmp(session->user.pass,password)) {
949
		SAFECOPY(session->username,unknown);
950
		/* Should go to the hack log? */
951
		if(scfg.sys_misc&SM_ECHO_PW)
952
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
953
				,session->socket,username,password,session->user.pass);
954
		else
955
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
956
				,session->socket,username);
957
		session->user.number=0;
958
		return(FALSE);
959
	}
960
	ar = arstr(NULL,session->req.ars,&scfg);
961
	authorized=chk_ar(&scfg,ar,&session->user);
962
	if(ar!=NULL && ar!=nular)
deuce's avatar
deuce committed
963
		FREE_AND_NULL(ar);
964

deuce's avatar
deuce committed
965
966
967
	if(session->req.dynamic==IS_SSJS)  {
		if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
			,NULL /* ftp index file */, NULL /* subscan */)) 
968
			lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
deuce's avatar
deuce committed
969
970
	}

971
	if(authorized)  {
972
		if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)  {
973
974
975
976
			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);
		}
977
978
		if(session->req.ld!=NULL)
			session->req.ld->user=strdup(username);
979

980
		SAFECOPY(session->username,username);
981
		return(TRUE);
982
	}
983

984
985
	SAFECOPY(session->username,unknown);

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

990
991
992
	return(FALSE);
}

993
static BOOL read_mime_types(char* fname)
994
995
996
997
998
999
{
	char	str[1024];
	char *	ext;
	char *	type;
	FILE*	mime_config;

1000
1001
	mime_count=0;

1002
	if((mime_config=fopen(fname,"r"))==NULL)
1003
		return(FALSE);
1004

1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
	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);