websrvr.c 89.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
 */

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

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

100
101
#include "sbbs.h"
#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 */
deuce's avatar
deuce committed
118
119
120
#define MAX_REQUEST_LINE		1024	/* NOT including terminator */
#define MAX_HEADERS_SIZE		16384	/* Maximum total size of all headers 
										   (Including terminator )*/
121

122
123
static scfg_t	scfg;
static BOOL		scfg_reloaded=TRUE;
124
static BOOL		http_logging_thread_running=FALSE;
125
126
static ulong	active_clients=0;
static ulong	sockets=0;
127
static BOOL		terminate_server=FALSE;
128
static BOOL		terminate_http_logging_thread=FALSE;
129
130
131
static uint		thread_count=0;
static SOCKET	server_socket=INVALID_SOCKET;
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
static link_list_t recycle_semfiles;
static link_list_t shutdown_semfiles;
141

142
143
static named_string_t** mime_types;

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

160
typedef struct  {
161
	char	*val;
162
163
164
165
	void	*next;
} linked_list;

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

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

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

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

194
195
196
} http_request_t;

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

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
214
215
216
	JSObject*		js_query;
	JSObject*		js_header;
	JSObject*		js_request;
217
218
219

	/* Client info */
	client_t		client;
220
221
222
223
224
} http_session_t;

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
static void respond(http_session_t * session);
312
static BOOL js_setup(http_session_t* session);
313
static char *find_last_slash(char *str);
314

315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
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
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);
}
416

417
static int lprintf(int level, char *fmt, ...)
418
419
420
421
422
423
424
425
426
427
428
{
	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);
429
    return(startup->lputs(startup->cbdata,level,sbuf));
430
431
432
433
434
}

#ifdef _WINSOCKAPI_

static WSADATA WSAData;
435
#define SOCKLIB_DESC WSAData.szDescription
436
437
438
439
440
441
442
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
443
		lprintf(LOG_INFO,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
444
445
446
447
		WSAInitialized=TRUE;
		return (TRUE);
	}

448
    lprintf(LOG_ERR,"!WinSock startup ERROR %d", status);
449
450
451
452
453
454
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)
455
#define SOCKLIB_DESC NULL
456
457
458
459
460
461

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
462
	    startup->status(startup->cbdata,str);
463
464
465
466
467
}

static void update_clients(void)
{
	if(startup!=NULL && startup->clients!=NULL)
468
		startup->clients(startup->cbdata,active_clients);
469
470
471
472
473
}

static void client_on(SOCKET sock, client_t* client, BOOL update)
{
	if(startup!=NULL && startup->client_on!=NULL)
474
		startup->client_on(startup->cbdata,TRUE,sock,client,update);
475
476
477
478
479
}

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
480
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
481
482
483
484
485
486
}

static void thread_up(BOOL setuid)
{
	thread_count++;
	if(startup!=NULL && startup->thread_up!=NULL)
487
		startup->thread_up(startup->cbdata,TRUE, setuid);
488
489
490
491
492
493
494
}

static void thread_down(void)
{
	if(thread_count>0)
		thread_count--;
	if(startup!=NULL && startup->thread_up!=NULL)
495
		startup->thread_up(startup->cbdata,FALSE, FALSE);
496
497
}

deuce's avatar
deuce committed
498
499
500
501
/********************************************************/
/* Adds an item to a linked list 						*/
/* ToDo: Replace this with link_list stuff from xpdev 	*/
/********************************************************/
502
503
static linked_list *add_list(linked_list *list,const char *value)  {
	linked_list*	entry;
504

505
506
	entry=malloc(sizeof(linked_list));
	if(entry==NULL)  {
507
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
508
		return(list);
509
	}
510
511
512
	entry->val=malloc(strlen(value)+1);
	if(entry->val==NULL)  {
		FREE_AND_NULL(entry);
513
		lprintf(LOG_CRIT,"Could not allocate memory for \"%s\" in linked list.",&value);
514
515
516
517
518
		return(list);
	}
	strcpy(entry->val,value);
	entry->next=list;
	return(entry);
519
520
}

