Skip to content
Snippets Groups Projects
websrvr.c 209 KiB
Newer Older
/* Synchronet Web Server */

/****************************************************************************
 * @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										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * 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
 *
 * Add support for UNIX-domain sockets for FastCGI
 *
 * Improved Win32 support for POST data... currently will read past Content-Length
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"
#include "xpendian.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"
#include "fastcgi.h"
#include "git_branch.h"
#include "git_hash.h"
static const char*	server_name="Synchronet Web Server";
static const char*	server_abbrev = "web";
static const char*	newline="\r\n";
static const char*	http_scheme="http://";
static const size_t	http_scheme_len=7;
deuce's avatar
deuce committed
static const char*	https_scheme="https://";
static const size_t	https_scheme_len=8;
static const char*	error_301="301 Moved Permanently";
static const char*	error_302="302 Moved Temporarily";
deuce's avatar
deuce committed
static const char*	error_307="307 Temporary Redirect";
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=STR_UNKNOWN_USER;
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
deuce's avatar
deuce committed
										   (Including terminator )*/
#define MAX_REDIR_LOOPS			20		/* Max. times to follow internal redirects for a single request */
#define MAX_POST_LEN			(4*1048576)	/* Max size of body for POSTS */
#define	OUTBUF_LEN				(256*1024)	/* 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	client_highwater=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		root_dir[MAX_PATH+1];
static char		error_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 struct mqtt mqtt;
static named_string_t** mime_types;
static named_string_t** cgi_handlers;
static named_string_t** xjs_handlers;
static named_string_t** alias_list; // request path aliases
struct log_data {
	char	*hostname;
	char	*ident;
	char	*user;
	char	*request;
	char	*referrer;
	char	*agent;
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) */
	BOOL			send_content;
