dawn/notes/
dawn/src/
dawn/src/docs/
/**************************************************************************/
// dawnstat.cpp - provide statistics on dawn based muds to dawnoftime.org
/***************************************************************************
 * The Dawn of Time v1.69r (c)1997-2004 Michael Garratt                    *
 * >> A number of people have contributed to the Dawn codebase, with the   *
 *    majority of code written by Michael Garratt - www.dawnoftime.org     *
 * >> To use this source code, you must fully comply with the dawn license *
 *    in licenses.txt... In particular, you may not remove this copyright  *
 *    notice.                                                              *
 **************************************************************************/
// About: The code within this module sends to dawnoftime.org statistical
//        information.  Using this information dawnoftime.org is able to 
//        get an idea of the number of dawn based muds running and 
//        hopefully the number of players.
//      
//        The statistical information submitted to dawnoftime.org is a 
//        summary of mudstats, lastonstats and mudclientstats, combined 
//        with a few other things specific to your mud environment 
//        (such as the name of the mud).  All information is kept private
//        and not made publically available through dawnoftime.org... but
//        in the future dawnoftime.org may include the statistical 
//        information to report how many dawn based muds are running, how
//        popular various mud clients are etc.
//
//        This information may be made publically available at
//        either the dawnoftime.org website or 
//        http://statistics.dawnoftime.org in the future (once enough 
//        muds are running dawn to make it worth while).
//      
//        By default the mud after about 10 minutes of running will 
//        send the stats to http://dawnstat2.dawnoftime.org/dawnstat2.php
//        This does not lag the mud in anyway, (unless your dns resolver
//        is broken - use sockets to determine this), if the dns resolver 
//        is broken there may be a one off small delay (ordinarily less 
//        than 5 seconds) while the mud resolves the ip address of 
//        dawnstat.dawnoftime.org in order to know where to send the 
//        stats to.
// 
// 

/**************************************************************************/
//#define DAWNSTAT_LOG_PROGRESS 

#define DAWNSTAT_SUBMIT_DOMAIN "dawnstat2.dawnoftime.org"
#define DAWNSTAT_SUBMIT_URL "/dawnstat2.php"

#define DAWNSTAT_DELAY 1800
/**************************************************************************/
#include "network.h"
#define __SEE_NETIO_INTERNAL_STRUCTURES__
#include "comm.h"
#include "include.h"
#include "laston.h"

#ifdef unix
	typedef int SOCKET;
	typedef struct sockaddr_in SOCKADDR_IN;
	#define SOCKET_ERROR            (-1)
	#define INVALID_SOCKET  (SOCKET)(~0)
#endif

#ifdef IPV6_SUPPORT_ENABLED
	struct addrinfo *res=NULL;
#else
	ipv4only_addrinfo *res=NULL;
#endif

#ifndef EISCONN
	#define EISCONN 56 // is already connected
#endif
/**************************************************************************/
static void dawnstat_logf(char * fmt, ...)
{
#ifndef DAWNSTAT_LOG_PROGRESS 
	return;
#endif
    char buf[HSL];
	va_list args;
	va_start(args, fmt);
	vsnprintf(buf, HSL-MIL, fmt, args);
	va_end(args);

	logf("dawnstat: %s", buf);
}
/**************************************************************************/
int count_player_list(void)
{
	int count=0;
	char_data *ch=player_list;
	for ( ; ch; ch= ch->next_player){
        count++;  
	}
	return count;
}
/**************************************************************************/
int count_active_players(void);
char *get_compile_time (bool show_parent_codebase_version);
extern time_t laston_since;
void do_lastonstats( char_data *ch, char *argument);
char *doWebFunction(DO_FUN * do_fun, char *argument, int level);

