websrvr.c 85.4 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
#if 0
320
321
322
323
324
325
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];
326
327
	if(ti->tm_mon >= 2 
		&& ti->tm_year+1900%400 ? (ti->tm_year+1900%100 ? (ti->tm_year+1900%4 ? 0:1):0):1)
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
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
#else
static time_t
sub_mkgmt(struct tm *tm)
{
        int y, nleapdays;
        time_t t;
        /* days before the month */
        static const unsigned short moff[12] = {
                0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
        };

        /*
         * XXX: This code assumes the given time to be normalized.
         * Normalizing here is impossible in case the given time is a leap
         * second but the local time library is ignorant of leap seconds.
         */

        /* minimal sanity checking not to access outside of the array */
        if ((unsigned) tm->tm_mon >= 12)
                return (time_t) -1;
        if (tm->tm_year < 1970 - 1900)
                return (time_t) -1;

        y = tm->tm_year + 1900 - (tm->tm_mon < 2);
        nleapdays = y / 4 - y / 100 + y / 400 -
            ((1970-1) / 4 - (1970-1) / 100 + (1970-1) / 400);
        t = ((((time_t) (tm->tm_year - (1970 - 1900)) * 365 +
                        moff[tm->tm_mon] + tm->tm_mday - 1 + nleapdays) * 24 +
                tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec;

        return (t < 0 ? (time_t) -1 : t);
}

time_t
time_gm(struct tm *tm)
{
        time_t t, t2;
        struct tm *tm2;
        int sec;

        /* Do the first guess. */
        if ((t = sub_mkgmt(tm)) == (time_t) -1)
                return (time_t) -1;

        /* save value in case *tm is overwritten by gmtime() */
        sec = tm->tm_sec;

        tm2 = gmtime(&t);
        if ((t2 = sub_mkgmt(tm2)) == (time_t) -1)
                return (time_t) -1;

        if (t2 < t || tm2->tm_sec != sec) {
                /*
                 * Adjust for leap seconds.
                 *
                 *     real time_t time
                 *           |
                 *          tm
                 *         /        ... (a) first sub_mkgmt() conversion
                 *       t
                 *       |
                 *      tm2
                 *     /        ... (b) second sub_mkgmt() conversion
                 *   t2
                 *                        --->time
                 */
                /*
                 * Do the second guess, assuming (a) and (b) are almost equal.
                 */
                t += t - t2;
                tm2 = gmtime(&t);

                /*
                 * Either (a) or (b), may include one or two extra
                 * leap seconds.  Try t, t + 2, t - 2, t + 1, and t - 1.
                 */
                if (tm2->tm_sec == sec
                    || (t += 2, tm2 = gmtime(&t), tm2->tm_sec == sec)
                    || (t -= 4, tm2 = gmtime(&t), tm2->tm_sec == sec)
                    || (t += 3, tm2 = gmtime(&t), tm2->tm_sec == sec)
                    || (t -= 2, tm2 = gmtime(&t), tm2->tm_sec == sec))
                        ;        /* found */
                else {
                        /*
                         * Not found.
                         */
                        if (sec >= 60)
                                /*
                                 * The given time is a leap second
                                 * (sec 60 or 61), but the time library
                                 * is ignorant of the leap second.
                                 */
                                ;        /* treat sec 60 as 59,
                                           sec 61 as 0 of the next minute */
                        else
                                /* The given time may not be normalized. */
                                t++;        /* restore t */
                }
        }

        return (t < 0 ? (time_t) -1 : t);
}
#endif
439

440
static int lprintf(int level, char *fmt, ...)
441
442
443
444
445
446
447
448
449
450
451
{
	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);
452
    return(startup->lputs(startup->cbdata,level,sbuf));
453
454
455
456
457
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
458
#define SOCKLIB_DESC WSAData.szDescription
459
460
461
462
463
464
465
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
466
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
467
468
469
470
		WSAInitialized=TRUE;
		return (TRUE);
	}

471
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
472
473
474
475
476
477
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
478
#define SOCKLIB_DESC NULL
479
480
481
482
483
484

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
485
	    startup->status(startup->cbdata,str);
486
487
488
489
490
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
491
		startup->clients(startup->cbdata,active_clients);
492
493
494
495
496
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
497
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
498
499
500
501
502
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
503
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
504
505
506
507
508
509
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
510
		startup->thread_up(startup->cbdata,TRUE, setuid);
511
512
513
514
515
516
517
}

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

