Skip to content
Snippets Groups Projects
websrvr.c 172 KiB
Newer Older
/* 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)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * 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.	*
 ****************************************************************************/

 * Support the ident protocol... the standard log format supports it.
 * 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.
 * 
 * Add support for multipart/form-data
 * 
deuce's avatar
deuce committed
/* Headers for CGI stuff */
#if defined(__unix__)
	#include <sys/wait.h>		/* waitpid() */
	#include <sys/types.h>
	#include <signal.h>			/* kill() */
#ifndef JAVASCRIPT
#undef SBBS	/* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbsdefs.h"
#include "sockwrap.h"		/* sendfilesocket() */
deuce's avatar
deuce committed
#include "multisock.h"
deuce's avatar
deuce committed
#include "base64.h"
#include "md5.h"
deuce's avatar
deuce committed
#include "js_socket.h"
#include "xpprintf.h"
deuce's avatar
deuce committed
#include "ssl.h"
static const char*	server_name="Synchronet Web Server";
static const char*	newline="\r\n";
static const char*	http_scheme="http://";
static const size_t	http_scheme_len=7;
static const char*	error_301="301 Moved Permanently";
static const char*	error_302="302 Moved Temporarily";
static const char*	error_404="404 Not Found";
static const char*	error_416="416 Requested Range Not Satisfiable";
static const char*	error_500="500 Internal Server Error";
deuce's avatar
deuce committed
static const char*	error_503="503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n";
static const char*	unknown="<unknown>";
deuce's avatar
deuce committed
static int len_503 = 0;
rswindell's avatar
rswindell committed
#define TIMEOUT_THREAD_WAIT		60		/* Seconds */
deuce's avatar
deuce committed
#define MAX_REQUEST_LINE		1024	/* NOT including terminator */
#define MAX_HEADERS_SIZE		16384	/* Maximum total size of all headers 
										   (Including terminator )*/