deuce's avatar
deuce committed
521
522
523
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
524
static void add_env(http_session_t *session, const char *name,const char *value)  {
525
526
	char	newname[129];
	char	fullname[387];
527
528
529
	char	*p;
	
	if(name==NULL || value==NULL)  {
530
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
531
532
533
534
535
536
537
538
539
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
540
541
542

	sprintf(fullname,"%s=%s",newname,value);
	session->req.cgi_env=add_list(session->req.cgi_env,fullname);
543
544
}

deuce's avatar
deuce committed
545
546
547
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
548
549
550
551
552
553
554
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");
555
	if(!strcmp(session->host_name,session->host_ip))
556
557
558
559
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
}

560
/*
deuce's avatar
deuce committed
561
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
562
563
564
565
 * (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?
 */
566
567
static int sockprint(SOCKET sock, const char *str)
{
568
569
570
	int len;
	int	result;
	int written=0;
571
	BOOL	wr;
572
573
574

	if(sock==INVALID_SOCKET)
		return(0);
575
	if(startup->options&WEB_OPT_DEBUG_TX)
576
		lprintf(LOG_DEBUG,"%04d TX: %s", sock, str);
577
	len=strlen(str);
578

579
	while(socket_check(sock,NULL,&wr,startup->max_inactivity*1000) && wr && written<len)  {
580
		result=sendsocket(sock,str+written,len-written);
581
582
		if(result==SOCKET_ERROR) {
			if(ERROR_VALUE==ECONNRESET) 
583
				lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",sock);
584
			else if(ERROR_VALUE==ECONNABORTED) 
585
				lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",sock);
586
			else
587
				lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
588
589
			return(0);
		}
590
591
592
		written+=result;
	}
	if(written != len) {
593
		lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",sock,ERROR_VALUE);
594
595
596
597
598
		return(0);
	}
	return(len);
}

deuce's avatar
deuce committed
599
600
601
602
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
603
604
605
606
607
608
609
610
611
612
static int getmonth(char *mon)
{
	int	i;
	for(i=0;i<12;i++)
		if(!stricmp(mon,months[i]))
			return(i);

	return 0;
}

deuce's avatar
deuce committed
613
614
615
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
616
617
618
static time_t decode_date(char *date)
{
	struct	tm	ti;
619
620
	char	*token;
	time_t	t;
621
622
623
624
625
626
627
628
629

	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? */

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

695
	t=time_gm(&ti);
696
	return(t);
697
698
699
700
701
702
703
704
705
}

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) 
706
		startup->socket_open(startup->cbdata,TRUE);
707
708
	if(sock!=INVALID_SOCKET) {
		if(set_socket_options(&scfg, sock,error))
709
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726

		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) {
727
		startup->socket_open(startup->cbdata,FALSE);
728
729
730
731
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
732
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",sock, ERROR_VALUE);
733
734
735
736
737
	}

	return(result);
}

deuce's avatar
deuce committed
738
739
740
741
742
743
744
745
/**************************************************/
/* End of a single request...					  */
/* This is called at the end of EVERY request	  */
/*  Log the request       						  */
/*  Free request-specific data ie: dynamic stuff  */
/*  Close socket unless it's being kept alive     */
/*   If the socket is closed, the session is done */
/**************************************************/
746
747
static void close_request(http_session_t * session)
{
748
	linked_list	*p;
749
750
	time_t		now;

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

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

781
	memset(&session->req,0,sizeof(session->req));
782
783
784
785
}

static int get_header_type(char *header)
{
786
	int i;
787
788
789
790
791
792
793
794
	for(i=0; headers[i].text!=NULL; i++) {
		if(!stricmp(header,headers[i].text)) {
			return(headers[i].id);
		}
	}
	return(-1);
}

deuce's avatar
deuce committed
795
/* Opposite of get_header_type() */
796
797
static char *get_header(int id) 
{
798
	int i;
799
800
	if(headers[id].id==id)
		return(headers[id].text);
801
802
803
804
805
806
807
808
809

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

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

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

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

819
820
821
	for(i=0;mime_types[i]!=NULL;i++)
		if(!stricmp(ext+1,mime_types[i]->name))
			return(mime_types[i]->value);
822
823

	return(unknown_mime_type);
824
825
}

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