521
522
static linked_list *add_list(linked_list *list,const char *value)  {
	linked_list*	entry;
523

524
525
	entry=malloc(sizeof(linked_list));
	if(entry==NULL)  {
526
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
527
		return(list);
528
	}
529
530
531
	entry->val=malloc(strlen(value)+1);
	if(entry->val==NULL)  {
		FREE_AND_NULL(entry);
532
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
533
534
535
536
537
		return(list);
	}
	strcpy(entry->val,value);
	entry->next=list;
	return(entry);
538
539
540
}

static void add_env(http_session_t *session, const char *name,const char *value)  {
541
542
	char	newname[129];
	char	fullname[387];
543
544
545
	char	*p;
	
	if(name==NULL || value==NULL)  {
546
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
547
548
549
550
551
552
553
554
555
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
556
557
558

	sprintf(fullname,"%s=%s",newname,value);
	session->req.cgi_env=add_list(session->req.cgi_env,fullname);
559
560
561
562
563
564
565
566
567
}

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");
568
	if(!strcmp(session->host_name,session->host_ip))
569
570
571
572
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
}

573
574
575
576
577
578
/*
 * 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?
 */
579
580
static int sockprint(SOCKET sock, const char *str)
{
581
582
583
	int len;
	int	result;
	int written=0;
584
	BOOL	wr;
585
586
587

	if(sock==INVALID_SOCKET)
		return(0);
588
	if(startup->options&WEB_OPT_DEBUG_TX)
589
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
590
	len=strlen(str);
591

592
	while(socket_check(sock,NULL,&wr,60000) && wr && written<len)  {
593
		result=sendsocket(sock,str+written,len-written);
594
595
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
596
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
597
			else if(ERROR_VALUE==ECONNABORTED) 
598
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
599
			else
600
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
601
602
			return(0);
		}
603
604
605
		written+=result;
	}
	if(written != len) {
606
		lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
607
608
609
610
611
		return(0);
	}
	return(len);
}

612
613
614
615
616
617
618
619
620
621
622
623
624
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;
625
626
	char	*token;
	time_t	t;
627
628
629
630
631
632
633
634
635
636
637
638
639

	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
640
641

	token=strtok(date,",");
642
643
	if(token==NULL)
		return(0);
644
645
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
646
		/* asctime() */
647
648
		/* Toss away week day */
		token=strtok(date," ");
649
650
		if(token==NULL)
			return(0);
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
		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;
675
676
677
	}
	else  {
		/* RFC 1123 or RFC 850 */
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
		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);
702
703
704
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
705

706
	t=time_gm(&ti);
707
	return(t);
708
709
710
711
712
713
714
715
716
}

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) 
717
		startup->socket_open(startup->cbdata,TRUE);
718
719
	if(sock!=INVALID_SOCKET) {
		if(set_socket_options(&scfg, sock,error))
720
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737

		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) {
738
		startup->socket_open(startup->cbdata,FALSE);
739
740
741
742
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
743
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
744
745
746
747
748
749
750
	}

	return(result);
}

static void close_request(http_session_t * session)
{
751
	linked_list	*p;
752
753
	time_t		now;

754
755
756
757
	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
		pthread_mutex_lock(&log_mutex);
758
		listPushNode(&log_list,session->req.ld);
759
760
761
762
		pthread_mutex_unlock(&log_mutex);
		sem_post(&log_sem);
		session->req.ld=NULL;
	}
763

764
765
766
767
768
	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;
769
	}
770
771
772
773
774
775
776
	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
777
	if(!session->req.keep_alive || session->socket==INVALID_SOCKET) {
778
		close_socket(session->socket);
779
		session->socket=INVALID_SOCKET;
780
781
782
783
784
785
		session->finished=TRUE;
	}
}