#define MAX_REDIR_LOOPS			20		/* Max. times to follow internal redirects for a single request */
#define MAX_POST_LEN			1048576	/* Max size of body for POSTS */
#define	OUTBUF_LEN				20480	/* Size of output thread ring buffer */
enum {
	 CLEANUP_SSJS_TMP_FILE
	,CLEANUP_POST_DATA
static volatile BOOL	http_logging_thread_running=FALSE;
static protected_uint32_t active_clients;
static protected_uint32_t thread_count;
static volatile ulong	sockets=0;
static volatile BOOL	terminate_server=FALSE;
deuce's avatar
deuce committed
static volatile BOOL	terminated=FALSE;
static volatile BOOL	terminate_http_logging_thread=FALSE;
deuce's avatar
deuce committed
static struct xpms_set	*ws_set=NULL;
static char		revision[16];
static char		root_dir[MAX_PATH+1];
static char		error_dir[MAX_PATH+1];
static char		temp_dir[MAX_PATH+1];
static char		cgi_dir[MAX_PATH+1];
static char		cgi_env_ini[MAX_PATH+1];
static char		default_auth_list[MAX_PATH+1];
static volatile	time_t	uptime=0;
static volatile	ulong	served=0;
static web_startup_t* startup=NULL;
static js_server_props_t js_server_props;
static str_list_t recycle_semfiles;
static str_list_t shutdown_semfiles;
static named_string_t** mime_types;
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
struct log_data {
	char	*hostname;
	char	*ident;
	char	*user;
	char	*request;
	char	*referrer;
	char	*agent;
	int		status;
	unsigned int	size;
	struct tm completed;
};

enum auth_type {
	 AUTHENTICATION_UNKNOWN
	,AUTHENTICATION_BASIC
	,AUTHENTICATION_DIGEST
deuce's avatar
deuce committed
	,AUTHENTICATION_TLS_PSK
deuce's avatar
deuce committed
char *auth_type_names[] = {
	 "Unknown"
	,"Basic"
	,"Digest"
deuce's avatar
deuce committed
	,"TLS-PSK"
enum algorithm {
	 ALGORITHM_UNKNOWN
	,ALGORITHM_MD5
	,ALGORITHM_MD5_SESS
};

enum qop_option {
	 QOP_NONE
	,QOP_AUTH
	,QOP_AUTH_INT
	,QOP_UNKNOWN
};

typedef struct {
	enum auth_type	type;
	char			username[(LEN_ALIAS > LEN_NAME ? LEN_ALIAS : LEN_NAME)+1];
	char			password[LEN_PASS+1];
	char			*digest_uri;
	char			*realm;
	char			*nonce;
	enum algorithm	algorithm;
	enum qop_option	qop_value;
	char			*cnonce;
	char			*nonce_count;
	unsigned char	digest[16];		/* MD5 digest */
} authentication_request_t;

	char		virtual_path[MAX_PATH+1];
	char		physical_path[MAX_PATH+1];
	BOOL    	expect_go_ahead;
	time_t		if_modified_since;
	BOOL		keep_alive;
	char		ars[256];
	authentication_request_t	auth;
	char		host[128];				/* The requested host. (as used for self-referencing URLs) */
	char		vhost[128];				/* The requested host. (virtual host) */
	char		status[MAX_REQUEST_LINE+1];
	char		request_line[MAX_REQUEST_LINE+1];
	BOOL		finished;				/* Done processing request. */
	BOOL		read_chunked;
	BOOL		write_chunked;
	BOOL		accept_ranges;
deuce's avatar
deuce committed
	time_t		if_range;
	/* CGI parameters */
	char		query_str[MAX_REQUEST_LINE+1];
	char		extra_path_info[MAX_REQUEST_LINE+1];
	str_list_t	cgi_env;
	str_list_t	dynamic_heads;
	/* Dynamically (sever-side JS) generated HTML parameters */
	FILE*	fp;
	char		*cleanup_file[MAX_CLEANUPS];
	BOOL	sent_headers;
	BOOL	prev_write;
	/* webctrl.ini overrides */
	char	*digest_realm;
} http_request_t;

typedef struct  {
deuce's avatar
deuce committed
	union xp_sockaddr	addr;
	socklen_t		addr_len;
deuce's avatar
deuce committed
	char			host_ip[INET6_ADDRSTRLEN];
	char			host_name[128];	/* Resolved remote host */
	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 */
	user_t			user;
	int				last_user_num;
	time_t			logon_time;
	char			username[LEN_NAME+1];

	/* JavaScript parameters */
	JSRuntime*		js_runtime;
	JSContext*		js_cx;
	JSObject*		js_glob;
	JSObject*		js_query;
	JSObject*		js_header;
	subscan_t		*subscan;
	/* Ring Buffer Stuff */
	RingBuf			outbuf;
	sem_t			output_thread_terminated;
	int				outbuf_write_initialized;
	pthread_mutex_t	outbuf_write;
	/* Client info */
	client_t		client;

	/* Synchronization stuff */
	pthread_mutex_t	struct_filled;
deuce's avatar
deuce committed

	/* TLS Stuff */
	BOOL			is_tls;
	CRYPT_SESSION	tls_sess;
	BOOL			tls_pending;
	BOOL			peeked_valid;
	char			peeked;
deuce's avatar
deuce committed
static CRYPT_CONTEXT tls_context = -1;

enum {
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
rswindell's avatar
rswindell committed
	,NULL	/* terminator */
rswindell's avatar
rswindell committed
static char* methods[] = {
	 "HEAD"
	,"GET"
rswindell's avatar
rswindell committed
	,NULL	/* terminator */
};
	 HEAD_DATE
	,HEAD_HOST
	,HEAD_IFMODIFIED
	,HEAD_AUTH
	,HEAD_CONNECTION
	,HEAD_WWWAUTH
	,HEAD_STATUS
	,HEAD_ALLOW
	,HEAD_EXPIRES
	,HEAD_LASTMODIFIED
	,HEAD_LOCATION
	,HEAD_PRAGMA
	,HEAD_SERVER
	,HEAD_ACCEPT_RANGES
	,HEAD_CONTENT_RANGE
	,HEAD_RANGE
deuce's avatar
deuce committed
	,HEAD_IFRANGE
};

static struct {
	int		id;
	char*	text;
} headers[] = {
	{ HEAD_DATE,			"Date"					},
	{ HEAD_HOST,			"Host"					},
	{ HEAD_IFMODIFIED,		"If-Modified-Since"		},
	{ HEAD_LENGTH,			"Content-Length"		},
	{ HEAD_TYPE,			"Content-Type"			},
	{ HEAD_AUTH,			"Authorization"			},
	{ HEAD_CONNECTION,		"Connection"			},
	{ HEAD_WWWAUTH,			"WWW-Authenticate"		},
	{ HEAD_STATUS,			"Status"				},
	{ HEAD_ALLOW,			"Allow"					},
	{ HEAD_EXPIRES,			"Expires"				},
	{ HEAD_LASTMODIFIED,	"Last-Modified"			},
	{ HEAD_LOCATION,		"Location"				},
	{ HEAD_PRAGMA,			"Pragma"				},
	{ HEAD_SERVER,			"Server"				},
	{ HEAD_REFERER,			"Referer"				},
	{ HEAD_AGENT,			"User-Agent"			},
	{ HEAD_TRANSFER_ENCODING,			"Transfer-Encoding"			},
	{ HEAD_ACCEPT_RANGES,	"Accept-Ranges"			},
	{ HEAD_CONTENT_RANGE,	"Content-Range"			},
	{ HEAD_RANGE,			"Range"					},
deuce's avatar
deuce committed
	{ HEAD_IFRANGE,			"If-Range"				},
	{ -1,					NULL /* terminator */	},
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
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"};

static void respond(http_session_t * session);
static BOOL js_setup(http_session_t* session);
static char *find_last_slash(char *str);
static BOOL check_extra_path(http_session_t * session);
static BOOL exec_ssjs(http_session_t* session, char* script);
static BOOL ssjs_send_headers(http_session_t* session, int chunked);
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);	/* why not use gmtime_r instead? */
        if (tm2 == NULL || (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);
}
static int lprintf(int level, const char *fmt, ...)
{
	va_list argptr;
	char sbuf[1024];

	va_start(argptr,fmt);
    vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;
    va_end(argptr);
		errorlog(&scfg,startup==NULL ? NULL:startup->host_name, sbuf);
		if(startup!=NULL && startup->errormsg!=NULL)
			startup->errormsg(startup->cbdata,level,sbuf);
	}

    if(startup==NULL || startup->lputs==NULL || level > startup->log_level)
        return(0);

#if defined(_WIN32)
	if(IsBadCodePtr((FARPROC)startup->lputs))
		return(0);
#endif

    return(startup->lputs(startup->cbdata,level,sbuf));
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
	size_t	sent=0;
	size_t	avail;
deuce's avatar
deuce committed
		ResetEvent(session->outbuf.empty_event);
		avail=RingBufFree(&session->outbuf);
		if(!avail) {
deuce's avatar
deuce committed
			WaitForEvent(session->outbuf.empty_event, 1);
			continue;
		}
		if(avail > len-sent)
			avail=len-sent;
		sent+=RingBufWrite(&(session->outbuf), ((const BYTE *)buf)+sent, avail);
deuce's avatar
deuce committed
#define HANDLE_CRYPT_CALL(status, session)  handle_crypt_call(status, session, __FILE__, __LINE__)

static BOOL handle_crypt_call(int status, http_session_t *session, const char *file, int line)
{
deuce's avatar
deuce committed
	int		sock = 0;

	if (status == CRYPT_OK)
		return TRUE;
	if (session != NULL) {
		if (session->is_tls)
			estr = get_crypt_error(session->tls_sess);
deuce's avatar
deuce committed
		sock = session->socket;
	}
deuce's avatar
deuce committed
	if (estr) {
deuce's avatar
deuce committed
		lprintf(LOG_ERR, "%04d cryptlib error %d at %s:%d (%s)", sock, status, file, line, estr);
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	else
		lprintf(LOG_ERR, "%04d cryptlib error %d at %s:%d", sock, status, file, line);
	return FALSE;
}

static BOOL session_check(http_session_t *session, BOOL *rd, BOOL *wr, unsigned timeout)
{
	BOOL	ret = FALSE;
	BOOL	lcl_rd;
	BOOL	*rd_ptr = rd?rd:&lcl_rd;

	if (session->is_tls) {
		if(wr)
			*wr=1;
		if(rd) {
			if(session->tls_pending) {
				*rd = TRUE;
				return TRUE;
			}
		}
		ret = socket_check(session->socket, rd_ptr, wr, timeout);
		if (ret && *rd_ptr) {
			session->tls_pending = TRUE;
			return TRUE;
		}
		return ret;
	}
	return socket_check(session->socket, rd, wr, timeout);
}

static int sess_sendbuf(http_session_t *session, const char *buf, size_t len, BOOL *failed)
deuce's avatar
deuce committed
	int	tls_sent;
deuce's avatar
deuce committed
	int status;
deuce's avatar
deuce committed
	while(sent<len && session->socket!=INVALID_SOCKET) {
deuce's avatar
deuce committed
		FD_SET(session->socket,&wr_set);
		/* Convert timeout from ms to sec/usec */
		tv.tv_sec=startup->max_inactivity;
		tv.tv_usec=0;
deuce's avatar
deuce committed
		sel=select(session->socket+1,NULL,&wr_set,NULL,&tv);
deuce's avatar
deuce committed
				if (session->is_tls) {
					status = cryptPushData(session->tls_sess, buf+sent, len-sent, &tls_sent);
					if (status == CRYPT_ERROR_TIMEOUT) {
						tls_sent = 0;
deuce's avatar
deuce committed
						if(!cryptStatusOK(cryptPopData(session->tls_sess, "", 0, &status)))
							lprintf(LOG_NOTICE,"%04d Cryptlib error popping data after timeout",session->socket);
deuce's avatar
deuce committed
						status = CRYPT_OK;
					}
					if(!HANDLE_CRYPT_CALL(status, session)) {
						HANDLE_CRYPT_CALL(cryptFlushData(session->tls_sess), session);
						if (failed)
							*failed=TRUE;
						return tls_sent;
					}
					result = tls_sent;
				}
				else {
					result=sendsocket(session->socket,buf+sent,len-sent);
					if(result==SOCKET_ERROR) {
						if(ERROR_VALUE==ECONNRESET) 
							lprintf(LOG_NOTICE,"%04d Connection reset by peer on send",session->socket);
						else if(ERROR_VALUE==ECONNABORTED) 
							lprintf(LOG_NOTICE,"%04d Connection aborted by peer on send",session->socket);
deuce's avatar
deuce committed
						else if(ERROR_VALUE==EPIPE) 
							lprintf(LOG_NOTICE,"%04d Unable to send to peer",session->socket);
deuce's avatar
deuce committed
						else
							lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",session->socket,ERROR_VALUE);
						if(failed)
							*failed=TRUE;
						return(sent);
					}
deuce's avatar
deuce committed
				lprintf(LOG_WARNING,"%04d Timeout selecting socket for write",session->socket);
				if(failed)
					*failed=TRUE;
				return(sent);
			case -1:
deuce's avatar
deuce committed
				lprintf(LOG_WARNING,"%04d !ERROR %d selecting socket for write",session->socket,ERROR_VALUE);
		}
		sent+=result;
	}
	if(failed && sent<len)
		*failed=TRUE;
deuce's avatar
deuce committed
	if(session->is_tls)
		HANDLE_CRYPT_CALL(cryptFlushData(session->tls_sess), session);
#ifdef _WINSOCKAPI_

static WSADATA WSAData;
#define SOCKLIB_DESC WSAData.szDescription
static BOOL WSAInitialized=FALSE;

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

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) {
		lprintf(LOG_DEBUG,"%s %s",WSAData.szDescription, WSAData.szSystemStatus);
		WSAInitialized=TRUE;
		return (TRUE);
	}

    lprintf(LOG_CRIT,"!WinSock startup ERROR %d", status);
	return (FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)

#endif

static void status(char* str)
{
	if(startup!=NULL && startup->status!=NULL)
	    startup->status(startup->cbdata,str);
}

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

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

static void client_off(SOCKET sock)
{
	if(startup!=NULL && startup->client_on!=NULL)
		startup->client_on(startup->cbdata,FALSE,sock,NULL,FALSE);
}

static void thread_up(BOOL setuid)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(startup->cbdata,TRUE, setuid);
	protected_uint32_adjust(&thread_count,-1);
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(startup->cbdata,FALSE, FALSE);
deuce's avatar
deuce committed
/*********************************************************************/
/* Adds an environment variable to the sessions  cgi_env linked list */
/*********************************************************************/
static void add_env(http_session_t *session, const char *name,const char *value)  {
	if(name==NULL || value==NULL)  {
		lprintf(LOG_WARNING,"%04d Attempt to set NULL env variable", session->socket);
		return;
	}
	SAFECOPY(newname,name);

	for(p=newname;*p;p++)  {
		*p=toupper(*p);
		if(*p=='-')
			*p='_';
	}
deuce's avatar
deuce committed
	p=xp_asprintf("%s=%s",newname,value);
	if(p==NULL) {
		lprintf(LOG_WARNING,"%04d Cannot allocate memory for string", session->socket);
		return;
	}
	strListPush(&session->req.cgi_env,p);
deuce's avatar
deuce committed
	free(p);
deuce's avatar
deuce committed
/***************************************/
/* Initializes default CGI envirnoment */
/***************************************/
static void init_enviro(http_session_t *session)  {
	char	str[128];
	union xp_sockaddr sockaddr;
	socklen_t socklen = sizeof(sockaddr);

	add_env(session,"SERVER_SOFTWARE",VERSION_NOTICE);
	getsockname(session->socket, &sockaddr.addr, &socklen);
	sprintf(str,"%d",inet_addrport(&sockaddr));
	add_env(session,"SERVER_PORT",str);
	add_env(session,"GATEWAY_INTERFACE","CGI/1.1");
	if(!strcmp(session->host_name,session->host_ip))
		add_env(session,"REMOTE_HOST",session->host_name);
	add_env(session,"REMOTE_ADDR",session->host_ip);
	add_env(session,"REQUEST_URI",session->req.request_line);
deuce's avatar
deuce committed
 * Sends string str to socket sock... returns number of bytes written, or 0 on an error
 * Can not close the socket since it can not set it to INVALID_SOCKET
 */
static int bufprint(http_session_t *session, const char *str)
	int len;

	len=strlen(str);
	return(writebuf(session,str,len));
deuce's avatar
deuce committed
/**********************************************************/
/* Converts a month name/abbr to the 0-based month number */
/* ToDo: This probobly exists somewhere else already	  */
/**********************************************************/
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
/*******************************************************************/
/* Converts a date string in any of the common formats to a time_t */
/*******************************************************************/
static time_t decode_date(char *date)
{
	struct	tm	ti;

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

	token=strtok_r(date,",",&last);
	/* This probobly only needs to be 9, but the extra one is for luck. */
	if(strlen(date)>15) {
		/* Toss away week day */
		token=strtok_r(date," ",&last);
		token=strtok_r(NULL," ",&last);
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
		token=strtok_r(NULL," ",&last);
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
		token=strtok_r(NULL,":",&last);
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
		token=strtok_r(NULL,":",&last);
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
		token=strtok_r(NULL," ",&last);
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
		token=strtok_r(NULL,"",&last);
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token)-1900;
	}
	else  {
		/* RFC 1123 or RFC 850 */
		token=strtok_r(NULL," -",&last);
		if(token==NULL)
			return(0);
		ti.tm_mday=atoi(token);
		token=strtok_r(NULL," -",&last);
		if(token==NULL)
			return(0);
		ti.tm_mon=getmonth(token);
		token=strtok_r(NULL," ",&last);
		if(token==NULL)
			return(0);
		ti.tm_year=atoi(token);
		token=strtok_r(NULL,":",&last);
		if(token==NULL)
			return(0);
		ti.tm_hour=atoi(token);
		token=strtok_r(NULL,":",&last);
		if(token==NULL)
			return(0);
		ti.tm_min=atoi(token);
		token=strtok_r(NULL," ",&last);
		if(token==NULL)
			return(0);
		ti.tm_sec=atoi(token);
		if(ti.tm_year>1900)
			ti.tm_year -= 1900;
	}
deuce's avatar
deuce committed
static void open_socket(SOCKET sock, void *cbdata)
deuce's avatar
deuce committed
#ifdef SO_ACCEPTFILTER
	struct accept_filter_arg afa;
#endif
deuce's avatar
deuce committed
	startup->socket_open(startup->cbdata,TRUE);
	if (cbdata != NULL && !strcmp(cbdata, "TLS")) {
		if(set_socket_options(&scfg, sock, "web|http|tls", error, sizeof(error)))
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
	}
	else {
		if(set_socket_options(&scfg, sock, "web|http", error, sizeof(error)))
			lprintf(LOG_ERR,"%04d !ERROR %s",sock,error);
deuce's avatar
deuce committed
#ifdef SO_ACCEPTFILTER
	memset(&afa, 0, sizeof(afa));
	strcpy(afa.af_name, "httpready");
	setsockopt(sock, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa));
#endif

	sockets++;
}

static void close_socket_cb(SOCKET sock, void *cbdata)
{
	startup->socket_open(startup->cbdata,FALSE);
	sockets--;
static int close_socket(SOCKET *sock)
	if(sock==NULL || *sock==INVALID_SOCKET)
	/* required to ensure all data is send when SO_LINGER is off (Not functional on Win32) */
	shutdown(*sock,SHUT_RDWR);
	result=closesocket(*sock);
	*sock=INVALID_SOCKET;
	if(startup!=NULL && startup->socket_open!=NULL) {
		startup->socket_open(startup->cbdata,FALSE);
	}
	sockets--;
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)
			lprintf(LOG_WARNING,"%04d !ERROR %d closing socket",*sock, ERROR_VALUE);
deuce's avatar
deuce committed
static int close_session_socket(http_session_t *session)
{
	char	buf[1];
	int		len;

	if(session==NULL || session->socket==INVALID_SOCKET)
		return(-1);

	if (session->is_tls) {
		// First, wait for the ringbuffer to drain...
		while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET) {
			HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, buf, 1, &len), session);
			SLEEP(1);
		}
		// Now wait for tranmission to complete
		while(pthread_mutex_trylock(&session->outbuf_write) == EBUSY) {
			HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, buf, 1, &len), session);
			SLEEP(1);
		}
		pthread_mutex_unlock(&session->outbuf_write);
		HANDLE_CRYPT_CALL(cryptDestroySession(session->tls_sess), session);
	}
	return close_socket(&session->socket);
}