/**************************************************************************/
static char *dawnstat_generate_statistics_text()
{
	static char result[45000];
	char stats[45000];
	stats[0]='\0';
	char *binded_sockets=netio_return_binded_sockets();

#define ENCODE_INT(field) strcat(stats, FORMATF("&" # field"=%d", field))
#define ENCODE_INTH(field, header) strcat(stats, FORMATF("&" header"=%d", field))
#define ENCODE_STR(field) strcat(stats, FORMATF("&" # field"=%s", url_encode_post_data(field)))
#define ENCODE_STRH(field, header) strcat(stats, FORMATF("&" header"=%s", url_encode_post_data(field)))

	ENCODE_STRH(game_settings->gamename,			"gamename");
	ENCODE_INTH(game_settings->unique_id,			"unique_id");
	ENCODE_STRH(game_settings->listen_on,		"listen_on");
	ENCODE_STR(binded_sockets);
	ENCODE_INTH(mainport,	"port");
	ENCODE_STRH(DAWN_RELEASE_VERSION, "release_version");
	ENCODE_STRH(DAWN_RELEASE_DATE, "release_date");
	ENCODE_STRH(fwrite_wordflag(game_settings_flags, game_settings->flags,"gamesettings_flags",NULL), "gamesettings_flags");
	ENCODE_STRH(fwrite_wordflag(game_settings_flags2, game_settings->flags2,"gamesettings_flags2",NULL), "gamesettings_flags2");
	ENCODE_STRH(fwrite_wordflag(game_settings_flags3, game_settings->flags3,"gamesettings_flags3",NULL), "gamesettings_flags3");
	ENCODE_STRH(fwrite_wordflag(game_settings_flags4, game_settings->flags4,"gamesettings_flags4",NULL), "gamesettings_flags4");	
	ENCODE_STRH(get_compile_time(false), "compile_time_text");
	ENCODE_INTH(game_settings->damage_scale_value, "damage_scale_value");
	ENCODE_INTH(game_settings->global_xp_scale_value,"global_xp_scale_value");	
	ENCODE_INTH(game_settings->newbie_start_gold,"newbie_start_gold");	
	ENCODE_INTH(game_settings->newbie_start_silver,"newbie_start_silver");	
	ENCODE_INTH(game_settings->newbie_start_practice,"newbie_start_practice");	
	ENCODE_INTH(game_settings->newbie_start_train,"newbie_start_train");	
	ENCODE_INT(top_area);
	ENCODE_INT(top_room); 
	ENCODE_INT(top_shop); 
	ENCODE_INT(top_mob_index); 
	ENCODE_INT(mobile_count); 
	ENCODE_INT(mobprog_count); 
	ENCODE_INT(top_obj_index); 
	ENCODE_INT(top_help); 
	ENCODE_INT(social_count);
	ENCODE_INTH(count_active_players(), "active_player_count");
	ENCODE_INTH(count_player_list(), "current_playerlist_count");
	ENCODE_INT(max_on);
	ENCODE_INT(LEVEL_IMMORTAL);
	ENCODE_INT(MAX_LEVEL);
	ENCODE_INT((int)current_time);
	ENCODE_INT((int)boot_time);
	ENCODE_INT((int)laston_since);

#ifdef __DATE__
	ENCODE_STRH(__DATE__,"compiled_date");
#endif
#ifdef __TIME__
	ENCODE_STRH(__TIME__,"compiled_time");
#endif
#if defined (__CYGWIN__)
	ENCODE_STRH("cygwin","compiled_platform");
#elif defined (WIN32)
	ENCODE_STRH("Win32","compiled_platform");
#elif defined (unix) || defined (__unix__)
#	if defined (linux)
		ENCODE_STRH("linux","compiled_platform");
#	elif defined (__OpenBSD__)
		ENCODE_STRH("OpenBSD","compiled_platform");
#	elif defined (__FreeBSD__)
		ENCODE_STRH("FreeBSD","compiled_platform");
#	elif defined (__NetBSD__)
		ENCODE_STRH("NetBSD","compiled_platform");
#	elif defined(__APPLE__) && defined(__MACH__)
		ENCODE_STRH("MacOSX","compiled_platform");
#	elif defined(BSD)
		ENCODE_STRH("BSD","compiled_platform");
#	else
		ENCODE_STRH("unix","compiled_platform");		
#	endif
#elif 
	ENCODE_STRH("unknown","compiled_platform");
#endif
	// possible compiler thing of interest
#ifdef __VERSION__ // e.g. "2.96 20000731 (Red Hat Linux 7.1 2.96-85)"
	ENCODE_STRH(__VERSION__,"compiler_version");
#endif 

	ENCODE_STRH(PLATFORM_INFO,"platform_info");	

	// retrieve a condensed version of laston stats output
	// will be able to be used to see how common classes/races are 
	// between muds, in addition stats about mccp usage etc.
	chImmortal->desc->colour_mode	= CT_NOCOLOUR;
	char *laston_stats=str_dup(doWebFunction(&do_lastonstats, "", MAX_LEVEL));
	chImmortal->desc->colour_mode	= CT_HTML;
	laston_stats=string_replace_all(laston_stats, "  ", " ");
	laston_stats=string_replace_all(laston_stats, "==", "=");
	laston_stats=string_replace_all(laston_stats, "\r", "");
	char *laston_stats_no_header=strstr(laston_stats, "-=");
	if(!laston_stats_no_header){
		laston_stats_no_header=laston_stats;
	}
	ENCODE_STRH(laston_stats_no_header,"laston_stats");
	free_string(laston_stats);

	laston_stats=str_dup(laston_generate_mud_client_stats());
	laston_stats=string_replace_all(laston_stats, "\r", "");
	ENCODE_STRH(laston_stats,"mudclient_stats");
	free_string(laston_stats);

	char *encoded=&stats[1];
	sprintf(result, 
		"POST " DAWNSTAT_SUBMIT_URL "  HTTP/1.1\r\n"
		"Content-Type: application/x-www-form-urlencoded\r\n"
		"Host: " DAWNSTAT_SUBMIT_DOMAIN "\r\n"
		"User-Agent: Mozilla/4.0 (compatible; DawnWebSubmit/1.0)\r\n"
		"Content-Length: %d\r\n"
		"Cache-Control: no-cache\r\n"
		"\r\n%s",
		str_len(encoded), 
		encoded);

	return result;
}
/**************************************************************************/
char *dawnstat_stattext_to_post;
char *dawnstat_post_response_position;
char dawnstat_post_response[MSL];
/**************************************************************************/
const char *get_winsock_error_text(int errorcode);
bool init_winsock();
int write_to_socket( dawn_socket output_socket, const char *txt, int length );
/**************************************************************************/
enum dawnstat_stages
{
	DAWNSTATSTAGE_WAIT,
	DAWNSTATSTAGE_RESOLVING_DOMAIN_REMOTE,
	DAWNSTATSTAGE_RESOLVING_DOMAIN_LOCAL,
	DAWNSTATSTAGE_DOMAIN_RESOLVED,
	DAWNSTATSTAGE_INITIATE_CONNECTION,
	DAWNSTATSTAGE_CONNECT_IN_PROGRESS,
	DAWNSTATSTAGE_GENERATE_STATS,
	DAWNSTATSTAGE_POSTING,
	DAWNSTATSTAGE_CHECK_POST_ACCEPTANCE,
	DAWNSTATSTAGE_CLOSE_SOCKET,
	DAWNSTATSTAGE_COMPLETED,
	DAWNSTATSTAGE_ABORTED
};