deuce's avatar
deuce committed
837
838
839
840
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
841
static BOOL send_headers(http_session_t *session, const char *status)
842
{
843
	int		ret;
844
	BOOL	send_file=TRUE;
845
	time_t	ti;
846
	const char	*status_line;
847
	struct stat	stats;
848
	struct tm	tm;
849
	linked_list	*p;
850
	char	*headers;
851
	char	header[MAX_REQUEST_LINE+1];
852

853
854
855
	lprintf(LOG_DEBUG,"%04d Request resolved to: %s"
		,session->socket,session->req.physical_path);
	if(session->http_ver <= HTTP_0_9) {
deuce's avatar
deuce committed
856
857
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
858
		return(TRUE);
859
	}
deuce's avatar
deuce committed
860

861
	status_line=status;
862
	ret=stat(session->req.physical_path,&stats);
863
	if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
864
		status_line="304 Not Modified";
865
		ret=-1;
866
		send_file=FALSE;
867
	}
868
	if(session->req.send_location==MOVED_PERM)  {
869
		status_line="301 Moved Permanently";
870
871
872
		ret=-1;
		send_file=FALSE;
	}
873
	if(session->req.send_location==MOVED_TEMP)  {
874
		status_line="302 Moved Temporarily";
875
876
877
		ret=-1;
		send_file=FALSE;
	}
878

879
880
881
	if(session->req.ld!=NULL)
		session->req.ld->status=atoi(status_line);

882
883
	headers=malloc(MAX_HEADERS_SIZE);
	if(headers==NULL)  {
884
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
885
886
887
		return(FALSE);
	}
	*headers=0;
888
	/* Status-Line */
889
	safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);
890
891
892

	lprintf(LOG_DEBUG,"%04d Result: %s",session->socket,header);

893
	safecat(headers,header,MAX_HEADERS_SIZE);
894
895
896

	/* General Headers */
	ti=time(NULL);
897
898
	if(gmtime_r(&ti,&tm)==NULL)
		memset(&tm,0,sizeof(tm));
899
	safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
900
901
902
		,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);
903
904
	safecat(headers,header,MAX_HEADERS_SIZE);
	if(session->req.keep_alive) {
905
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
906
907
908
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
909
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
910
911
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
912
913

	/* Response Headers */
914
	safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
915
	safecat(headers,header,MAX_HEADERS_SIZE);
916
917
	
	/* Entity Headers */
918
	if(session->req.dynamic) {
919
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST");
920
921
922
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
	else {
923
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD");
924
925
		safecat(headers,header,MAX_HEADERS_SIZE);
	}
926

927
	if(session->req.send_location) {
928
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
929
		safecat(headers,header,MAX_HEADERS_SIZE);
930
	}
931
	if(session->req.keep_alive) {
deuce's avatar
deuce committed
932
		if(ret)  {
933
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
934
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
935
		}
936
		else  {
937
			safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
938
			safecat(headers,header,MAX_HEADERS_SIZE);
939
		}
940
	}
941

942
	if(!ret && !session->req.dynamic)  {
943
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
944
		safecat(headers,header,MAX_HEADERS_SIZE);
945
		gmtime_r(&stats.st_mtime,&tm);
946
		safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
947
			,get_header(HEAD_LASTMODIFIED)
948
949
			,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
			,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
950
		safecat(headers,header,MAX_HEADERS_SIZE);
951
	} 
rswindell's avatar
rswindell committed
952

953
954
	if(session->req.dynamic)  {
		/* Dynamic headers */
955
		/* Set up environment */
956
		p=session->req.dynamic_heads;
957
		while(p != NULL)  {
958
			safecat(headers,p->val,MAX_HEADERS_SIZE);
959
			p=p->next;
960
		}
961
	}
962

963
	safecat(headers,"",MAX_HEADERS_SIZE);
964
	send_file = (sockprint(session->socket,headers) && send_file);
deuce's avatar
deuce committed
965
	FREE_AND_NULL(headers);
966
	return(send_file);
967
968
}

969
static int sock_sendfile(SOCKET socket,char *path)
970
971
{
	int		file;
972
	long	offset=0;
973
	int		ret=0;
974

975
	if(startup->options&WEB_OPT_DEBUG_TX)
976
		lprintf(LOG_DEBUG,"%04d Sending %s",socket,path);
977
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
978
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",socket,errno,path);
979
	else {
980
		if((ret=sendfilesocket(socket, file, &offset, 0)) < 1) {
981
			lprintf(LOG_DEBUG,"%04d !ERROR %d sending %s"
deuce's avatar
deuce committed
982
				, socket, errno, path);
983
984
			ret=0;
		}
985
986
		close(file);
	}
987
	return(ret);
988
989
}

deuce's avatar
deuce committed
990
991
992
993
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
994
static void send_error(http_session_t * session, const char* message)
995
996
{
	char	error_code[4];
997
	struct stat	sb;
998
	char	sbuf[1024];
999

1000
	session->req.if_modified_since=0;
1001
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
1002
	session->req.keep_alive=FALSE;
1003
	session->req.send_location=NO_LOCATION;
1004
	SAFECOPY(error_code,message);
1005
	sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
deuce's avatar
deuce committed
1006
1007
	session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
	send_headers(session,message);
1008
	if(!stat(session->req.physical_path,&sb)) {
1009
1010
1011
1012
1013
1014
		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;
1015
	}
1016
	else {
1017
		lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
1018
			,session->socket,session->req.physical_path);
1019
		safe_snprintf(sbuf,sizeof(sbuf)
1020
1021
1022
			,"<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>"
1023
1024
1025
			"please notify <a href=\"mailto:sysop@%s\">"
			"%s</a></BODY></HTML>"
			,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
1026
		sockprint(session->socket,sbuf);
1027
1028
		if(session->req.ld!=NULL)
			session->req.ld->size=strlen(sbuf);
1029
	}
1030
1031
1032
	close_request(session);
}

1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
void http_logon(http_session_t * session, user_t *usr)
{
	if(usr==NULL)
		getuserdat(&scfg, &session->user);

	if(session->user.number==session->last_user_num)
		return;
	if(session->user.number==0)
		SAFECOPY(session->username,unknown);
	else
		SAFECOPY(session->username,session->user.alias);
	session->last_user_num=session->user.number;
	session->logon_time=time(NULL);
}

void http_logoff(http_session_t * session)
{
	if(session->last_user_num<=0)
		return;
	SAFECOPY(session->username,unknown);
	logoutuserdat(&scfg, &session->user, time(NULL), session->logon_time);
	memset(&session->user,0,sizeof(session->user));
	session->last_user_num=session->user.number;
}

BOOL http_checkuser(http_session_t * session)
{
1060
	if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
1061
1062
		if(session->last_js_user_num==session->user.number)
			return(TRUE);
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
		lprintf(LOG_INFO,"%04d JavaScript: Initializing User Objects",session->socket);
		if(session->user.number>0) {
			if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user
				,NULL /* ftp index file */, NULL /* subscan */)) {
				lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
				send_error(session,"500 Error initializing JavaScript User Objects");
				return(FALSE);
			}
		}
		else {
			if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, NULL
				,NULL /* ftp index file */, NULL /* subscan */)) {
				lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript User Objects",session->socket);
				send_error(session,"500 Error initializing JavaScript User Objects");
				return(FALSE);
			}
		}
