websrvr.c 80.6 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
125
126
static scfg_t	scfg;
static BOOL		scfg_reloaded=TRUE;
static uint 	http_threads_running=0;
static ulong	active_clients=0;
static ulong	sockets=0;
127
static BOOL		terminate_server=FALSE;
128
129
130
131
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
static ulong	mime_count=0;
static char		revision[16];
132
133
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
134
static char		cgi_dir[MAX_PATH+1];
135
static time_t	uptime=0;
136
static DWORD	served=0;
137
static web_startup_t* startup=NULL;
138
static js_server_props_t js_server_props;
139

140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/* 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;
};

156
typedef struct  {
157
	char	*val;
158
159
160
161
	void	*next;
} linked_list;

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

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

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

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

190
191
192
} http_request_t;

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

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

	/* Client info */
	client_t		client;
213
214
215
216
217
218
219
} http_session_t;

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

220
static mime_types_t		mime_types[MAX_MIME_TYPES];
221
222
223
224

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

enum { 
	 HTTP_HEAD
	,HTTP_GET
};
238

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

246
247
248
249
250
251
252
enum { 
	 IS_STATIC
	,IS_CGI
	,IS_JS
	,IS_SSJS
};

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

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

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

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

308
309
310
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"};

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

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

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

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

#ifdef _WINSOCKAPI_

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

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

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

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

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
372
#define SOCKLIB_DESC NULL
373
374
375
376
377
378

#endif

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

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

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

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

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

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

415
416
static linked_list *add_list(linked_list *list,const char *value)  {
	linked_list*	entry;
417

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

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

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

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

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

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

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

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

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

	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
534
535

	token=strtok(date,",");
536
537
	if(token==NULL)
		return(0);
538
539
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
540
		/* asctime() */
541
542
		/* Toss away week day */
		token=strtok(date," ");
543
544
		if(token==NULL)
			return(0);
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_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;
569
570
571
	}
	else  {
		/* RFC 1123 or RFC 850 */
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
		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);
596
597
598
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
599

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

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

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

	return(result);
}

static void close_request(http_session_t * session)
{
645
	linked_list	*p;
646
647
	time_t		now;

648
649
650
651
652
653
654
655
656
	if(session->req.ld!=NULL) {
		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);
		session->req.ld=NULL;
	}
657

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

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

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

703
704
static const char* unknown_mime_type="application/octet-stream";

705
static const char* get_mime_type(char *ext)
706
707
708
709
{
	uint i;

	if(ext==NULL)
710
711
712
		return(unknown_mime_type);

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

	return(unknown_mime_type);
717
718
}

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

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

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

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

	/* General Headers */
	ti=time(NULL);
777
778
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
779
	snprintf(header,MAX_REQUEST_LINE,"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
780
781
782
		,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);
783
784
785
786
787
788
789
790
791
	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);
	}
792
793

	/* Response Headers */
794
795
	snprintf(header,MAX_REQUEST_LINE,"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
	safecat(headers,header,MAX_HEADERS_SIZE);
796
797
	
	/* Entity Headers */
798
799
800
801
802
803
804
805
	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);
	}
806

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

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

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

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

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

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

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

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

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

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

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

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

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

978
		SAFECOPY(session->username,username);
979
		return(TRUE);
980
	}
981

982
983
	SAFECOPY(session->username,unknown);

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

988
989
990
	return(FALSE);
}

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

998
	if((mime_config=fopen(fname,"r"))==NULL)
999
		return(FALSE);
1000

1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
	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);
1021
	lprintf(LOG_DEBUG,"Loaded %d mime types",mime_count);
1022
1023
1024
	return(mime_count>0);
}

1025
static int sockreadline(http_session_t * session, char *buf, size_t length)
1026
1027
1028
1029
{
	char	ch;
	DWORD	i;
	BOOL	rd;
1030
#if 0
1031
1032
1033
1034
	time_t	start;

	start=time(NULL);
	for(i=0;TRUE;) {
1035
		if(!socket_check(session->socket,&rd,NULL,1000)) {
1036
1037
			session->req.keep_alive=FALSE;
			close_request(session);
1038
			session->socket=INVALID_SOCKET;
1039
1040
1041
1042
			return(-1);
		}

		if(!rd) {
1043
			if(time(NULL)-start>startup->max_inactivity) {
1044
1045
				session->req.keep_alive=FALSE;
				close_request(session);
1046
				session->socket=INVALID_SOCKET;
1047
1048
1049
1050
1051
				return(-1);        /* time-out */
			}
			continue;       /* no data */
		}

1052
		if(recv(session->socket, &ch, 1, 0)!=1)
1053
1054
1055
1056
1057
1058
1059
1060
			break;

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

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