dawnstat_stages dawnstat_stage=DAWNSTATSTAGE_WAIT;
time_t dawnstat_connect_timeout=0;
time_t dawnstat_resolve_timeout=0;

static dawn_socket dawnstat_socket;
int dawnstat_address_count;
char dawnstat_connection_address_and_port[MIL];
/**************************************************************************/
// - resolve domain name, performed once per reboot
static void dawnstat_resolve_domain_directly()
{
	resolver_address_found=false;
	resolver_address_failed=false;
	dawnstat_stage=DAWNSTATSTAGE_RESOLVING_DOMAIN_LOCAL;
	dawnstat_resolve_timeout=current_time+20;

	resolverlocal_queue_command(FORMATF("resolve system %s.", DAWNSTAT_SUBMIT_DOMAIN));	

}
/**************************************************************************/
// - resolve domain name, performed once per reboot
static void dawnstat_resolve_domain()
{
	dawnstat_logf("starting resolving of '%s.'", DAWNSTAT_SUBMIT_DOMAIN);

	if(!resolver_running){
		dawnstat_logf("The resolver isn't running, using direct resolution.");
		dawnstat_resolve_domain_directly();
		return;
	}

	if(resolver_version<1500){
		dawnstat_logf("Old resolver version running, resolving directly.");
		dawnstat_resolve_domain_directly();
		return;
	}

	resolver_send_data(FORMATF("resolve system %s.", DAWNSTAT_SUBMIT_DOMAIN));
	resolver_address_found=false;
	resolver_address_failed=false;

	dawnstat_stage=DAWNSTATSTAGE_RESOLVING_DOMAIN_REMOTE;
	dawnstat_resolve_timeout=current_time+100;
}