/* Waits for the outbuf to drain */
static void drain_outbuf(http_session_t * session)
{
	if(session->socket==INVALID_SOCKET)
		return;
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));
	/* ToDo: This should probobly timeout eventually... */
	while(RingBufFull(&session->outbuf) && session->socket!=INVALID_SOCKET)
		SLEEP(1);
	/* Lock the mutex to ensure data has been sent */
	while(session->socket!=INVALID_SOCKET && !session->outbuf_write_initialized)
	if(session->socket==INVALID_SOCKET)
	pthread_mutex_lock(&session->outbuf_write);		/* Win32 Access violation here on Jan-11-2006 - shutting down webserver while in use */
	pthread_mutex_unlock(&session->outbuf_write);
}

deuce's avatar
deuce committed
/**************************************************/
/* 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 */
/**************************************************/
static void close_request(http_session_t * session)
{
	if(session->req.write_chunked) {
		session->req.write_chunked=0;
		writebuf(session,"0\r\n",3);
		if(session->req.dynamic==IS_SSJS)
			ssjs_send_headers(session,FALSE);
		else
			/* Non-ssjs isn't capable of generating headers during execution */
			writebuf(session, newline, 2);
	/* Force the output thread to go NOW */
	sem_post(&(session->outbuf.highwater_sem));

	if(session->req.ld!=NULL) {
		now=time(NULL);
		localtime_r(&now,&session->req.ld->completed);
		listPushNode(&log_list,session->req.ld);
	strListFree(&session->req.headers);
	strListFree(&session->req.dynamic_heads);
	strListFree(&session->req.cgi_env);
	if(session->req.post_map != NULL) {
		xpunmap(session->req.post_map);
		session->req.post_data=NULL;
		session->req.post_map=NULL;
	}
	FREE_AND_NULL(session->req.post_data);
	FREE_AND_NULL(session->req.error_dir);
	FREE_AND_NULL(session->req.cgi_dir);
	FREE_AND_NULL(session->req.auth_list);
	FREE_AND_NULL(session->req.realm);
	FREE_AND_NULL(session->req.digest_realm);
	FREE_AND_NULL(session->req.auth_list);
	FREE_AND_NULL(session->req.auth.digest_uri);
	FREE_AND_NULL(session->req.auth.cnonce);
	FREE_AND_NULL(session->req.auth.realm);
	FREE_AND_NULL(session->req.auth.nonce);
	FREE_AND_NULL(session->req.auth.nonce_count);

	/*
	 * This causes all active http_session_threads to terminate.
	 */
	if((!session->req.keep_alive) || terminate_server) {
deuce's avatar
deuce committed
		close_session_socket(session);
	if(session->socket==INVALID_SOCKET)
		session->finished=TRUE;

deuce's avatar
deuce committed
	if(session->js_cx!=NULL && (session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS)) {
		JS_BEGINREQUEST(session->js_cx);
deuce's avatar
deuce committed
		JS_GC(session->js_cx);
		JS_ENDREQUEST(session->js_cx);
	if(session->subscan!=NULL)
		putmsgptrs(&scfg, session->user.number, session->subscan);
	if(session->req.fp!=NULL)
		fclose(session->req.fp);

	for(i=0;i<MAX_CLEANUPS;i++) {
		if(session->req.cleanup_file[i]!=NULL) {
			if(!(startup->options&WEB_OPT_DEBUG_SSJS))
				remove(session->req.cleanup_file[i]);
			free(session->req.cleanup_file[i]);
		}
	memset(&session->req,0,sizeof(session->req));
}

static int get_header_type(char *header)
{
	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
/* Opposite of get_header_type() */
static char *get_header(int id) 
{
	if(headers[id].id==id)
		return(headers[id].text);

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

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

static const char* get_mime_type(char *ext)
		return(unknown_mime_type);

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

	return(unknown_mime_type);
static char* get_cgi_handler(const char* fname)
	if(cgi_handlers==NULL || (ext=getfext(fname))==NULL)
		return(NULL);
	for(i=0;cgi_handlers[i]!=NULL;i++) {
		if(stricmp(cgi_handlers[i]->name, ext+1)==0)
			return(cgi_handlers[i]->value);
}

static BOOL get_xjs_handler(char* ext, http_session_t* session)
{
	size_t	i;

deuce's avatar
deuce committed
	if(ext==NULL || xjs_handlers==NULL || ext[0]==0)
		return(FALSE);

	for(i=0;xjs_handlers[i]!=NULL;i++) {
		if(stricmp(xjs_handlers[i]->name, ext+1)==0) {
			if(getfname(xjs_handlers[i]->value)==xjs_handlers[i]->value)	/* no path specified */
				SAFEPRINTF2(session->req.xjs_handler,"%s%s",scfg.exec_dir,xjs_handlers[i]->value);
			else
				SAFECOPY(session->req.xjs_handler,xjs_handlers[i]->value);
			return(TRUE);
		}
	}
	return(FALSE);
}

/* 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) {
	dstlen=strlen(dst);
	appendlen=strlen(append);
	if(dstlen+appendlen+2 < maxlen) {
		strcat(dst,append);
		strcat(dst,newline);
	}
}

deuce's avatar
deuce committed
/*************************************************/
/* Sends headers for the reply.					 */
/* HTTP/0.9 doesn't use headers, so just returns */
/*************************************************/
static BOOL send_headers(http_session_t *session, const char *status, int chunked)
	BOOL	send_file=TRUE;
	char	header[MAX_REQUEST_LINE+1];
	BOOL	send_entity=TRUE;
deuce's avatar
deuce committed
	if(session->socket==INVALID_SOCKET) {
		session->req.sent_headers=TRUE;
	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
		session->req.sent_headers=TRUE;
deuce's avatar
deuce committed
		if(session->req.ld != NULL)
			session->req.ld->status=atoi(status);
deuce's avatar
deuce committed
		return(TRUE);
deuce's avatar
deuce committed
	headers=malloc(MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
	if(headers==NULL)  {
		lprintf(LOG_CRIT,"Could not allocate memory for response headers.");
		return(FALSE);
	}
	*headers=0;
	if(!session->req.sent_headers) {
deuce's avatar
deuce committed
		session->req.sent_headers=TRUE;
		status_line=status;
		ret=stat(session->req.physical_path,&stats);
		if(session->req.method==HTTP_OPTIONS)
			ret=-1;
		if(!ret && session->req.if_modified_since && (stats.st_mtime <= session->req.if_modified_since) && !session->req.dynamic) {
			status_line="304 Not Modified";
			ret=-1;
			send_file=FALSE;
			send_entity=FALSE;
deuce's avatar
deuce committed
		if(!ret && session->req.if_range && (stats.st_mtime > session->req.if_range || session->req.dynamic)) {
			status_line="200 OK";
			session->req.range_start=0;
			session->req.range_end=0;
		}
		if(session->req.send_location==MOVED_PERM)  {
			status_line=error_301;
			ret=-1;
			send_file=FALSE;
		}
		if(session->req.send_location==MOVED_TEMP)  {
			status_line=error_302;
			ret=-1;
			send_file=FALSE;
		}
			session->req.ld->status=stat_code;
		if(stat_code==304 || stat_code==204 || (stat_code >= 100 && stat_code<=199)) {
		/* Status-Line */
		safe_snprintf(header,sizeof(header),"%s %s",http_vers[session->http_ver],status_line);

		lprintf(LOG_DEBUG,"%04d Result: %s",session->socket,header);
		safecat(headers,header,MAX_HEADERS_SIZE);
		/* General Headers */
		ti=time(NULL);
		if(gmtime_r(&ti,&tm)==NULL)
			memset(&tm,0,sizeof(tm));
		safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
			,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);
		safecat(headers,header,MAX_HEADERS_SIZE);
		if(session->req.keep_alive) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Keep-Alive");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_CONNECTION),"Close");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}
		/* Response Headers */
		safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_SERVER),VERSION_NOTICE);
		safecat(headers,header,MAX_HEADERS_SIZE);

		/* Entity Headers */
		if(session->req.dynamic) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, POST, OPTIONS");
			safecat(headers,header,MAX_HEADERS_SIZE);
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"none");
			safecat(headers,header,MAX_HEADERS_SIZE);
deuce's avatar
deuce committed
		}
		else {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ALLOW),"GET, HEAD, OPTIONS");
			safecat(headers,header,MAX_HEADERS_SIZE);
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_ACCEPT_RANGES),"bytes");
			safecat(headers,header,MAX_HEADERS_SIZE);
		if(session->req.send_location) {
			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LOCATION),(session->req.virtual_path));
			safecat(headers,header,MAX_HEADERS_SIZE);
		}

			safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TRANSFER_ENCODING),"Chunked");
			safecat(headers,header,MAX_HEADERS_SIZE);
		}

		/* DO NOT send a content-length for chunked */
		if(send_entity) {
			if((session->req.keep_alive || session->req.method == HTTP_HEAD) && session->req.dynamic!=IS_CGI && (!chunked)) {
				if(ret)  {
					safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_LENGTH),"0");
					safecat(headers,header,MAX_HEADERS_SIZE);
				}
				else  {
					if((session->req.range_start || session->req.range_end) && atoi(status_line)==206) {
						safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),session->req.range_end-session->req.range_start+1);
						safecat(headers,header,MAX_HEADERS_SIZE);
					}
					else {
						safe_snprintf(header,sizeof(header),"%s: %d",get_header(HEAD_LENGTH),(int)stats.st_size);
						safecat(headers,header,MAX_HEADERS_SIZE);
					}
			if(!ret && !session->req.dynamic)  {
				safe_snprintf(header,sizeof(header),"%s: %s",get_header(HEAD_TYPE),session->req.mime_type);
				safecat(headers,header,MAX_HEADERS_SIZE);
				gmtime_r(&stats.st_mtime,&tm);
				safe_snprintf(header,sizeof(header),"%s: %s, %02d %s %04d %02d:%02d:%02d GMT"
					,get_header(HEAD_LASTMODIFIED)
					,days[tm.tm_wday],tm.tm_mday,months[tm.tm_mon]
					,tm.tm_year+1900,tm.tm_hour,tm.tm_min,tm.tm_sec);
				safecat(headers,header,MAX_HEADERS_SIZE);
			}
			if(session->req.range_start || session->req.range_end) {
				switch(atoi(status_line)) {
					case 206:	/* Partial reply */
						safe_snprintf(header,sizeof(header),"%s: bytes %d-%d/%d",get_header(HEAD_CONTENT_RANGE),session->req.range_start,session->req.range_end,stats.st_size);
						safecat(headers,header,MAX_HEADERS_SIZE);
						break;
					default:
						safe_snprintf(header,sizeof(header),"%s: *",get_header(HEAD_CONTENT_RANGE));
						safecat(headers,header,MAX_HEADERS_SIZE);
						break;
				}
	if(session->req.dynamic)  {
		/* Dynamic headers */
		for(idx=0;session->req.dynamic_heads[idx]!=NULL;idx++)
			safecat(headers,session->req.dynamic_heads[idx],MAX_HEADERS_SIZE);
		/* free() the headers so they don't get sent again if more are sent at the end of the request (chunked) */
		strListFreeStrings(session->req.dynamic_heads);
	safecat(headers,"",MAX_HEADERS_SIZE);
	send_file = (bufprint(session,headers) && send_file);
	session->req.write_chunked=chunked;
deuce's avatar
deuce committed
	free(headers);
	return(send_file);
static int sock_sendfile(http_session_t *session,char *path,unsigned long start, unsigned long end)
	int		i;
	char	buf[2048];		/* Input buffer */
	unsigned long		remain;
	if(startup->options&WEB_OPT_DEBUG_TX)
		lprintf(LOG_DEBUG,"%04d Sending %s",session->socket,path);
	if((file=open(path,O_RDONLY|O_BINARY))==-1)
		lprintf(LOG_WARNING,"%04d !ERROR %d opening %s",session->socket,errno,path);
		if(start || end) {
			if(lseek(file, start, SEEK_SET)==-1) {
				lprintf(LOG_WARNING,"%04d !ERROR %d seeking to position %lu in %s",session->socket,ERROR_VALUE,start,path);
deuce's avatar
deuce committed
			remain=end-start+1;
		}
		else {
			remain=-1L;
		}
		while((i=read(file, buf, remain>sizeof(buf)?sizeof(buf):remain))>0) {
			if(writebuf(session,buf,i)!=i) {
				lprintf(LOG_WARNING,"%04d !ERROR sending %s",session->socket,path);
				return(0);
			}
			ret+=i;
deuce's avatar
deuce committed
/********************************************************/
/* Sends a specified error message, closes the request, */
/* and marks the session to be closed 					*/
/********************************************************/
static void send_error(http_session_t * session, const char* message)
	struct stat	sb;
	char	sbuf[MAX_PATH+1];
	char	sbuf2[MAX_PATH+1];
	if(session->socket==INVALID_SOCKET)
		return;
	session->req.if_modified_since=0;
	lprintf(LOG_INFO,"%04d !ERROR: %s",session->socket,message);
	session->req.send_location=NO_LOCATION;
	SAFECOPY(error_code,message);
	SAFECOPY(session->req.status,message);
	if(atoi(error_code)<500) {
		/*
		 * Attempt to run SSJS error pages
		 * If this fails, do the standard error page instead,
		 * ie: Don't "upgrade" to a 500 error
		 */

			/* We have a custom error directory from webctrl.ini look there first */
			sprintf(sbuf,"%s%s%s",session->req.error_dir,error_code,startup->ssjs_ext);
			if(stat(sbuf,&sb)) {
				/* No custom .ssjs error message... check for custom .html */
				sprintf(sbuf2,"%s%s.html",session->req.error_dir,error_code);
				if(stat(sbuf2,&sb)) {
					/* Nope, no custom .html error either, check for global ssjs one */
					sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext);
				}
			}
		}
		else
			sprintf(sbuf,"%s%s%s",error_dir,error_code,startup->ssjs_ext);
		if(!stat(sbuf,&sb)) {
			lprintf(LOG_INFO,"%04d Using SSJS error page",session->socket);
			session->req.dynamic=IS_SSJS;
			if(js_setup(session)) {
				sent_ssjs=exec_ssjs(session,sbuf);
					int	snt=0;

					lprintf(LOG_INFO,"%04d Sending generated error page",session->socket);
					snt=sock_sendfile(session,session->req.physical_path,0,0);
					if(snt<0)
						snt=0;
					if(session->req.ld!=NULL)
						session->req.ld->size=snt;
				}
				else
					 session->req.dynamic=IS_STATIC;
			else
				session->req.dynamic=IS_STATIC;
		if(session->req.error_dir) {
			sprintf(session->req.physical_path,"%s%s.html",session->req.error_dir,error_code);
			if(stat(session->req.physical_path,&sb))
				sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
		}
		else
deuce's avatar
deuce committed
			sprintf(session->req.physical_path,"%s%s.html",error_dir,error_code);
		session->req.mime_type=get_mime_type(strrchr(session->req.physical_path,'.'));
		send_headers(session,message,FALSE);
		if(!stat(session->req.physical_path,&sb)) {
			int	snt=0;
			snt=sock_sendfile(session,session->req.physical_path,0,0);
			if(snt<0)
				snt=0;
			if(session->req.ld!=NULL)
				session->req.ld->size=snt;
		}
		else {
			lprintf(LOG_NOTICE,"%04d Error message file %s doesn't exist"
				,session->socket,session->req.physical_path);
			safe_snprintf(sbuf,sizeof(sbuf)
				,"<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>"
				"please notify <a href=\"mailto:sysop@%s\">"
				"%s</a></BODY></HTML>"
				,error_code,error_code,error_code,scfg.sys_inetaddr,scfg.sys_op);
			if(session->req.ld!=NULL)
				session->req.ld->size=strlen(sbuf);
		}
	session->req.finished=TRUE;
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;
	lprintf(LOG_DEBUG,"%04d HTTP Logon (user #%d)",session->socket,session->user.number);
	if(session->subscan!=NULL)
		getmsgptrs(&scfg,session->user.number,session->subscan);

	session->logon_time=time(NULL);
		SAFECOPY(session->username,unknown);
		SAFECOPY(session->username,session->user.alias);
		/* Adjust Connect and host */
		SAFECOPY(session->user.modem, session->client.protocol);
		SAFECOPY(session->user.comp, session->host_name);
deuce's avatar
deuce committed
		SAFECOPY(session->user.ipaddr, session->host_ip);
		session->user.logontime = (time32_t)session->logon_time;
	session->client.user=session->username;
	client_on(session->socket, &session->client, /* update existing client record? */TRUE);

	session->last_user_num=session->user.number;
}

void http_logoff(http_session_t* session, SOCKET socket, int line)
	lprintf(LOG_DEBUG,"%04d HTTP Logoff (user #%d) from line %d"
		,socket,session->user.number, line);
	SAFECOPY(session->username,unknown);
	if(!logoutuserdat(&scfg, &session->user, time(NULL), session->logon_time))
		lprintf(LOG_ERR,"%04d !ERROR in logoutuserdat", socket);
	memset(&session->user,0,sizeof(session->user));
	session->last_user_num=session->user.number;
}

BOOL http_checkuser(http_session_t * session)
{
	if(session->req.dynamic==IS_SSJS || session->req.dynamic==IS_JS) {
		if(session->last_js_user_num==session->user.number)
			return(TRUE);
		lprintf(LOG_DEBUG,"%04d JavaScript: Initializing User Objects",session->socket);
		JS_BEGINREQUEST(session->js_cx);
rswindell's avatar
rswindell committed
			if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, &session->user, &session->client
				,NULL /* ftp index file */, session->subscan /* subscan */)) {
				JS_ENDREQUEST(session->js_cx);
				lprintf(LOG_ERR,"%04d !JavaScript ERROR creating user objects",session->socket);
				send_error(session,"500 Error initializing JavaScript User Objects");
				return(FALSE);
			}
		}
		else {
rswindell's avatar
rswindell committed
			if(!js_CreateUserObjects(session->js_cx, session->js_glob, &scfg, /* user: */NULL, &session->client
				,NULL /* ftp index file */, session->subscan /* subscan */)) {
				JS_ENDREQUEST(session->js_cx);
				lprintf(LOG_ERR,"%04d !ERROR initializing JavaScript User Objects",session->socket);
				send_error(session,"500 Error initializing JavaScript User Objects");
				return(FALSE);
			}
		}
		JS_ENDREQUEST(session->js_cx);
		session->last_js_user_num=session->user.number;
static void calculate_digest(http_session_t * session, char *ha1, char *ha2, unsigned char digest[MD5_DIGEST_SIZE])
{
	MD5		ctx;

	MD5_open(&ctx);
	MD5_digest(&ctx, ha1, strlen(ha1));
	MD5_digest(&ctx, ":", 1);
	/* exception on next line (session->req.auth.nonce==NULL) */
	MD5_digest(&ctx, session->req.auth.nonce, strlen(session->req.auth.nonce));
	MD5_digest(&ctx, ":", 1);

	if(session->req.auth.qop_value != QOP_NONE) {
		MD5_digest(&ctx, session->req.auth.nonce_count, strlen(session->req.auth.nonce_count));
		MD5_digest(&ctx, ":", 1);
		MD5_digest(&ctx, session->req.auth.cnonce, strlen(session->req.auth.cnonce));
		MD5_digest(&ctx, ":", 1);
		switch(session->req.auth.qop_value) {
			case QOP_AUTH:
				MD5_digest(&ctx, "auth", 4);
				break;
			case QOP_AUTH_INT:
				MD5_digest(&ctx, "auth-int", 7);
				break;
deuce's avatar
deuce committed
			default:
				break;
		}
		MD5_digest(&ctx, ":", 1);
	}
	MD5_digest(&ctx, ha2, strlen(ha2));
	MD5_close(&ctx, digest);
}

static BOOL digest_authentication(http_session_t* session, int auth_allowed, user_t thisuser, char** reason)
{
	unsigned char	digest[MD5_DIGEST_SIZE];
	char			ha1[MD5_DIGEST_SIZE*2+1];
	char			ha1l[MD5_DIGEST_SIZE*2+1];
	char			ha1u[MD5_DIGEST_SIZE*2+1];
	char			ha2[MD5_DIGEST_SIZE*2+1];
	char			*pass;
	char			*p;
	time32_t		nonce_time;
	time32_t		now;
	MD5				ctx;

	if((auth_allowed & (1<<AUTHENTICATION_DIGEST))==0) {
		*reason="digest auth not allowed";
		return(FALSE);
	}
	if(session->req.auth.qop_value==QOP_UNKNOWN) {
		*reason="QOP unknown";
		return(FALSE);
	}
	if(session->req.auth.algorithm==ALGORITHM_UNKNOWN) {
		*reason="algorithm unknown";
		return(FALSE);
	}
	/* Validate rules from RFC-2617 */
	if(session->req.auth.qop_value==QOP_AUTH
			|| session->req.auth.qop_value==QOP_AUTH_INT) {
		if(session->req.auth.cnonce==NULL) {
			*reason="no cnonce";
			return(FALSE);
		}
		if(session->req.auth.nonce_count==NULL) {
			*reason="no nonce count";
			return(FALSE);
		}
	}
	else {
		if(session->req.auth.cnonce!=NULL) {
			*reason="unexpected cnonce present";
			return(FALSE);
		}
		if(session->req.auth.nonce_count!=NULL) {
			*reason="unexpected nonce count present";
			return(FALSE);
		}
	}

	/* H(A1) */
	MD5_open(&ctx);
	MD5_digest(&ctx, session->req.auth.username, strlen(session->req.auth.username));
	MD5_digest(&ctx, ":", 1);
	MD5_digest(&ctx, session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name), strlen(session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name)));
	MD5_digest(&ctx, ":", 1);
	MD5_digest(&ctx, thisuser.pass, strlen(thisuser.pass));
	MD5_close(&ctx, digest);
	MD5_hex((BYTE*)ha1, digest);

	/* H(A1)l */
	pass=strdup(thisuser.pass);
	strlwr(pass);
	MD5_open(&ctx);
	MD5_digest(&ctx, session->req.auth.username, strlen(session->req.auth.username));
	MD5_digest(&ctx, ":", 1);
	MD5_digest(&ctx, session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name), strlen(session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name)));
	MD5_digest(&ctx, ":", 1);
	MD5_digest(&ctx, pass, strlen(pass));
	MD5_close(&ctx, digest);
	MD5_hex((BYTE*)ha1l, digest);

	/* H(A1)u */
	strupr(pass);
	MD5_open(&ctx);
	MD5_digest(&ctx, session->req.auth.username, strlen(session->req.auth.username));
	MD5_digest(&ctx, ":", 1);
	MD5_digest(&ctx, session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name), strlen(session->req.digest_realm?session->req.digest_realm:(session->req.realm?session->req.realm:scfg.sys_name)));
	MD5_digest(&ctx, ":", 1);
	MD5_digest(&ctx, thisuser.pass, strlen(thisuser.pass));
	MD5_close(&ctx, digest);
	MD5_hex((BYTE*)ha1u, digest);
	free(pass);

	/* H(A2) */
	MD5_open(&ctx);
	MD5_digest(&ctx, methods[session->req.method], strlen(methods[session->req.method]));
	MD5_digest(&ctx, ":", 1);
	/* exception here, session->req.auth.digest_uri==NULL */
	MD5_digest(&ctx, session->req.auth.digest_uri, strlen(session->req.auth.digest_uri));
	/* TODO QOP==AUTH_INT */
	if(session->req.auth.qop_value == QOP_AUTH_INT) {
		*reason="QOP value";
		return(FALSE);
	}
	MD5_close(&ctx, digest);
	MD5_hex((BYTE*)ha2, digest);

	/* Check password as in user.dat */
	calculate_digest(session, ha1, ha2, digest);
	if(thisuser.pass[0]) {	// Zero-length password is "special" (any password will work)
		if(memcmp(digest, session->req.auth.digest, sizeof(digest))) {
			/* Check against lower-case password */
			calculate_digest(session, ha1l, ha2, digest);
			if(memcmp(digest, session->req.auth.digest, sizeof(digest))) {
				/* Check against upper-case password */
				calculate_digest(session, ha1u, ha2, digest);
				if(memcmp(digest, session->req.auth.digest, sizeof(digest))) {
					*reason="digest mismatch";
					return(FALSE);
				}
			}
		}
	}

	/* Validate nonce */
	p=strchr(session->req.auth.nonce, '@');
	if(p==NULL) {
		session->req.auth.stale=TRUE;
		*reason="nonce lacks @";
		return(FALSE);
	}
	*p=0;
	if(strcmp(session->req.auth.nonce, session->client.addr)) {
		session->req.auth.stale=TRUE;
		*reason="nonce doesn't match client IP address";
		return(FALSE);
	}
	*p='@';
	p++;
	nonce_time=strtoul(p, &p, 10);
	if(*p) {
		session->req.auth.stale=TRUE;
		*reason="unexpected data after nonce time";
		return(FALSE);
	}
	now=(time32_t)time(NULL);
	if(nonce_time > now) {
		session->req.auth.stale=TRUE;
		*reason="nonce time in the future";
		return(FALSE);
	}
	if(nonce_time < now-1800) {
		session->req.auth.stale=TRUE;
		*reason="stale nonce time";
		return(FALSE);
	}

	return(TRUE);
}