deuce's avatar
deuce committed
	BOOL			upgrading;
	char*		location_to_send;
	char*		vary_list;
	char		status[MAX_REQUEST_LINE+1];
	char		request_line[MAX_REQUEST_LINE+1];
	char		orig_request_line[MAX_REQUEST_LINE+1];
	BOOL		finished;				/* Done processing request. */
	BOOL		read_chunked;
	BOOL		write_chunked;
	off_t		range_start;
	off_t		range_end;
	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;
	char	*fastcgi_socket;
} 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 */
deuce's avatar
deuce committed
	int				http_ver;       /* Request HTTP version.  0 = HTTP/0.9, 1=HTTP/1.0, 2=HTTP/1.1 */
	BOOL			finished;		/* Do not accept any more imput from client */
	enum parsed_vpath parsed_vpath; /* file area/base access */
	int				libnum;
	user_t			user;
	int				last_user_num;
	time_t			logon_time;
	char			username[LEN_NAME+1];
	char			redir_req[MAX_REQUEST_LINE+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
enum {
};
static char* http_vers[] = {
	 ""
	,"HTTP/1.0"
rswindell's avatar
rswindell committed
	,NULL	/* terminator */
deuce's avatar
deuce committed
static char* response_http_vers[] = {
	 ""
	,"HTTP/1.1"
	,"HTTP/1.1"
	,NULL	/* terminator */
};
rswindell's avatar
rswindell committed
static char* methods[] = {
	 "HEAD"
	,"GET"
rswindell's avatar
rswindell committed
	,NULL	/* terminator */
};
	,IS_FASTCGI
enum {
	 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
deuce's avatar
deuce committed
	,HEAD_STS
	,HEAD_UPGRADEINSECURE
	,HEAD_VARY
	,HEAD_CSP
};

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"				},
deuce's avatar
deuce committed
	{ HEAD_STS,			"Strict-Transport-Security"		},
	{ HEAD_UPGRADEINSECURE,		"Upgrade-Insecure-Requests"		},
	{ HEAD_VARY,			"Vary"					},
	{ HEAD_CSP,			"Content-Security-Policy"		},
	{ -1,					NULL /* terminator */	},
/* Everything MOVED_TEMP and everything after is a magical internal redirect */
deuce's avatar
deuce committed
	,MOVED_TEMPREDIR
#define GCES(status, sess, action) do {                                             \
	char *GCES_estr;                                                                \
	int GCES_level;                                                                 \
	get_crypt_error_string(status, sess->tls_sess, &GCES_estr, action, &GCES_level);\
	if (GCES_estr) {                                                                \
		if(GCES_level < startup->tls_error_level)                                   \
			GCES_level = startup->tls_error_level;                                  \
		lprintf(GCES_level, "%04d TLS %s", sess->socket, GCES_estr);                \
		free_crypt_attrstr(GCES_estr);                                              \
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_cx(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 int sess_recv(http_session_t *session, char *buf, size_t length, int flags);
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 lputs(int level, const char* str)
{
	mqtt_lputs(&mqtt, TOPIC_SERVER, level, str);
	if(level <= LOG_ERR) {
		char errmsg[1024];
		SAFEPRINTF2(errmsg, "%-4s %s", server_abbrev, str);
		errorlog(&scfg, &mqtt, level, startup == NULL ? NULL : startup->host_name, errmsg);
		if(startup != NULL && startup->errormsg != NULL)
			startup->errormsg(startup->cbdata, level, errmsg);
	}

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

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

    return startup->lputs(startup->cbdata,level,str);
}

#if defined(__GNUC__)   // Catch printf-format errors with lprintf
static int lprintf(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
#endif
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);
    return lputs(level, sbuf);
static int writebuf(http_session_t	*session, const char *buf, size_t len)
{
	size_t	sent=0;
	size_t	avail;
	if (session->req.sent_headers && session->req.send_content == FALSE)
		return (0);
deuce's avatar
deuce committed
		ResetEvent(session->outbuf.empty_event);
		avail=RingBufFree(&session->outbuf);
		if(!avail) {
			(void)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, action)  handle_crypt_call_except2(status, session, action, CRYPT_OK, CRYPT_OK, __FILE__, __LINE__)
#define HANDLE_CRYPT_CALL_EXCEPT(status, session, action, ignore)  handle_crypt_call_except2(status, session, action, ignore, ignore, __FILE__, __LINE__)
#define HANDLE_CRYPT_CALL_EXCEPT2(status, session, action, ignore, ignore2)  handle_crypt_call_except2(status, session, action, ignore, ignore2, __FILE__, __LINE__)
deuce's avatar
deuce committed

deuce's avatar
deuce committed
static BOOL handle_crypt_call_except2(int status, http_session_t *session, const char *action, int ignore, int ignore2, const char *file, int line)
deuce's avatar
deuce committed
{
	if (status == CRYPT_OK)
		return TRUE;
	if (status == ignore)
		return FALSE;
	if (status == ignore2)
		return FALSE;
deuce's avatar
deuce committed
	GCES(status, session, action);
deuce's avatar
deuce committed
	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;
deuce's avatar
deuce committed
		if(rd || wr == NULL) {
			if(session->tls_pending) {
deuce's avatar
deuce committed
				*rd_ptr = TRUE;
deuce's avatar
deuce committed
				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;
	BOOL local_failed = FALSE;
	if (failed == NULL)
		failed = &local_failed;

	while(sent<len && session->socket!=INVALID_SOCKET && *failed == FALSE) {
Deucе's avatar
Deucе committed
		if (socket_writable(session->socket, startup->max_inactivity * 1000)) {
			if (session->is_tls) {
				/*
				 * Limit as per js_socket.c.
				 * Sure, this is TLS, not SSH, but we see weird stuff here in sz file transfers.
				 */
				size_t sendbytes = len-sent;
				if (sendbytes > 0x2000)
					sendbytes = 0x2000;
				status = cryptPushData(session->tls_sess, buf+sent, sendbytes, &tls_sent);
				GCES(status, session, "pushing data");
				if (status == CRYPT_ERROR_TIMEOUT) {
					tls_sent = 0;
					if(!cryptStatusOK(status=cryptPopData(session->tls_sess, "", 0, &status))) {
						if (status != CRYPT_ERROR_TIMEOUT && status != CRYPT_ERROR_PARAM2)
							GCES(status, session, "popping data after timeout");
deuce's avatar
deuce committed
					}
Deucе's avatar
Deucе committed
					status = CRYPT_OK;
deuce's avatar
deuce committed
				}
Deucе's avatar
Deucе committed
				if(cryptStatusOK(status)) {
					HANDLE_CRYPT_CALL_EXCEPT(status = cryptFlushData(session->tls_sess), session, "flushing data", CRYPT_ERROR_COMPLETE);
					if (cryptStatusError(status))
						*failed=TRUE;
Deucе's avatar
Deucе committed
				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);
#ifdef EPIPE
					else if(ERROR_VALUE==EPIPE)
						lprintf(LOG_NOTICE,"%04d Unable to send to peer",session->socket);
#endif
					else
						lprintf(LOG_WARNING,"%04d !ERROR %d sending on socket",session->socket,ERROR_VALUE);
					*failed=TRUE;
					return(sent);
				}
			}
		}
		else {
			lprintf(LOG_WARNING,"%04d Timeout waiting for socket to become writable",session->socket);
			*failed=TRUE;
			return(sent);
	if(sent<len)
deuce's avatar
deuce committed
	if(session->is_tls)
deuce's avatar
deuce committed
		HANDLE_CRYPT_CALL(cryptFlushData(session->tls_sess), session, "flushing data");
#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)
static char* server_host_name(void)
{
	return startup->host_name[0] ? startup->host_name : scfg.sys_inetaddr;
}

static void set_state(enum server_state state)
	if(startup != NULL) {
		if(startup->set_state != NULL)
			startup->set_state(startup->cbdata, state);
		mqtt_server_state(&mqtt, state);
}

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

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);
	mqtt_client_on(&mqtt, 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);
	mqtt_client_on(&mqtt, FALSE, sock, NULL, FALSE);
}

static void thread_up(BOOL setuid)
{
	if(startup!=NULL && startup->thread_up!=NULL)
		startup->thread_up(startup->cbdata,TRUE, setuid);
	(void)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.orig_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
	if(startup!=NULL && startup->socket_open!=NULL)
		startup->socket_open(startup->cbdata,TRUE);
deuce's avatar
deuce committed
	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
}

static void close_socket_cb(SOCKET sock, void *cbdata)
{
	if(startup!=NULL && startup->socket_open!=NULL)
		startup->socket_open(startup->cbdata,FALSE);
static int close_socket(SOCKET *sock)
	char	ch;
	time_t	end = time(NULL) + startup->max_inactivity;
	if(sock==NULL || *sock==INVALID_SOCKET)
deuce's avatar
deuce committed
		return -1;
	/* required to ensure all data is sent */
	shutdown(*sock,SHUT_WR);
Deucе's avatar
Deucе committed
	while(socket_readable(*sock, startup->max_inactivity * 1000))  {
		if (recv(*sock,&ch,1,0) <= 0)
			break;
	result=closesocket(*sock);
	*sock=INVALID_SOCKET;
	if(startup!=NULL && startup->socket_open!=NULL) {
		startup->socket_open(startup->cbdata,FALSE);
	}
	if(result!=0) {
		if(ERROR_VALUE!=ENOTSOCK)