/**************************************************************************/
// if a connection failed on a given ip address, move onto the next ip
// if there are no remaining ip's abort the entire process
static void dawnstat_connection_attempt_failed()
{
	dawnstat_address_count++;
	resolve_result_address *addr=resolve_result_address_list->get(dawnstat_address_count);
	if(!addr || IS_NULLSTR(addr->address)){
		dawnstat_stage=DAWNSTATSTAGE_ABORTED;
		dawnstat_logf("dawnstat connect aborted.");
	}else{
		dawnstat_stage=DAWNSTATSTAGE_INITIATE_CONNECTION;
	}
}

/**************************************************************************/
// this should only be run once per address in the list of addresses
static void dawnstat_initiate_connection()
{
	//	initiate connection to DAWNSTAT_SUBMIT_DOMAIN
	resolve_result_address *addr=resolve_result_address_list->get(dawnstat_address_count);
	if(!addr || IS_NULLSTR(addr->address)){
		dawnstat_connection_attempt_failed();
		return;
	}

	if(addr->ipv6){
		sprintf(dawnstat_connection_address_and_port, "[%s]:80", addr->address);
	}else{
		sprintf(dawnstat_connection_address_and_port, "%s:80", addr->address);
	}

	dawnstat_logf("initiating connection to %s", dawnstat_connection_address_and_port);

	// free a previous socket if it hasn't been already freed
	if(dawnstat_socket){
		closesocket(dawnstat_socket);
		dawnstat_socket=0;
	}

	// setup the socket address structure for where we want to connect to
#ifdef IPV6_SUPPORT_ENABLED
	struct addrinfo hints;
	memset(&hints, 0, sizeof(struct addrinfo));		
	hints.ai_flags=AI_NUMERICHOST; // we have an ip address
	if(addr->ipv6){
		hints.ai_family   = AF_INET6;
	}else{
		hints.ai_family   = AF_INET;
	}
	hints.ai_socktype = SOCK_STREAM;
	if(res){
		freeaddrinfo(res);
		res=NULL;
	}
	int r = getaddrinfo(addr->address, // ip address in text form
						"80", &hints, &res);
	// check that it was converted successfully
	if (r) {
		#ifdef WIN32
			if(r==WSAHOST_NOT_FOUND){
				dawnstat_logf("getaddrinfo error %d - couldn't convert '%s' to a valid ip address.", 
					r, addr->address);
			}else if(r==WSAEAFNOSUPPORT && addr->ipv6){
				dawnstat_logf("\n"
					"getaddrinfo error %d - couldn't convert '%s' to a valid ip address\n"
					" - getaddrinfo reported this system has no support for the AF_INET6 address family.\n"
					"   This error message is normal if this system doesn't support ipv6, as it\n"
					"   was an ipv6 address getaddrinfo() was asked to convert.", 
					r, addr->address);
			}else{
				dawnstat_logf("dawnstat_initiate_connection(): getaddrinfo(%s) error %d", 
					addr->address, r);
			}
		#else
			{
				dawnstat_logf("dawnstat_initiate_connection(): getaddrinfo(%s) error %d - '%s'", 
					addr->address, r, gai_strerror(r));			
			}
		#endif
		dawnstat_connection_attempt_failed();
		return;
	}
#else // !IPV6_SUPPORT_ENABLED
	if(addr->ipv6){
		dawnstat_logf("skipping connection to %s "
			"- ipv6 address format not supported on this compile", 
			dawnstat_connection_address_and_port);
		dawnstat_connection_attempt_failed();
		return;
	}

	if(res){
		delete res;
		res=NULL;
	}
	res=new ipv4only_addrinfo;
	res->ai_next=NULL;
	res->ai_family=AF_INET;
	res->ai_socktype=SOCK_STREAM;
	res->ai_protocol=IPPROTO_TCP;
	struct sockaddr_in sa;
	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_port = htons(80);
	sa.sin_addr.s_addr= inet_addr( addr->address);
	if(sa.sin_addr.s_addr==INADDR_NONE){ 
		// we had an invalid address supplied
		// technically INADDR_NONE can be returned by the valid ip 255.255.255.255
		// but 255.255.255.255 isn't useful in this context so we assume input was invalid.
		dawnstat_logf("'%s' was not a valid ipv4 address.", addr->address);
		dawnstat_connection_attempt_failed();
		return;
	}
	res->ai_addr=(struct sockaddr *) &sa;
	res->ai_addrlen=sizeof(sa);
#endif
	dawnstat_socket= socket(res->ai_family, res->ai_socktype, res->ai_protocol);

	if (dawnstat_socket== dawn_socket_INVALID_SOCKET){
		dawnstat_logf("Error creating connection socket");
		dawnstat_connection_attempt_failed();
		return;
	}


	{ // set the socket to non-blocking mode
#ifdef WIN32
		unsigned long blockmode = 1; 
		if(ioctlsocket(dawnstat_socket, FIONBIO, &blockmode)== SOCKET_ERROR)    
		{        
			dawnstat_logf("ioctlsocket: error setting new socket to nonblocking, "
				"WSAGetLastError=%d", WSAGetLastError());        
			dawnstat_connection_attempt_failed();
			return;    
		}  
#else
		if ( fcntl(dawnstat_socket, F_SETFL, O_NONBLOCK)< 0 ){
			dawnstat_logf("fcntl: error setting new socket to nonblocking");
			dawnstat_connection_attempt_failed();
			return;    
		}
#endif
	}

	// start the connection	

	int nRet= connect(dawnstat_socket, res->ai_addr, (socklen_t)res->ai_addrlen);

	// check for instant results
	
	if(nRet==0){ // successful connection, jump straight to generating the stats to post
		dawnstat_logf("dawnstat_initiate_connection(): immediate connect, jumping to generate stats.");
		dawnstat_stage=DAWNSTATSTAGE_GENERATE_STATS;
		return;
	}

	// check the error codes, if we have anything other than 
	// what is normal for a blocked connect call, we abort the connection
#ifdef WIN32
	int WSALastError=WSAGetLastError();
	if(WSALastError!=WSAEWOULDBLOCK){
		dawnstat_logf("dawnstat_initiate_connection(): connect() error %s",
			get_winsock_error_text(WSALastError));
		dawnstat_connection_attempt_failed();
		return;
	}
#else
	if(nRet<0){
		if (errno != EINPROGRESS && errno != EALREADY) 
		{
			dawnstat_logf("dawnstat_initiate_connection(): connect() error %d", errno);
			dawnstat_connection_attempt_failed();
			return;
		}
	}
#endif

	// the connection process has started, and is now in progress
	dawnstat_connect_timeout=current_time+120; // 2 minutes to connect
	dawnstat_logf("connection initiation successful");
	dawnstat_stage=DAWNSTATSTAGE_CONNECT_IN_PROGRESS;
	return;
}
/**************************************************************************/
static void dawnstat_process_connect()
{
	// handle the timeout first
	if(dawnstat_connect_timeout<current_time){
		dawnstat_logf("dawnstat_process_connect(): pending connection timed out.");
		dawnstat_connection_attempt_failed();
		return;
	}

	int nRet;
	dawnstat_logf("processing connection %s", dawnstat_connection_address_and_port);
#ifdef WIN32
	{ 
		// use a select call to see if the socket to be ready for writing
		// set our select_timeout, to 0 seconds
		struct timeval select_timeout;
		select_timeout.tv_sec=0;
		select_timeout.tv_usec=0;

		// prepare our file descriptor set
		fd_set fdWrite; // connect success is reported here
		FD_ZERO(&fdWrite);
		FD_SET(dawnstat_socket, &fdWrite);
		fd_set fdExcept; // connect failure is reported here
		FD_ZERO(&fdExcept);
		FD_SET(dawnstat_socket, &fdExcept);

		// check if the socket is ready
		nRet=select((int)dawnstat_socket+1, NULL, &fdWrite, &fdExcept, &select_timeout);

		dawnstat_logf("select() returned %d", nRet);
		if(nRet<1){
			if(nRet==0){
				// it isn't ready yet - wait longer
			}else{
				dawnstat_logf("dawnstat_process_connect(): select() returned error %d", nRet);
				dawnstat_connection_attempt_failed();		
			}
			return;
		}

		if(FD_ISSET( dawnstat_socket, &fdWrite )){
			// socket is ready for writing
			dawnstat_logf("dawnstat_process_connect(): connection established.");
			dawnstat_stage=DAWNSTATSTAGE_GENERATE_STATS;
			return;
		}

		if(FD_ISSET( dawnstat_socket, &fdExcept )){
			// connection failed
			dawnstat_logf("dawnstat_process_connect(): connection failed.");
		}else{		
			dawnstat_logf("dawnstat_process_connect(): don't know how we got here!");
		}
		dawnstat_connection_attempt_failed();
		return; 
	}
#else
	// just attempt to connect the socket again using connect(), 
	// if the socket is now connected, connect() will return 0 or error 56 (already connected)
	nRet= connect(dawnstat_socket, res->ai_addr, res->ai_addrlen);
	if(nRet==0){
		// successful connection, generate the stats to post
		dawnstat_stage=DAWNSTATSTAGE_GENERATE_STATS;
		return;
	}
	if(nRet<0){
		if(errno==EISCONN){
			// we consider the connection successful, generate the stats to post
			dawnstat_logf("dawnstat_process_connect(): socket connected.");
			dawnstat_stage=DAWNSTATSTAGE_GENERATE_STATS;
			return;
		}

		if (errno != EINPROGRESS && errno != EALREADY){
			dawnstat_logf("dawnstat_process_connect(): connect() error %d", errno);
			dawnstat_connection_attempt_failed();
			return;
		}
	}
#endif
	return;
}
/**************************************************************************/
static void dawnstat_generate_stats()
{
	dawnstat_stattext_to_post=dawnstat_generate_statistics_text();
	dawnstat_stage=DAWNSTATSTAGE_POSTING;
}
/**************************************************************************/
static void dawnstat_post()
{
	char *msg=dawnstat_stattext_to_post;

	int written;
	int msglen=str_len(msg);

	written=write_to_socket(dawnstat_socket, msg, msglen);
	
	if(written<0){ // check for an error
		dawnstat_logf("dawnstat_post(): An error occured posting statistics.");
		dawnstat_connection_attempt_failed();		
		return;
	}

	// check for an incomplete write
	if(written<msglen){
		dawnstat_logf("Incomplete write, sent %d bytes of %d, write rest later", written, msglen);
		dawnstat_stattext_to_post+=written;
		return;
	}

	// completed write
	dawnstat_logf("Submitted %d bytes", written);
	
	memset(dawnstat_post_response, 0, sizeof(dawnstat_post_response));	
	dawnstat_post_response_position=dawnstat_post_response;
	dawnstat_stage=DAWNSTATSTAGE_CHECK_POST_ACCEPTANCE;	
}