static int get_header_type(char *header)
{
786
	int i;
787
788
789
790
791
792
793
794
795
796
	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) 
{
797
	int i;
798
799
	if(headers[id].id==id)
		return(headers[id].text);
800
801
802
803
804
805
806
807
808

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

809
810
static const char* unknown_mime_type="application/octet-stream";

811
static const char* get_mime_type(char *ext)
812
813
814
815
{
	uint i;

	if(ext==NULL)
816
817
818
		return(unknown_mime_type);

	for(i=0;i<mime_count;i++)
819
		if(!stricmp(ext+1,mime_types[i].ext))
820
821
822
			return(mime_types[i].type);

	return(unknown_mime_type);
823
824
}

825
826
/* 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) {
827
	size_t dstlen,appendlen;
828
829
830
831
832
833
834
835
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

836
static BOOL send_headers(http_session_t *session, const char *status)
837
{
838
	int		ret;
839
	BOOL	send_file=TRUE;
840
	time_t	ti;
841
	const char	*status_line;
842
	struct stat	stats;
843
	struct tm	tm;
844
	linked_list	*p;
845
	char	*headers;
846
	char	header[MAX_REQUEST_LINE+1];
847

848
	status_line=status;
849
	ret=stat(session->req.physical_path,&stats);
850
851
852
853
854
	/*
	 * 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
	 */
855
	if(!ret && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
856
		status_line="304 Not Modified";
857
		ret=-1;
858
		send_file=FALSE;
859
	}
860
	if(session->req.send_location==MOVED_PERM)  {
861
		status_line="301 Moved Permanently";
862
863
864
		ret=-1;
		send_file=FALSE;
	}
865
	if(session->req.send_location==MOVED_TEMP)  {
866
		status_line="302 Moved Temporarily";
867
868
869
		ret=-1;
		send_file=FALSE;
	}
870
871
872

	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
873
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
874
875
876
		return(FALSE);
	}
	*headers=0;
877
	/* Status-Line */
878
	safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
879
	safecat(headers,header,MAX_HEADERS_SIZE);
880
881
882

	/* General Headers */
	ti=time(NULL);
883
884
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
885
	safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
886
887
888
		,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);
889
890
	safecat(headers,header,MAX_HEADERS_SIZE);
	if(session->req.keep_alive) {
891
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
892
893
894
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
895
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
896
897
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
898
899

	/* Response Headers */
900
	safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
901
	safecat(headers,header,MAX_HEADERS_SIZE);
902
903
	
	/* Entity Headers */
904
	if(session->req.dynamic) {
905
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
906
907
908
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
909
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
910
911
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
912

913
	if(session->req.send_location) {
914
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
915
		safecat(headers,header,MAX_HEADERS_SIZE);
916
	}
917
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
918
		if(ret)  {
919
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
920
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
921
		}
922
		else  {
923
			safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
924
			safecat(headers,header,MAX_HEADERS_SIZE);
925
		}
926
	}
927

928
	if(!ret && !session->req.dynamic)  {
929
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
930
		safecat(headers,header,MAX_HEADERS_SIZE);
931
		gmtime_r(&stats.st_mtime,&tm);
932
		safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
933
			,get_header(HEAD_LASTMODIFIED)
934
935
			,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
			,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
936
		safecat(headers,header,MAX_HEADERS_SIZE);
937
	} 
rswindell's avatar
rswindell committed
938

939
940
	if(session->req.dynamic)  {
		/* Dynamic headers */
941
		/* Set up environment */
942
		p=session->req.dynamic_heads;
943
		while(p != NULL)  {
944
			safecat(headers,p->val,MAX_HEADERS_SIZE);
945
			p=p->next;
946
		}
947
	}
948

949
	safecat(headers,"",MAX_HEADERS_SIZE);
950
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
951
	FREE_AND_NULL(headers);
952
	return(send_file);
953
954
}