deuce's avatar
deuce committed
static void badlogin(SOCKET sock, const char* prot, const char* user, const char* passwd, const char* host, union xp_sockaddr* addr)
deuce's avatar
deuce committed
	char addrstr[INET6_ADDRSTRLEN];
	ulong count;

	SAFEPRINTF(reason,"%s LOGIN", prot);
	count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
	if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold) {
		hacklog(&scfg, reason, user, passwd, host, addr);
#ifdef _WIN32
		if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
			PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif
	}
	if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
		filter_ip(&scfg, prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
deuce's avatar
deuce committed
			,host, inet_addrtop(addr, addrstr, sizeof(addrstr)), user, /* fname: */NULL);
	if(count>1)
		mswait(startup->login_attempt_delay);
static BOOL check_ars(http_session_t * session)
	int		auth_allowed=0;
	unsigned *auth_list;
	unsigned auth_list_len;

	auth_list=parseEnumList(session->req.auth_list?session->req.auth_list:default_auth_list, ",", auth_type_names, &auth_list_len);
deuce's avatar
deuce committed
	for(i=0; ((unsigned)i)<auth_list_len; i++)
		auth_allowed |= 1<<auth_list[i];
	if(auth_list)
		free(auth_list);
	/* No authentication provided */
	if(session->req.auth.type==AUTHENTICATION_UNKNOWN) {
		/* No authentication information... */
		if(session->last_user_num!=0) {
			if(session->last_user_num>0)
				http_logoff(session,session->socket,__LINE__);
			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);
	i=matchuser(&scfg, session->req.auth.username, FALSE);
	if(i==0) {
		if(session->last_user_num!=0) {
			if(session->last_user_num>0)
				http_logoff(session,session->socket,__LINE__);
			session->user.number=0;
			http_logon(session,NULL);
		}
		if(!http_checkuser(session))
			return(FALSE);
		if(scfg.sys_misc&SM_ECHO_PW && session->req.auth.type==AUTHENTICATION_BASIC)
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: '%s' (password: %s)"
				,session->socket,session->req.auth.username,session->req.auth.password);
			lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: '%s'"
				,session->socket,session->req.auth.username);
	switch(session->req.auth.type) {
deuce's avatar
deuce committed
		case AUTHENTICATION_TLS_PSK:
			if((auth_allowed & (1<<AUTHENTICATION_TLS_PSK))==0)
				return(FALSE);
			if(session->last_user_num!=0) {
				if(session->last_user_num>0)
					http_logoff(session,session->socket,__LINE__);
				session->user.number=0;
				http_logon(session,NULL);
			}
			if(!http_checkuser(session))
				return(FALSE);
			break;
		case AUTHENTICATION_BASIC:
			if((auth_allowed & (1<<AUTHENTICATION_BASIC))==0)
				return(FALSE);
			if(thisuser.pass[0] && stricmp(thisuser.pass,session->req.auth.password)) {
				if(session->last_user_num!=0) {
					if(session->last_user_num>0)
						http_logoff(session,session->socket,__LINE__);
					session->user.number=0;
					http_logon(session,NULL);
				}
				if(!http_checkuser(session))
					return(FALSE);
				if(scfg.sys_misc&SM_ECHO_PW)
					lprintf(LOG_WARNING,"%04d !BASIC AUTHENTICATION FAILURE for user '%s' (password: %s)"
						,session->socket,session->req.auth.username,session->req.auth.password);
					lprintf(LOG_WARNING,"%04d !BASIC AUTHENTICATION FAILURE for user '%s'"
						,session->socket,session->req.auth.username);
				badlogin(session->socket,session->client.protocol, session->req.auth.username, session->req.auth.password, session->host_name, &session->addr);
				return(FALSE);
			}
			break;
		case AUTHENTICATION_DIGEST:
		{
			char* reason="unknown";
			if(!digest_authentication(session, auth_allowed, thisuser, &reason)) {
				lprintf(LOG_NOTICE,"%04d !DIGEST AUTHENTICATION FAILURE (reason: %s) for user '%s'"
						,session->socket,reason,session->req.auth.username);
				badlogin(session->socket,session->client.protocol, session->req.auth.username, "<digest>", session->host_name, &session->addr);
deuce's avatar
deuce committed
		default:
			break;
		http_logoff(session,session->socket,__LINE__);
		session->user.number=i;
		http_logon(session,&thisuser);
	}
	if(!http_checkuser(session))
		return(FALSE);

	if(session->req.ld!=NULL) {
		FREE_AND_NULL(session->req.ld->user);
		/* FREE()d in http_logging_thread */
		session->req.ld->user=strdup(session->req.auth.username);
	ar = arstr(NULL,session->req.ars,&scfg);
rswindell's avatar
rswindell committed
	authorized=chk_ar(&scfg,ar,&session->user,&session->client);
deuce's avatar
deuce committed
		FREE_AND_NULL(ar);
	if(authorized)  {
		switch(session->req.auth.type) {
deuce's avatar
deuce committed
			case AUTHENTICATION_TLS_PSK:
				add_env(session,"AUTH_TYPE","TLS-PSK");
				break;
			case AUTHENTICATION_BASIC:
				add_env(session,"AUTH_TYPE","Basic");
				break;
			case AUTHENTICATION_DIGEST:
				add_env(session,"AUTH_TYPE","Digest");
				break;
deuce's avatar
deuce committed
			default:
				break;
		/* Should use real name if set to do so somewhere ToDo */
		add_env(session,"REMOTE_USER",session->user.alias);
		if(thisuser.pass[0])
			loginSuccess(startup->login_attempt_list, &session->addr);

	lprintf(LOG_WARNING,"%04d !AUTHORIZATION FAILURE for user %s, ARS: %s"
		,session->socket,session->req.auth.username,session->req.ars);
#ifdef _WIN32
	if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
		PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif

static named_string_t** read_ini_list(char* path, char* section, char* desc
	if((fp=iniOpenFile(path, /* create? */FALSE))!=NULL) {
		list=iniReadNamedStringList(fp,section);
		iniCloseFile(fp);
		COUNT_LIST_ITEMS(list,i);
			lprintf(LOG_DEBUG,"Read %u %s from %s section of %s"
				,i,desc,section==NULL ? "root":section,path);
	} else
		lprintf(LOG_WARNING, "Error %d opening %s", errno, path);
deuce's avatar
deuce committed
static int sess_recv(http_session_t *session, char *buf, size_t length, int flags)
{
	int len;
	
	if (session->is_tls) {
		if (flags == MSG_PEEK) {
			if (session->peeked_valid) {
				buf[0] = session->peeked;
				return 1;
			}
			if (HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, &session->peeked, 1, &len), session)) {
				if (len == 1) {
					session->peeked_valid = TRUE;
					buf[0] = session->peeked;
					return 1;
				}
				return 0;
			}
			return -1;
		}
		else {
			if (session->peeked_valid) {
				buf[0] = session->peeked;
				session->peeked_valid = FALSE;
				return 1;
			}
			if (HANDLE_CRYPT_CALL(cryptPopData(session->tls_sess, buf, length, &len), session)) {
				if (len == 0) {
					session->tls_pending = FALSE;
				}
				return len;
			}
			return -1;
		}
	}
	else {
		return recv(session->socket, buf, length, flags);
	}
}

static int sockreadline(http_session_t * session, char *buf, size_t length)
	fd_set	rd_set;
	struct	timeval tv;
	for(i=0;TRUE;) {
		if(session->socket==INVALID_SOCKET)
			return(-1);
deuce's avatar
deuce committed
		if ((!session->is_tls) || (!session->tls_pending)) {
			FD_ZERO(&rd_set);
			FD_SET(session->socket,&rd_set);
			/* Convert timeout from ms to sec/usec */
			tv.tv_sec=startup->max_inactivity;
			tv.tv_usec=0;
			sel=select(session->socket+1,&rd_set,NULL,NULL,&tv);
			switch(sel) {
				case 1:
					if (session->is_tls)
						session->tls_pending=TRUE;
					break;
				case -1:
					close_session_socket(session);
					lprintf(LOG_DEBUG,"%04d !ERROR %d selecting socket for read",session->socket,ERROR_VALUE);
					return(-1);
				default:
					/* Timeout */
					lprintf(LOG_NOTICE,"%04d Session timeout due to inactivity (%d seconds)",session->socket,startup->max_inactivity);
					return(-1);
			}
deuce's avatar
deuce committed
		switch(sess_recv(session, &ch, 1, 0)) {
deuce's avatar
deuce committed
				if(session->is_tls || ERROR_VALUE!=EAGAIN) {
					if(startup->options&WEB_OPT_DEBUG_RX)
						lprintf(LOG_DEBUG,"%04d !ERROR %d receiving on socket",session->socket,ERROR_VALUE);
deuce's avatar
deuce committed
					close_session_socket(session);
				/* Socket has been closed */
deuce's avatar
deuce committed
				close_session_socket(session);
		if(ch=='\n')
			break;

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

	/* Terminate at length if longer */
	if(i>length)
		i=length;
	while(i>0 && buf[i-1]=='\r')
		i--;

	buf[i]=0;
	if(startup->options&WEB_OPT_DEBUG_RX) {
		lprintf(LOG_DEBUG,"%04d RX: %s",session->socket,buf);
			lprintf(LOG_DEBUG,"%04d Long header, chucked %d bytes",session->socket,chucked);
	return(i);
deuce's avatar
deuce committed
static int pipereadline(HANDLE pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len)
deuce's avatar
deuce committed
static int pipereadline(int pipe, char *buf, size_t length, char *fullbuf, size_t fullbuf_len)
#ifndef _WIN32
	struct timeval tv={0,0};
	fd_set  read_set;
#endif
deuce's avatar
deuce committed
	/* Terminate buffers */
	if(buf != NULL)
		buf[0]=0;
	if(fullbuf != NULL)
		fullbuf[0]=0;
		ReadFile(pipe, &ch, 1, (DWORD*)&ret, NULL);
		tv.tv_sec=startup->max_cgi_inactivity;
		tv.tv_usec=0;
		FD_ZERO(&read_set);
		FD_SET(pipe, &read_set);
		if(select(pipe+1, &read_set, NULL, NULL, &tv)<1)
			return(-1);
		ret=read(pipe, &ch, 1);
deuce's avatar
deuce committed
			if(fullbuf != NULL && i < (fullbuf_len-1)) {
				fullbuf[i]=ch;
				fullbuf[i+1]=0;
			}

deuce's avatar
deuce committed
			if(buf != NULL && i<length)
				buf[i]=ch;

			i++;
	}

	/* Terminate at length if longer */
	if(i>length)
		i=length;
deuce's avatar
deuce committed
	if(i>0 && buf != NULL && buf[i-1]=='\r')
		buf[--i]=0;
deuce's avatar
deuce committed
	else {
		if(buf != NULL)
			buf[i]=0;
	}
	return(i);
deuce's avatar
deuce committed
static int recvbufsocket(http_session_t *session, char *buf, long count)
deuce's avatar
deuce committed
	while(rd<count && session_check(session,NULL,NULL,startup->max_inactivity*1000))  {
		i=sess_recv(session,buf+rd,count-rd,0);
deuce's avatar
deuce committed
				if(session->is_tls || ERROR_VALUE!=EAGAIN)
					close_session_socket(session);
deuce's avatar
deuce committed
				close_session_socket(session);
	return(0);
static void unescape(char *p)
{
	char *	dst;
	char	code[3];
	
	dst=p;
	for(;*p;p++) {
		if(*p=='%' && isxdigit((uchar)*(p+1)) && isxdigit((uchar)*(p+2))) {
			sprintf(code,"%.2s",p+1);
			*(dst++)=(char)strtol(code,NULL,16);
			p+=2;
		}
		else  {
			if(*p=='+')  {
				*(dst++)=' ';
			}
			else  {
				*(dst++)=*p;
			}
		}
	}
	*(dst)=0;
}

static void js_add_queryval(http_session_t * session, char *key, char *value)
	jsuint		len;
	int			alen;

	/* Return existing object if it's already been created */
	if(JS_GetProperty(session->js_cx,session->js_query,key,&val) && val!=JSVAL_VOID)  {
		keyarray = JSVAL_TO_OBJECT(val);
		alen=-1;
	}
Loading
Loading full blame...