/**************************************************************************/
static void dawnstat_check_post_acceptance()
{
	int len=sizeof(dawnstat_post_response)-str_len(dawnstat_post_response)-10;
	int read=recv(dawnstat_socket, dawnstat_post_response_position, len, 0);
	
	if(read<0){ // check for an error
		dawnstat_logf("dawnstat_check_post_acceptance(): An error occured checking for "
			"the post acceptance, posting to next ip or failing.");
		dawnstat_connection_attempt_failed();
		return;
	}

	// the response will be formatted in the form:
	// "HTTP/1.1" <space> <status code> <space> <text description of status code>\n"
	// where <status code> is a 3 http status digit code, 200 is considered successful
	// we only need to read 2 space characters, in order to get the status code
	if(count_char(dawnstat_post_response, ' ')<2){
		dawnstat_logf("Incomplete read, will continue read next time around.");
		dawnstat_logf("Read response so far: '%s'", dawnstat_post_response);
		dawnstat_post_response_position=&dawnstat_post_response[str_len(dawnstat_post_response)];
		return;
	}

	// completed read, get the status code
	char *start=strstr(dawnstat_post_response," ");
	if(!start){
		// shouldn't be possible to get here unless someone has introduced bugs into count_char()
		dawnstat_logf("dawnstat_check_post_acceptance(): can't find spaces to get start of status code!");
		dawnstat_connection_attempt_failed();
		return;
	}
	start++;
	char *end=strstr(start," ");
	if(!end){
		// shouldn't be possible to get here unless someone has introduced bugs into count_char()
		dawnstat_logf("dawnstat_check_post_acceptance(): can't find spaces to get end of status code!");
		dawnstat_connection_attempt_failed();
		return;
	}
	*end='\0';
	if(!is_number(start)){
		// confirm the status code we are checking is an actual number
		dawnstat_logf("dawnstat_check_post_acceptance(): status code found isn't a number for some reason!");
		dawnstat_connection_attempt_failed();
		return;
	}
	int status_code=atoi(start);
	*end=' ';
	if(status_code!=200){
		dawnstat_logf("post unsuccessful - http status code %d.", status_code);
		dawnstat_connection_attempt_failed();		
		return;
	}

	dawnstat_logf("post accepted - http status code 200.");
	dawnstat_stage=DAWNSTATSTAGE_CLOSE_SOCKET;	
}