1080
		session->last_js_user_num=session->user.number;
1081
1082
1083
1084
	}
	return(TRUE);
}

1085
static BOOL check_ars(http_session_t * session)
1086
1087
1088
1089
{
	char	*username;
	char	*password;
	uchar	*ar;
1090
	BOOL	authorized;
1091
	char	auth_req[MAX_REQUEST_LINE+1];
1092
1093
	int		i;
	user_t	thisuser;
1094

1095
	if(session->req.auth[0]==0) {
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
		/* No authentication information... */
		if(session->last_user_num!=0) {
			if(session->last_user_num>0)
				http_logoff(session);
			session->user.number=0;
			http_logon(session,NULL);
		}
		if(!http_checkuser(session))
			return(FALSE);
		if(session->req.ars[0]) {
			/* There *IS* an ARS string  ie: Auth is required */
			if(startup->options&WEB_OPT_DEBUG_RX)
				lprintf(LOG_NOTICE,"%04d !No authentication information",session->socket);
			return(FALSE);
		}
		/* No auth required, allow */
		return(TRUE);
1113
	}
1114
	SAFECOPY(auth_req,session->req.auth);
1115

1116
	username=strtok(auth_req,":");
1117
1118
	if(username==NULL)
		username="";
1119
1120
1121
	password=strtok(NULL,":");
	/* Require a password */
	if(password==NULL)
1122
		password="";
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
	i=matchuser(&scfg, username, FALSE);
	if(i==0) {
		if(session->last_user_num!=0) {
			if(session->last_user_num>0)
				http_logoff(session);
			session->user.number=0;
			http_logon(session,NULL);
		}
		if(!http_checkuser(session))
			return(FALSE);