955
static int sock_sendfile(SOCKET socket,char *path)
956
957
{
	int		file;
958
	long	offset=0;
959
	int		ret=0;
960

961
	if(startup->options&WEB_OPT_DEBUG_TX)
962
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
963
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
964
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
965
	else {
966
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 1) {
967
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
968
				, socket, errno, path);
969
970
			ret=0;
		}
971
972
		close(file);
	}
973
	return(ret);
974
975
}

976
static void send_error(http_session_t * session, const char* message)
977
978
{
	char	error_code[4];
979
	struct stat	sb;
980
	char	sbuf[1024];
981

982
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
983
	session->req.keep_alive=FALSE;
984
	session->req.send_location=NO_LOCATION;
985
	SAFECOPY(error_code,message);
986
	sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
987
988
	if(session->req.ld!=NULL)
		session->req.ld->status=atoi(message);
989
	if(session->http_ver > HTTP_0_9)  {
990
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
991
		send_headers(session,message);
992
	}
993
	if(!stat(session->req.physical_path,&sb)) {
994
995
996
997
998
999
		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;
1000
	}
1001
	else {
1002
		lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
1003
			,session->socket,session->req.physical_path);
1004
		safe_snprintf(sbuf,sizeof(sbuf)
1005
1006
1007
			,"<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>"
1008
1009
1010
			"please notify <a href=\"mailto:sysop@%s\">"
			"%s</a></BODY></HTML>"
			,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
1011
		sockprint(session->socket,sbuf);
1012
1013
		if(session->req.ld!=NULL)
			session->req.ld->size=strlen(sbuf);
1014
	}
1015
1016
1017
	close_request(session);
}

1018
static BOOL check_ars(http_session_t * session)
1019
1020
1021
1022
{
	char	*username;
	char	*password;
	uchar	*ar;
1023
	BOOL	authorized;
1024
	char	auth_req[MAX_REQUEST_LINE+1];
1025

1026
	if(session->req.auth[0]==0) {
1027
		if(startup->options&WEB_OPT_DEBUG_RX)
1028
			lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket);
1029
		return(FALSE);
1030
	}
1031
	SAFECOPY(auth_req,session->req.auth);
1032

1033
	username=strtok(auth_req,":");
1034
1035
	if(username==NULL)
		username="";
1036
1037
1038
	password=strtok(NULL,":");
	/* Require a password */
	if(password==NULL)
1039
		password="";
1040
1041
	session->user.number=matchuser(&scfg, username, FALSE);
	if(session->user.number==0) {
1042
		SAFECOPY(session->username,unknown);
1043
		if(scfg.sys_misc&SM_ECHO_PW)
1044
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s"
1045
1046
				,session->socket,username,password);
		else
1047
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s"
1048
1049
1050
				,session->socket,username);
		return(FALSE);
	}
1051
1052
	getuserdat(&scfg, &session->user);
	if(session->user.pass[0] && stricmp(session->user.pass,password)) {
1053
		SAFECOPY(session->username,unknown);
1054
		/* Should go to the hack log? */
1055
		if(scfg.sys_misc&SM_ECHO_PW)
1056
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
1057
				,session->socket,username,password,session->user.pass);
1058
		else
1059
			lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
1060
				,session->socket,username);
1061
		session->user.number=0;
1062
		return(FALSE);
1063
	}
1064
	ar = arstr(NULL,session->req.ars,&scfg);
1065
	authorized=chk_ar(&scfg,ar,&session->user);
1066
	if(ar!=NULL && ar!=nular)
deuce's avatar
deuce committed
1067
		FREE_AND_NULL(ar);
1068

deuce's avatar
deuce committed
1069
1070
1071
	if(session->req.dynamic==IS_SSJS)  {
		if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
			,NULL /* ftp index file */, NULL /* subscan */)) 
1072
			lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
deuce's avatar
deuce committed
1073
1074
	}

1075
	if(authorized)  {
1076
		if(session->req.dynamic==IS_CGI || session->req.dynamic==IS_STATIC)  {
1077
1078
1079
1080
			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);
		}
1081
1082
		if(session->req.ld!=NULL)
			session->req.ld->user=strdup(username);
1083

1084
		SAFECOPY(session->username,username);
1085
		return(TRUE);