/**************************************************************************/
void dawnstat_update() 
{
	static time_t wait_until=0;
	// this function is called every 10 seconds by default (PULSE_DAWNSTAT)
	// it is necessary to be called this often due to is non blocking io	
	if((int)dawnstat_stage<(int)DAWNSTATSTAGE_COMPLETED){
		if(!wait_until || dawnstat_stage!=DAWNSTATSTAGE_WAIT){
			dawnstat_logf("dawnstat_update(%d)", (int)dawnstat_stage);
		}
	}

	switch(dawnstat_stage){
		case DAWNSTATSTAGE_WAIT:
			{				
				if(wait_until==0){
					wait_until=current_time+DAWNSTAT_DELAY;
				}else if(wait_until<current_time){
					// time to move on
					wait_until=0;
					dawnstat_logf("moving on to resolving stage.");					
					dawnstat_resolve_domain();					
				}
			}
			break;

		case DAWNSTATSTAGE_RESOLVING_DOMAIN_REMOTE:
			dawnstat_logf("checking if domain has been resolved");

			if(resolver_address_found){
				dawnstat_stage=DAWNSTATSTAGE_DOMAIN_RESOLVED;
				for(resolve_result_address *node=resolve_result_address_list; node; node=node->next){
					dawnstat_logf("dawnstat address resolved as ipv%d '%s'", node->ipv6?6:4, node->address);
				}
			}
			if(dawnstat_resolve_timeout<current_time){
				dawnstat_logf("domain resolution timed out for resolver, trying local resolution.");
				dawnstat_resolve_domain_directly();
				return;
			}
			if(resolver_address_failed){
				dawnstat_logf("domain resolution failed via resolver, trying local resolution.");
				dawnstat_resolve_domain_directly();
				return;
			}
			break;

		case DAWNSTATSTAGE_RESOLVING_DOMAIN_LOCAL:
			dawnstat_logf("checking if domain has been resolved locally");
			if(resolver_address_found){
				dawnstat_stage=DAWNSTATSTAGE_DOMAIN_RESOLVED;
				for(resolve_result_address *node=resolve_result_address_list; node; node=node->next){
					dawnstat_logf("dawnstat address resolved as ipv%d '%s'", node->ipv6?6:4, node->address);
				}
				return;
			}
			if(dawnstat_resolve_timeout<current_time){
				dawnstat_logf("domain resolution timed out.");
				dawnstat_stage=DAWNSTATSTAGE_ABORTED;
				return;
			}
			
			if(resolver_address_failed){
				dawnstat_logf("domain resolution failed.");
				dawnstat_stage=DAWNSTATSTAGE_ABORTED;
				return;
			}
			break;
			
		case DAWNSTATSTAGE_DOMAIN_RESOLVED:
			dawnstat_address_count=0;
			dawnstat_stage=DAWNSTATSTAGE_INITIATE_CONNECTION;
			break;

		case DAWNSTATSTAGE_INITIATE_CONNECTION:
			dawnstat_initiate_connection();
			break;

		case DAWNSTATSTAGE_CONNECT_IN_PROGRESS:
			dawnstat_logf("processing connection.");
			dawnstat_process_connect();
			break;
			
		case DAWNSTATSTAGE_GENERATE_STATS:
			dawnstat_logf("generating statistics.");
			dawnstat_generate_stats();
			break;

		case DAWNSTATSTAGE_POSTING:
			dawnstat_logf("posting statistics.");
			dawnstat_post();
			break;

		case DAWNSTATSTAGE_CHECK_POST_ACCEPTANCE:
			dawnstat_logf("checking for post acceptance.");
			dawnstat_check_post_acceptance();
			break;

		case DAWNSTATSTAGE_CLOSE_SOCKET:
			dawnstat_logf("closing socket.");
			if(dawnstat_socket){
				closesocket(dawnstat_socket);
				dawnstat_socket=0;
			}			
			dawnstat_stage=DAWNSTATSTAGE_COMPLETED;
			break;

		case DAWNSTATSTAGE_COMPLETED:
		case DAWNSTATSTAGE_ABORTED:
			// redo dawnstats once per day if successful
			// or try again in 7 hours time if failed last time
			{
				static time_t redo_in=0;
				if(redo_in){
					if(redo_in<current_time){
						dawnstat_logf("restarting dawnstats");
						
						dawnstat_stage=DAWNSTATSTAGE_WAIT;
						redo_in=0;
					}
				}else{
					if(dawnstat_stage==DAWNSTATSTAGE_ABORTED){
						redo_in=current_time + (7*60*60); // every 7 hours
					}else{
						redo_in=current_time + (24*60*60); // every 24 hours
					}
				}

			}
		default:
			break;
	};


}

/**************************************************************************/
/**************************************************************************/