dawn/notes/
dawn/src/
dawn/src/docs/
/**************************************************************************/
// resolve.cpp - handles IPC (inter process communication) between mud and 
//               dns/ident resolving process src/extras/resolver.cpp
// (c) Michael Garratt, September 2000
/***************************************************************************
 * 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.                                                              *
 **************************************************************************/
#ifdef WIN32
	#define WIN32_LEAN_AND_MEAN	// Speed up the compiling process
	#include <windows.h> 
	#include <direct.h>
#endif
#include "network.h"
#include "comm.h"
#include "resolve.h"

#define RESOLVER_BASE_FILENAME "resolver"

/**************************************************************************/
// prototypes and variables
#ifdef WIN32
	int resolver_poll_and_process_WIN32();
	void resolver_init_WIN32(char * mainpath);
#else
	dawn_socket sockets[2];
#endif

#ifdef IPV6_SUPPORT_ENABLED	
	#ifdef WIN32
		const char *get_winsock_error_text(int errorcode);
		#define redirect_socket_error_text(r) get_winsock_error_text(r) 
	#else
		#define redirect_socket_error_text(r) gai_strerror(r)
	#endif
#endif
/**************************************************************************/
#define RESOLVERLOCAL_VERBOSE_LEVEL 0
resolve_result_address *resolve_result_address_list=NULL;
bool resolver_address_found=false;
bool resolver_address_failed=false;
/**************************************************************************/
extern int abort_threshold; // stored in update.cpp
void update_alarm();
/**************************************************************************/
// resolve_result_address class implementation
resolve_result_address::resolve_result_address()
{
	address=str_dup("");
	ipv6=false;
	next=NULL;
}
resolve_result_address::~resolve_result_address()
{
	if(next){
		delete next;
	}
	replace_string(address, "");
}

void resolve_result_address::add(char *add_address, bool add_ipv6)
{
	if(IS_NULLSTR(address)){
		replace_string(address, add_address);
		ipv6=add_ipv6;
		return;
	}

	if(next){
		next->add(add_address, add_ipv6);
		return;
	}
	resolve_result_address *node;
	node=new resolve_result_address();
	replace_string(node->address, add_address);
	node->ipv6=add_ipv6;
	next=node;

}

resolve_result_address *resolve_result_address::get(int count)
{
	if(!this){
		return NULL;
	}
	if(count==0){
		return this;
	}	
	count--;
	if(next){
		return next->get(count);
	}
	return NULL;
}

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

void resolver_send_data( const char * buf);
/**************************************************************************/
#ifdef unix 
void resolver_init_unix( char *mainpath )
{
    char *p;
    int child_pid;
	int grandchild_pid;
    int sockets[2];
	int stderr_sockets[2];
    char resolverpath[MSL];

	// dont init over an existing resolver
	if (resolver_running){
		logf("resolver_init(): resolver already running");
		return;
	}

	// get the path component from the startup path if necessary
	strcpy(resolverpath, mainpath);
    p = strrchr( resolverpath, '/' );
    if ( p ) {
		*(p+1)='\0';
		strcat(resolverpath,RESOLVER_BASE_FILENAME);
    }else{
        strcpy(resolverpath, "./"RESOLVER_BASE_FILENAME);
    }
#ifdef __CYGWIN__
	strcat(resolverpath,".exe");
#endif


	if(!file_exists(resolverpath))
	{
		bugf("%s not found!", resolverpath);
		log_notef("%s is used to convert IP addresses into domain names (seen using "
			 "the sockets command).  Without this support programming running dawn "
			 "will not do any IP to domain name resolution.", resolverpath);
		resolver_running=false;
		return;
	}

	// create two pairs of sockets IPC (first pair for stdin/stdout, second for stderr)
	if(socketpair( AF_UNIX, SOCK_STREAM, 0, sockets)<0){
		bugf("resolver_init_unix(): socketpair( AF_UNIX, SOCK_STREAM, 0, sockets)<0 - error %d (%s)",
			errno, strerror( errno));		
		return;		
	}
	if(socketpair( AF_UNIX, SOCK_STREAM, 0, stderr_sockets)<0){
		bugf("resolver_init_unix(): socketpair( AF_UNIX, SOCK_STREAM, 0, stderr_sockets)<0 - error %d (%s)",
			errno, strerror( errno));		
		return;		
	}


	// fork and connect all the sockets
	int parent_pid=getpid();
    child_pid = fork(); // first fork

	if(child_pid<0){   
		// failure of first fork
		bugf("resolver_init_unix(): initial fork failed! errno=%d (%s)",
			errno, strerror(errno));
		closesocket(sockets[0]);
		closesocket(sockets[1]);
		closesocket(stderr_sockets[0]);
		closesocket(stderr_sockets[1]);
		resolver_running=false;
		return;
	}

	if ( child_pid == 0 )
	{   
		// child process of first fork

		// do a second fork to avoid zombies
		grandchild_pid = fork(); // second fork
		if(grandchild_pid<0){
			// failure of second fork
			bugf("resolver_init_unix(): failed second fork! errno=%d (%s)",
				errno, strerror(errno));
			exit_clean(0, "resolver_init_unix", "second fork failed");			
		}

		if(grandchild_pid>0){
			// parent of second fork (first fork's child)
			exit_clean(0, "resolver_init_unix", "parent process shutting down");			
		}
		
		// grandchild (child of second fork)
		// init the resolver in here
		char portbuf[70];
		sprintf(portbuf,"port=%d,initialpid=%d", mainport, parent_pid);
		closesocket(sockets[1]);
		closesocket(stderr_sockets[1]);
		dup2( sockets[0], 0 );
		dup2( sockets[0], 1 );
		dup2( stderr_sockets[0], 2 );
		for(int i=3; i<1000; i++){ // close all file descriptors 
			close(i);
		}
		execlp( resolverpath, "resolver", portbuf, NULL );
		// Still here?  Then exec failed. 
		bugf("resolver_init_unix(): execlp of '%s,%s' failed! - error %d (%s)",
			resolverpath, "resolver", errno, strerror( errno));
		// exit cleanly - in the sense that the mud is still booting okay
		exit_clean(1, "resolver_init_unix", "execlp call failed");
	}

	// parent
	resolver_stdinout = sockets[1];
	closesocket( sockets[0] );
	resolver_stderr = stderr_sockets[1];		
	closesocket( stderr_sockets[0] );
	signal(SIGPIPE, SIG_IGN);
	if ( fcntl( resolver_stdinout, F_SETFL, FNDELAY) < 0 )
	{
		bugf("resolver_init_unix(): fcntl( resolver_stdinout, F_SETFL, FNDELAY) < 0 - error %d (%s)",
			errno, strerror( errno));
		closesocket( resolver_stdinout );
		closesocket( resolver_stderr);
		resolver_stdinout=-1;
		resolver_running=false;
	}
	if ( fcntl( resolver_stderr, F_SETFL, FNDELAY) < 0 )
	{
		bugf("resolver_init_unix(): fcntl( resolver_stderr, F_SETFL, FNDELAY) < 0 - error %d (%s)",
			errno, strerror( errno));
		closesocket( resolver_stdinout );
		closesocket( resolver_stderr);
		resolver_stdinout=-1;
		resolver_running=false;
	}
	resolver_running=true;

	// wait for the grandchild to close
	#ifdef HAVE_SYS_WAIT_H 
		int status;
		waitpid(child_pid, &status, WNOHANG);
	#endif

	log_string("resolver_init_unix: resolver started");
}
#endif
/**************************************************************************/
// resolver_init pipes the dns and ident resolver at bootup 
void resolver_init( char *mainpath )
{
	log_string("Starting hostname/ident resolver process...");
#ifdef unix
	resolver_init_unix(mainpath);
#endif
#ifdef WIN32
	resolver_init_WIN32(mainpath);
#endif
	resolver_version=0;

	logf("Querying resolvers version.");
	resolver_send_data("version");
}
/**************************************************************************/
// resolver_query - passes thru socket pipe to resolver the request for an
//                 ip address to host name conversion
// called only from init_descriptor()
void resolver_query( connection_data *c )
{
    char buf[MIL];
	
	sprintf (buf, "resolveip %s", c->remote_ip);

	if(resolver_running){
		logf("Sending '%s' query to dns resolver", buf);
		resolver_send_data(buf);
	}else{
		logf("Queuing '%s' query for local dns resolution", buf);
		resolver_send_data(buf);
		resolverlocal_queue_command(buf);
	}

	if(!GAMESETTING(GAMESET_DONT_PERFORM_IDENT_LOOKUPS)){
		// ident is no longer supported by the resolver for the time being
		// - Kal, Jan 04
/*		sprintf (buf, "resolveident %s,%d,%d,%s", 
			c->remote_ip,
			c->local_port,
			c->remote_port,
			c->local_ip);
		logf("Sending '%s' query to dns resolver", buf);
		resolver_send_data(buf);
*/
	}	
}

/**************************************************************************/
// loops thru descriptors filling in hostnames on matching ip addresses
// - called from get_resolver
// could be made static - just not cause of visual studio function searching
void resolver_apply_ip_results(char *remote_ip, char *results)
{
    connection_data *c, *c_next;

	if(*results==','){ // ident update 
		int lport, rport;
		char response_type[512];
		char os[512];
		char username[512];
		bool username_valid=true;

		int count=sscanf(results, ",%d,%d %s : %s : %s",
			&lport, &rport, response_type, os, username);

		if(count!=5){
			logf("Failed to parse username from ident response '%s', .",
				results);
			username_valid=false;
		}else if(strcmp(response_type,"USERID")){
			logf("Ident response not of type userid.");
			username_valid=false;			
		}

		for ( c = connection_list; c; c = c_next )
		{
			c_next = c->next;
			if ( !str_cmp(c->remote_ip, remote_ip)
				&& c->local_port==lport
				&& c->remote_port==rport) 
			{
				// matching ip address
				replace_string(c->ident_raw_result, results+1);
				if (CH(c)){
					logf("%s ident resolves as %s (%s)", 
						c->remote_tcp_pair, results, CH(c)->name);
				}else{
					logf("%s ident resolves as %s", 
						c->remote_tcp_pair, results);
				}
				if(username_valid){
					replace_string(c->ident_username, username);
				}

				if(!c->resolved && CH(c)){
					char buf[MSL];
					
					if(GAMESETTING(GAMESET_LOG_ALL_IP_CONNECTS)){
						sprintf(buf, "%13s - %15s -> %s", 
							CH(c)->name, c->remote_ip, c->remote_hostname);
						append_datetimestring_to_file( CONNECTS_FILE, buf);		
					}
				}
				c->resolved=true;

				if(c->connected_state==CON_PLAYING && CH(c)){
					laston_update_char(CH(c));
				}
				// check bans on updated connection
				check_connection_ban(c);
			}
		}
	}else{ // dns update
		for ( c = connection_list; c; c = c_next )
		{
			c_next = c->next;
			if ( !str_cmp(c->remote_ip, remote_ip))
			{
				// matching ip address
				replace_string(c->remote_hostname, trim_string(results));
				if (CH(c)){
					logf("%s resolves as '%s' (%s)", 
						c->remote_ip, c->remote_hostname, CH(c)->name);
				}else{
					logf("%s resolves as '%s'", 
						c->remote_ip, c->remote_hostname);
				}

				if(!c->resolved && CH(c)){
					char buf[MSL];
					
					sprintf(buf, "%13s - %15s -> %s", 
						CH(c)->name, c->remote_ip, c->remote_hostname);
					append_datetimestring_to_file( CONNECTS_FILE, buf);		
				}
				c->resolved=true;
				if(c->connected_state==CON_PLAYING && CH(c)){
					laston_update_char(CH(c));
				}
				// check bans on updated connection
				check_connection_ban(c);
			}
		}
	}
}

/**************************************************************************/
int count_active_players(void);
extern char max_count_ip_buf[MIL];
extern int max_count_ip;
/**************************************************************************/
// resolve_stats generates a dns request within the dawn of time domain
// this request isn't actually answered but provides a method to roughly
// measure of how many muds are running DOT.
void resolve_stats()
{
	char *p;
	static bool resolved_stats=false;
	static time_t resolved_at=0;
	if(resolved_stats){
		if( (resolved_at + 60*60*24*2) < current_time ){
			resolved_stats=false;
		}
		return;
	}

	// we only 'resolve stats' after the mud has been up at least 30 minutes
	if(lastreboot_time + (30*60) > current_time){
		return;
	}
	max_count_ip_calc();
	if(max_count_ip<1){ 
		return;
	}

	char address[MSL];
	char address2[MSL];
	int len;
	len=3-str_len(max_count_ip_buf)%3;
	if(len>2){
		len=0;
	}
	len+=str_len(max_count_ip_buf);
	sprintf(address,"%s     ", max_count_ip_buf);	
	address[len]='\0';	
	strcpy(address2,encodeBase64(address, len));
	len=str_len(address2);
	if(len>120){
		sprintf(address,"a%d.b%.60s.c%.60s.d%.60s", 
			len, address2, &address2[60], &address2[120]);	
	}else if(len>60){
		sprintf(address,"a%d.b%.60s.c%.60s", 
			len, address2, &address2[60]);	
	}else{
		sprintf(address,"a%d.b%s", len, address2);
	}
	for(p=address; *p; p++){
		if(!is_alnum(*p) && *p!='-'){
			if(*p=='/'){
				*p='.';
			}else{ // should only ever be a +
				*p='-';
			}
		}
	}	

	char stats_request[MSL];

	// generate the game world stats
	char world_stats[MSL];	
	sprintf(world_stats, "W%u-%u-%u-%u-%u-%u-%u-%u-%u", 
		(unsigned int)top_area, (unsigned int)top_room, (unsigned int)top_shop, 
		(unsigned int)top_mob_index, (unsigned int)mobile_count, 
		(unsigned int)mobprog_count, (unsigned int)top_obj_index, 
		(unsigned int)top_help, (unsigned int)social_count);


	// generate the general stats
	char game_stats[MSL];	
	sprintf(game_stats, "G%d-%d-%d-%d-%d", 
		max_count_ip,
		count_active_players(),
		max_on,	
		LEVEL_IMMORTAL,	
		MAX_LEVEL);

	// generate the version stats
	char version[MSL];
	sprintf(version, "V%s-%s", DAWN_RELEASE_VERSION, DAWN_RELEASE_DATE);
	for(p=version; *p; p++){
		if(!is_alnum(*p) && *p!='-'){
			*p='-';
		}
	}
#ifdef WIN32
	strcat(version, "w");
#else
#	ifdef unix
		strcat(version, "u");
#	else
		strcat(version, "x");
#	endif
#endif

	// generate the gamename 
	char game_name[MSL];
	sprintf(game_name, "N%s", MUD_NAME);
	game_name[60]='\0';
	for(p=game_name; *p; p++){
		if(!is_alnum(*p) && *p!='-'){
			*p='-';
		}
	}	
	
	sprintf(stats_request, 
		"%s.%s.%s.%s.%s.sta"
		"tsv3.da"
		"wnoft"
		"ime.o"
		"rg.", 
		address, version, game_name, game_stats, world_stats);	

	if(!resolver_running || resolver_version<1500){
		resolverlocal_queue_command(FORMATF("res"
			"olve * direct.%s", stats_request));
	}else{
		resolver_send_data( FORMATF("res"
			"olve * %s", stats_request));
	}

	resolved_at=current_time;
	resolved_stats=true;
}

/**************************************************************************/
void resolver_process_resolveip_response(char *data)
{
	char original_data[MIL];
	char parameter[MIL];
	char *equals;
	char label[MIL];
	char value[MIL];

	char ip[MIL];
	char lookup[MIL];
	char reverse[MIL];
	// responses are in the format:
	// "ip=127.0.0.1 lookup=localhost reverse=127.0.0.1"

	strcpy(original_data, data);
	// find the ip= bit
	while(!IS_NULLSTR(data)){
		// pick a parameter off
		data=one_argument(data, parameter);
		
		// pull out the equals
		equals=strstr(parameter, "=");

		if(!equals){
			bugf("resolver_process_resolveip_response: missing '=' "
				"in the '%s' parameters of result \"%s\"",
				parameter, original_data);
			return;
		}

		// get the label and value
		*equals='\0';
		strcpy(label, parameter);
		strcpy(value, equals+1);

		if(!str_cmp(label, "ip")){
			strcpy(ip, value);
		}else if(!str_cmp(label, "lookup")){
			strcpy(lookup, value);
		}else if(!str_cmp(label, "reverse")){
			strcpy(reverse, value);
		}else{
			logf("resolver_process_resolveip_response: unrecognised parameter label in '%s=%s'",
				label, value);
		}
	}

	// check that we managed to parse some input
	if(IS_NULLSTR(ip)){
		bugf("resolver_process_resolveip_response: missing 'ip=...' "
			"entry in \"%s\"", original_data);
		return;
	}
	if(IS_NULLSTR(lookup)){
		bugf("resolver_process_resolveip_response: missing 'lookup=...' "
			"entry in \"%s\"", original_data);
		return;
	}

	// check if the reverse is different from the ip...
	// if it is the resolved result should show both ip's to prevent
	// spoofing
	if(str_cmp(reverse, ip)){
		strcpy(lookup, FORMATF("%s->%s", lookup, reverse));
	}

	// go thru and apply the results
	for ( connection_data *c = connection_list; c; c = c_next )
	{
		c_next = c->next;
		if ( !str_cmp(c->remote_ip, ip))
		{
			// matching ip address
			replace_string(c->remote_hostname, lookup);
			if (CH(c)){
				logf("%s resolves as '%s' (%s)", 
					c->remote_ip, c->remote_hostname, CH(c)->name);
			}else{
				logf("%s resolves as '%s'", 
					c->remote_ip, c->remote_hostname);
			}

			if(!c->resolved && CH(c)){
				char buf[MSL];
				
				sprintf(buf, "%13s - %15s -> %s", 
					CH(c)->name, c->remote_ip, c->remote_hostname);
				append_datetimestring_to_file( CONNECTS_FILE, buf);		
			}
			c->resolved=true;
			if(c->connected_state==CON_PLAYING && CH(c)){
				laston_update_char(CH(c));
			}
			// check bans on updated connection
			check_connection_ban(c);
		}
	}
}
/**************************************************************************/
void resolver_process_resolve_response(char *data)
{
	// resolve_result response
	char request_name[200];
	data=one_argument(data, request_name);

	// check if it is a system request
	if(!str_cmp(request_name, "system")){
		char *p, *address_end;

		if(resolve_result_address_list){
			delete resolve_result_address_list;
		}
		resolve_result_address_list=new resolve_result_address;
		resolver_address_found=false;
		resolver_address_failed=false;

		p=strstr(data, "ipv6-'");
		while(p){
			p+=6;
			address_end=strstr(p, "'");
			if(!address_end){
				break;
			}
			*address_end='\0';
			resolve_result_address_list->add(p, true);
			*address_end='\'';			
			p=strstr(address_end, "ipv6-'");
		}

		p=strstr(data, "ipv4-'");
		while(p){
			p+=6;
			address_end=strstr(p, "'");
			if(!address_end){
				break;
			}
			*address_end='\0';
			resolve_result_address_list->add(p, false);
			*address_end='\'';			
			p=strstr(address_end, "ipv4-'");
		}

		if(IS_NULLSTR(resolve_result_address_list->address)){			
			resolver_address_failed=true;
		}else{
			resolver_address_found=true;			
		}

		//for(resolve_result_address *node=resolve_result_address_list; node; node=node->next){
		//	logf("resolve_result_address '%s' %d", node->address, node->version);
		//}
		return;
	}

	// otherwise it was a resolve request performed by a user
	logf("Applying resolver results '%s %s'", request_name, data);
	// search for requesting name
	for(char_data *ch=player_list; ch; ch=ch->next_player){
		if(!str_cmp(ch->name, request_name)){
			ACTIVE_CH(ch)->println("");
			ACTIVE_CH(ch)->titlebar("RESOLVER RESULTS");
			ACTIVE_CH(ch)->print("`x");
			ACTIVE_CH(ch)->println(data);
			ACTIVE_CH(ch)->titlebar("");
			return;
		}
	}
	return;
}
/**************************************************************************/
void resolver_process_version_response(char *data)
{
	// version response
	int a;
	int b;				
	sscanf(data, "%d.%d", &a, &b);
	if(b<10){
		b*=100;
	}else if(b<100){
		b*=10;
	}
	b%=1000;
	resolver_version=(a*1000)+b;
	logf("Resolver version = %d.%03d", resolver_version/1000, resolver_version%1000);
	return;
}
/**************************************************************************/
void resolver_process_response(char *data)
{
	char entire_line[MSL];
	char *process; // what do perform the processing on
	char command_response[MIL];

	if(IS_NULLSTR(data)){
		return;
	}

	if(str_len(data)>MSL-5){
		bugf("resolver_process_response(), input data is too long (%d characters) - this is not normal.", 
			str_len(data));
		bugf("First 40 characters of input: %.40s", data);
		return;
	}
	process=trim_string(data);

	// get rid of any potentially dangerous characters in the dns response
	for(char *ps=process; *ps; ps++){
		unsigned char ups=(unsigned char)*ps;
		if(ups==MXP_BEGIN_TAG){
			*ps='?';
		}else if(ups=='~'){
			*ps='?';
		}else if(ups=='~'){
			*ps='?';
		}else if(ups<0x1F || ups==0x7F || ups==0xFF){
			*ps='?';
		}
	}

	// strip off the resolver debugging surround if it is use
	if(!str_prefix("STDOUT[[[",process)){
		strcpy(entire_line, process+str_len("STDOUT[[["));		
		if(!str_cmp(&entire_line[str_len(entire_line)-3], "]]]")){
			entire_line[str_len(entire_line)-3]='\0';
		}
		process=trim_string(entire_line);
	}else{
		strcpy(entire_line, process);
	}
	
	// find out what it is a response too
	process=one_argument(process, command_response);

	if(!str_cmp("resolveip_response", command_response)){
		resolver_process_resolveip_response(process);
	}else if(!str_cmp("resolve_response", command_response)){
		resolver_process_resolve_response(process);
	}else if(!str_cmp("version_response", command_response)){
		resolver_process_version_response(process);
	}else{
		bugf("resolver_process_response(): unexpected response '%s' from resolver.", entire_line);
	}

}

/**************************************************************************/
// called from resolver_poll_and_process() when resolver_stdin pipe has 
// returned results 
void resolver_get_response( void )
{
    char buf[MSL + 1];
    int size;
    char *p, *q;

    size = recv( resolver_stdinout, buf, MSL, 0);
    if ( size < 0 ){
        closesocket( resolver_stdinout);
		closesocket( resolver_stderr);
		resolver_stdinout=-1;
		resolver_running=false;
    }

    buf[size] = '\0';
    q = buf;

    while ( (p = strchr( q, '\n' )) )
    {
        *p = '\0';
		if(*(p-1)=='\r'){ // get rid of \r codes if they are there
			*(p-1)='\0';
		}

		resolver_process_response(q);
		q = p + 1; // skip over the \n
    }
}
/**************************************************************************/
// called from resolver_poll_and_process() when resolver_stdin pipe has 
// returned results 
void resolver_get_stderr_response( void )
{
    char buf[MSL + 1];
    int size;
    char *p, *q;

    size = recv( resolver_stderr, buf, MSL, 0);
    if ( size < 0 ){
        closesocket( resolver_stdinout);
		closesocket( resolver_stderr);
		resolver_stdinout=-1;
		resolver_running=false;
    }

    buf[size] = '\0';
    q = buf;

	log_string("---resolver output:");
    while ( (p = strchr( q, '\n' )) )
    {
        *p = '\0';
		if(*(p-1)=='\r'){ // get rid of \r codes if they are there
			*(p-1)='\0';
		}

		logf("-%-70s-",q);
		q = p + 1; // skip over the \n
    }
}

/**************************************************************************/
// send data to the resolver
void resolver_send_data( const char * buf)
{
	char data[MSL];
	sprintf(data,"%s\n", buf);

#ifdef WIN32
	if(resolver_running){
		extern HANDLE hResolversStdIn;
		unsigned long dwWritten;
		if (! WriteFile(hResolversStdIn, data, str_len(data), &dwWritten, NULL)) {
		   assert(false && "write failed");
		}
//		logf("wrote %d bytes to resolver.", dwWritten);
	}
	return;
#endif
	if(!resolver_running){ // we dont have a resolver
		return;
	}
	
    if ( send( resolver_stdinout, data, str_len( data), 0) < 0 )
    {
		logf("Error sending to resolver (fd=%d), closing it.", resolver_stdinout);
		// problems writing to resolver socket pipe
        closesocket( resolver_stdinout);
		closesocket( resolver_stderr);
		resolver_stdinout=-1;
		resolver_version=0;
		resolver_running=false;
		logf("resolver_send_data: resolver closed! (fd=%d)", resolver_stdinout);
    }
}

/**************************************************************************/
void do_rebootresolver(char_data *ch, char *)
{
	if (IS_NPC(ch))
	{
		do_huh(ch,"");
        return;
	}

	ch->println("Rebooting resolver...");
	log_string("Rebooting resolver");
    if ( send( resolver_stdinout, "close\n", str_len( "close")+1,0) < 0 ){
		// problems writing to resolver socket pipe
        closesocket( resolver_stdinout);
		closesocket( resolver_stderr);
        resolver_running=false;
		log_string("resolver_query: resolver closed!");
    }
    resolver_running=false;
	resolver_init(EXE_FILE);
    return;
}
/**************************************************************************/
void do_resolve(char_data *ch, char *argument)
{
	if (IS_NPC(ch)){
		ch->println("Players only");
        return;
	}

	argument=trim_string(argument);
	if(IS_NULLSTR(argument)){
		ch->println("syntax: resolve <domain name to lookup>");
		ch->println("syntax: resolve <ip address to lookup>");
		return;
	}

	if(!resolver_running){

		if(IS_ADMIN(ch)){
			ch->wrapln("`RWARNING:`x The separate dns resolving process, isn't currently running.  "
				"Because of this, the only option the mud has to resolve domain names "
				"is to look them up itself.  This has the potential to lag the mud, so"
				"lookups at this stage are restricted to admin only.  Please look at "
				"getting your dns resolver working!");
		}else{
			ch->wrapln("The separate dns resolving process, isn't currently running.  "
				"Because of this, the only option the mud has to resolve domain names "
				"is to look them up itself.  This has the potential to lag the mud, so"
				"this command is only available to the admin at this point in time.");
			return;
		}
	}

	if(resolver_running && resolver_version<1500){
		ch->wrapln("The version of the dns resolver is too old for this "
			"version on dawn, obtain an update.");
		return;
	}

	ch->printlnf("Sending '%s' to the dns resolver", argument);
	ch->println("The results will be sent to your display when they are received.");

	char *command=FORMATF("resolve %s %s", TRUE_CH(ch)->name, argument);

	if(resolver_running){
		resolver_send_data(command);
	}else{
		resolverlocal_queue_command(command);
	}

    return;
}

/**************************************************************************/
// check if the resolver connection has input
void resolver_poll_and_process()
{
#ifdef WIN32
	resolver_poll_and_process_WIN32();
	return;
#endif

	static int errcount=0;
	static struct timeval null_time;
	static fd_set resolver_in;
	static fd_set resolver_out;
	static fd_set resolver_exception;

	if(!resolver_running){ // we dont have a resolver
		return;
	}
	
	FD_ZERO( &resolver_in);
	FD_ZERO( &resolver_out);
	FD_ZERO( &resolver_exception);

	FD_SET( (dawn_socket)resolver_stdinout, &resolver_in);
	FD_SET( (dawn_socket)resolver_stdinout, &resolver_out);
	FD_SET( (dawn_socket)resolver_stdinout, &resolver_exception);

	FD_SET( (dawn_socket)resolver_stderr, &resolver_in);
	FD_SET( (dawn_socket)resolver_stderr, &resolver_out);
	FD_SET( (dawn_socket)resolver_stderr, &resolver_exception);

	int maxdesc=UMAX(resolver_stdinout, resolver_stderr)+1;

	if( select( maxdesc, &resolver_in, &resolver_out, &resolver_exception, &null_time ) < 0 ){
		socket_error( FORMATF("resolver_poll_and_process: select()."));
		do_abort();
		exit_error( 1 , "resolver_poll_and_process", "select error");
	}


	if( FD_ISSET( resolver_stdinout, &resolver_exception) ){
		errcount++;
		logf("Exception raised when reading from resolver.");
		if(errcount>50){
			logf("Too many errors encounted, closing resolver socket.");
			resolver_running=false;
		}
		return;
	}

	if( FD_ISSET( resolver_stderr, &resolver_exception) ){
		errcount++;
		logf("Exception raised when reading from resolver stderr.");
		if(errcount>50){
			logf("Too many errors encounted, closing resolver socket.");
			resolver_running=false;
		}
		return;
	}


	if( FD_ISSET( resolver_stdinout, &resolver_in) ){
		resolver_get_response();
	}

	if( FD_ISSET( resolver_stderr, &resolver_in) ){
		resolver_get_stderr_response();
	}

}
/**************************************************************************/
#ifdef WIN32
/**************************************************************************/
// handles to various file descriptors of the resolver
HANDLE hResolversStdIn;
HANDLE hResolversStdOut;
HANDLE hResolversStdErr;
/**************************************************************************/
void resolver_init_WIN32(char * mainpath)
{
	char *resolver_filename="resolver.exe";
	if(!file_exists(resolver_filename)){
		char cwd[MSL];
		getcwd(cwd, MSL);
		bugf("%s"DIR_SYM"%s not found!", cwd, resolver_filename);
		if(GAMESETTING(GAMESET_PERFORM_LOCAL_DNS_LOOKUPS)){
			log_notef("%s is used to convert IP addresses into domain names (seen using "
					 "the sockets command).  "
					 "Currently the gamesetting flag 'perform_local_dns_lookups' is "
					 "turned on so the translation of IP address to domain names will be "
					 "performed by the mud itself.  The only down side of doing this is "
					 "when the mud looks up the domain name of an ip address, it waits "
					 "for the answer.  If the IP doesn't have a domain name, the mud "
					 "will stall for all players until the lookup it times out after "
					 "about 30 seconds.", resolver_filename);
		}else{
			log_notef("%s is used to convert IP addresses into domain names (seen using "
					 "the sockets command).  "
					 "Currently the gamesetting flag 'perform_local_dns_lookups' is "
					 "turned off, so there is no IP to domain name conversion.  "
					 "You can turn this on within gameedit so the translation of IP address "
					 "to domain names will be performed by the mud itself.  The only down "
					 "side of turning on this option is that when the mud looks up the "
					 "domain name of an ip address, it waits for the answer.  If the IP "
					 "doesn't have a domain name, the mud will stall for all players until "
					 "the lookup it times out after about 30 seconds.", resolver_filename);
		}
		return;
	}


	// The resolver system is based on using pipes for IPC (Inter Process Communication).

	// The resolver.exe application, has been written to read commands from its stdin,
	// and send responses to commands on stdout while showing diagnostic information
	// on stderr.  In order to capture the resolver output and send it commands to 
	// its stdin, the mud launches it as a child process which inherits the stdin, 
	// stdout and stderr descriptors.

	// we will use CreatePipe to create pipes we will use for IPC,
	// since they need to be inheritable, we need to set the security 
	// attributes accordingly.	

	SECURITY_ATTRIBUTES saAttr; 
	memset(&saAttr, 0, sizeof(SECURITY_ATTRIBUTES));
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
	saAttr.bInheritHandle = true; 
	saAttr.lpSecurityDescriptor = NULL; 

	// resolver handles - what are sent to it using CreateProcess
	HANDLE hChildResolverStdIn;
	HANDLE hChildResolverStdOut;
	HANDLE hChildResolverStdErr;

	// temp
	HANDLE hTempHandle;  // used to allow us to get a non inheritable handle

	// StdIn pipe
	if (! CreatePipe(&hChildResolverStdIn, &hTempHandle, &saAttr, 0)) {
		assert(!"Stdin pipe creation failed\n"); 
	}
	// get a non inheritable version of hTempHandle into hResolversStdIn
    if(!DuplicateHandle(GetCurrentProcess(), hTempHandle, GetCurrentProcess(), 
		&hResolversStdIn, 0,false,DUPLICATE_SAME_ACCESS))
	{
		assert(!"DuplicateHandle failed\n"); 
	}
    CloseHandle(hTempHandle);
	
	// StdOut pipe
	if (! CreatePipe(&hTempHandle, &hChildResolverStdOut, &saAttr, 0)) {
		assert(!"Stdout pipe creation failed\n"); 
	}
	// get a non inheritable version of hTempHandle into hResolversStdIn
    if(!DuplicateHandle(GetCurrentProcess(), hTempHandle, GetCurrentProcess(), 
		&hResolversStdOut, 0,false,DUPLICATE_SAME_ACCESS))
	{
		assert(!"DuplicateHandle failed\n"); 
	}
    CloseHandle(hTempHandle);
	
	// StdErr pipe
	if (! CreatePipe(&hTempHandle, &hChildResolverStdErr, &saAttr, 0)) {
		assert(!"Stdout pipe creation failed\n"); 
	}
	// get a non inheritable version of hTempHandle into hResolversStdIn
    if(!DuplicateHandle(GetCurrentProcess(), hTempHandle, GetCurrentProcess(), 
		&hResolversStdErr, 0,false,DUPLICATE_SAME_ACCESS))
	{
		assert(!"DuplicateHandle failed\n"); 
	}
    CloseHandle(hTempHandle);


	PROCESS_INFORMATION piProcInfo; 
	STARTUPINFO siStartInfo; 	
	memset( &piProcInfo, 0, sizeof(PROCESS_INFORMATION) );
	memset( &siStartInfo, 0, sizeof(STARTUPINFO) );
	siStartInfo.cb = sizeof(STARTUPINFO); 
	siStartInfo.dwFlags = STARTF_USESTDHANDLES;
	siStartInfo.hStdOutput = hChildResolverStdIn;//hChildResolverStdOut;
    siStartInfo.hStdInput  = hChildResolverStdOut; //hChildResolverStdIn;
	siStartInfo.hStdOutput = hChildResolverStdOut;
    siStartInfo.hStdInput  = hChildResolverStdIn;
    siStartInfo.hStdError  = hChildResolverStdErr;

	int r=CreateProcess(NULL, 
		resolver_filename,	// command line 
		NULL,			// process security attributes 
		NULL,			// primary thread security attributes 
		true,			// handles are inherited 
		0,				// creation flags 
		NULL,			// use parent's environment 
		NULL,			// use parent's current directory 
		&siStartInfo,	// STARTUPINFO pointer 
		&piProcInfo);	// receives PROCESS_INFORMATION 

	if(r==0){
		logf("resolver_init_WIN32: CreateProcess failed to launch '%s', there will be no DNS resolution of ip addresses!", resolver_filename);
		resolver_running=false;
		return;
	}
	logf("resolver_init_WIN32: %s launched successfully - result=%d.", resolver_filename, r);
	resolver_running=true;
}

/**************************************************************************/
int resolver_poll_and_process_WIN32()
{
	if(!resolver_running){
		return 0;
	}

	DWORD dwRead; 
	char chBuf[MSL]; 
	unsigned long dwBytesAvailable;
	unsigned long dwBytesRead;
	int r;


	// check resolvers stdout		
	dwBytesAvailable=1;
	for (;dwBytesAvailable>0;) 
	{	
		r=PeekNamedPipe(hResolversStdOut,chBuf, MIL, &dwBytesRead, &dwBytesAvailable,NULL);
		if(!r){
			bugf("PeekNamedPipe() reported error %d while reading the resolvers stdout pipe.", GetLastError());
		}else{
			if(dwBytesAvailable){
				memset(&chBuf, 0, sizeof(chBuf)-1);
				chBuf[sizeof(chBuf)-1]='\0';
				if( !ReadFile( hResolversStdOut, chBuf, MSL-10, &dwRead, NULL) || dwRead == 0) {
					break; 
				}			
				//logf("resolver stdout{{{%s}}} len=%d", chBuf, str_len(chBuf));
				resolver_process_response(chBuf);
			}
		}
	}


	// resolvers stderr
	dwBytesAvailable=1;
	for (;dwBytesAvailable>0;) 
	{	 
		r=PeekNamedPipe(hResolversStdErr,NULL, NULL, NULL, &dwBytesAvailable,NULL);
		if(!r){
			bugf("PeekNamedPipe() reported error %d while reading the resolvers stdout pipe.", GetLastError());
		}else{
			if(dwBytesAvailable){
				memset(&chBuf, 0, sizeof(chBuf)-1);
				chBuf[sizeof(chBuf)-1]='\0';
				if( !ReadFile( hResolversStdErr, chBuf, MIL, &dwRead, NULL) || dwRead == 0) {
					break; 
				}
				
				//logf("resolver stderr{{{%s}}} len=%d", chBuf, str_len(chBuf));
			}
		}
	} 
	return 0;
}

#endif // WIN32
/**************************************************************************/
/**************************************************************************/

/**************************************************************************/
// general logging and debugging information -> stderr
void resolver_logf(int verbose, char * fmt, ...)
{
	if(verbose>RESOLVERLOCAL_VERBOSE_LEVEL){
		return;
	}
    char buf[1024];
	va_list args;
	va_start(args, fmt);
	vsnprintf(buf, 1014, fmt, args);
	va_end(args);

	log_string(buf);
}

/**************************************************************************/
#ifdef IPV6_SUPPORT_ENABLED	
	typedef sockaddr_storage sockaddr_type;
#else
    typedef sockaddr_in sockaddr_type;
#endif
	
#ifdef WIN32
	#define get_socket_error_text(errorcode) get_winsock_error_text(errorcode)
	const char *get_winsock_error_text(int errorcode);
#else
	#define get_socket_error_text(errorcode) gai_strerror(errorcode)
#endif

/**************************************************************************/
// return true if it resl
bool resolve_text_ip_into_address(sockaddr_type *socket_address, size_t *address_length, char*pszAddress, int *error_value, char *error_message)
{
	if(IS_NULLSTR(pszAddress)){
		resolver_logf(2, "resolve_text_ip_into_address(): Empty query '%s' is not a valid ipv4 or ipv6 address.", pszAddress);
		return false; // can't have an empty string
	}
#ifdef IPV6_SUPPORT_ENABLED
		struct addrinfo hints, *res;
		memset(&hints, 0, sizeof(struct addrinfo));		
		hints.ai_flags=AI_NUMERICHOST; // we don't want any hostnames here
		hints.ai_family   = PF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;

		res=NULL;
		int r = getaddrinfo(pszAddress, // ip address in text form
					"0",  // we don't care about the service
					&hints, 
					&res);

		if (r) {
			char errmsgbuf[2048];
			if(error_value){
				*error_value=2;
			}
#ifdef WIN32
			if(r==WSAHOST_NOT_FOUND){
				if(error_value){
					*error_value=2;
				}
				sprintf(errmsgbuf,"resolve_text_ip_into_address error %d (%s)- couldn't convert '%s' to a valid ip address.", 
					r, get_socket_error_text(r), pszAddress);
			}
			else{
				sprintf(errmsgbuf,"resolve_text_ip_into_address(): getaddrinfo() error %d (%s)- couldn't convert '%s' to a valid ip address.", 
					 r, get_socket_error_text(r), pszAddress);
			}
#else
			{
				sprintf(errmsgbuf,"resolve_text_ip_into_address(): getaddrinfo() error %d (%s)- couldn't convert '%s' to a valid ip address.", 
					r, gai_strerror(r), pszAddress);
			}
#endif
			if(error_message){
				strcpy(error_message, errmsgbuf);
			}
			if(res){
				freeaddrinfo(res);
			}
			return false;
		}
		// we had success
		memcpy(socket_address, res->ai_addr, res->ai_addrlen);
		*address_length=res->ai_addrlen;
		freeaddrinfo(res);


#else // !IPV6_SUPPORT_ENABLED
		memset(socket_address, 0, sizeof(sockaddr_type));
		socket_address->sin_addr.s_addr = inet_addr( pszAddress);
		socket_address->sin_family = AF_INET;
		*address_length=sizeof(sockaddr_type);

		if(socket_address->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 as a bind address, so we assume the 
			// input node->psz_bind_address was invalid.
			if(error_value){
				*error_value=2;
			}
			if(error_message){
				sprintf(error_message,"Invalid query: '%s' is not a valid ipv4 address.",pszAddress);
			}
			return false;
		}
#endif	// ifdef IPV6_SUPPORT_ENABLED

		return true;
}

/**************************************************************************/
const char *resolverlocal_resolveip(char *argument)
{
	static char result[HSL];
	char result_hostname[2048];
	int errval;
	char errmsg[MSL];
	sockaddr_type socket_address;
	size_t address_length;
	result_hostname[0]='\0';

	// check that we have a valid ipv4 or ipv6 address in szIPAddress
	// by attempting to resolve it, if we don't the below function will 
	// return false
	if(!resolve_text_ip_into_address(&socket_address, &address_length, argument, &errval, errmsg)){
		resolver_logf(errval, "%s", errmsg);
		sprintf(result, "unrecognised address '%s'", argument);
		return result;		
	}

	// report what we are about to do
	resolver_logf(3, "dns lookup: %s", argument);

	// do the actual resolution
#ifdef IPV6_SUPPORT_ENABLED
	const struct sockaddr* sa=(const struct sockaddr*)&socket_address;
	char sz_host[NI_MAXHOST], sz_serv[NI_MAXSERV];
	int hostlen = NI_MAXHOST, servlen = NI_MAXSERV, r;
	socklen_t salen=(socklen_t)address_length;

	r= getnameinfo( sa, salen, sz_host, hostlen, sz_serv, servlen, NI_NUMERICSERV);

	if(r==0){ // success, give back the result 
		strcpy(result_hostname, sz_host);
	}
#else
	struct hostent *hostent;
	hostent= gethostbyaddr( 
		(const char *) &socket_address.sin_addr.s_addr, 
		sizeof(socket_address.sin_addr.s_addr), 
		socket_address.sin_family);
		
	if (hostent){ // success, strdup the result - replacing the old result if necessary
		strcpy(result_hostname, hostent->h_name);
	}
#endif
	sprintf(result, "ip=%s lookup=%s reverse=%s", argument, result_hostname, argument);
	logf("resolverlocal_resolveip(): returning '%s'", result);
	return result;
}

/**************************************************************************/
// look up an ip address or hostname 
// this command can potentially lag the mud
const char *resolverlocal_resolve(char *argument)
{
	bool system_result=false;
	bool log_resolve=true;
	static char result[HSL];
	// process requests in the format: 'resolve <requesting_name> <domain_name>'
	// get the name of the person we are resolving for
	char requesting_name[200];
	char *n=argument;
	while(!IS_NULLSTR(n) && isspace(*n)){ // trim off any leading whitespace
		n++;
	}

	// *n is either the end of the string or a non space character
	char *name_start=n;
	for( ;!IS_NULLSTR(n) && !isspace(*n); n++){
		// loop till we find the next space or end of string
	}
	if(IS_NULLSTR(n)){ // premature end of string -> error
		// we return the message with a destination of "system" since no name was specified
		snprintf( result, sizeof(result), "system invalid request - requesting name not specified.");
		return result;
	}

	// *n contains a space after one or more non space characters
	*n='\0'; // terminate the name_start

	if(str_len(name_start)>(int)sizeof(requesting_name)-1){
		// we return the message with a destination of "system" since no name was specified
		snprintf( result, sizeof(result), "system invalid request - requesting name too long.");
		return result;
	}

	// copy name_start into requesting_name
	strcpy(requesting_name, name_start);
	
	n++; // advance over the terminating we just did
	// fast forward to find the next piece of text
	while(!IS_NULLSTR(n) && isspace(*n)){
		n++;
	}

	if(IS_NULLSTR(n)){// premature end of string -> error
		// we return the message with a destination of "system" since no name was specified
		snprintf( result, sizeof(result), "%s invalid request - nothing specified to resolve.", 
			requesting_name);
		return result;
	}

	log_resolve=(requesting_name[0]!='*' || requesting_name[1]);
	if(!strcmp(requesting_name,"system")){
		log_resolve=false;
		system_result=true;
	}
	if(log_resolve){
		logf( "resolver_local_resolve(): resolving '%s' for '%s'", n, requesting_name);
	}

	// at this stage, n points to the hostname/ip we want to resolve 

#ifdef IPV6_SUPPORT_ENABLED
	{
		// ipv4/ipv6 lookup code
		struct addrinfo hints, *reshead, *res;
		int r;

		memset(&hints, 0, sizeof(hints));
		hints.ai_family = PF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags=AI_CANONNAME; 

		abort_threshold=RUNNING_DNS_ABORT_THRESHOLD;
		update_alarm();
		r=getaddrinfo(n, "80", &hints, &res);
		update_alarm();
		abort_threshold = RUNNING_ABORT_THRESHOLD;

		if(r){
			// failed to resolve address, report the error and give up
			sprintf(result,"%s resolverlocal_resolve(): getaddrinfo() error %d (%s) occurred while attempting to resolve '%s'.", 
				 requesting_name, r, redirect_socket_error_text(r), n);
			if(res){
				freeaddrinfo(res);
			}
			return (log_resolve||system_result)?result:"";
		}
		if(!res){
			// if it did resolve, but didn't give us a usable result report that
			sprintf(result,"%s resolverlocal_resolve(): getaddrinfo() returned an empty res value when resolving '%s' - effectively no result.", 
				 requesting_name, n);
			return (log_resolve||system_result)?result:"";
		}

		// successful resolution, format the results
		if(IS_NULLSTR(res->ai_canonname)){
			sprintf(result, "%s Resolve request for:`1    %s", requesting_name, n);
		}else{
			// canonical name of the specified node (offical name)
			sprintf(result, "%s Resolve request for:`1    %s`1Official Name:`1    %s", 
				requesting_name,  n, res->ai_canonname);
		}

		reshead=res; // save the head of the res list for freeaddrinfo
		for(; res; res= res->ai_next){
			char temp[MSL];
			char sz_host[NI_MAXHOST+1];
			int hostlen = NI_MAXHOST;

			r= getnameinfo( res->ai_addr, (socklen_t)res->ai_addrlen, sz_host, hostlen, NULL, 0,NI_NUMERICHOST);
			if(r){
				snprintf(temp, MSL-2, "`1unexpected error converting binary ai_addr into text version!");
			}else{
				char family[MSL];
				if(res->ai_family==PF_INET){
					strcpy(family, "ipv4");
				}else if(res->ai_family==PF_INET6){
					strcpy(family, "ipv6");
				}else{
					sprintf(family, "%d", res->ai_family);
				}
				snprintf(temp, MSL-2, "`1%s-'%s'", family, sz_host);
			}
			strcat(result,temp);
			if(str_len(result)>HSL-MSL){
				break;
			}
		}
	}
#else
	{
		// ipv4 lookup only code
		struct hostent *h;
		abort_threshold=RUNNING_DNS_ABORT_THRESHOLD;
		update_alarm();
		h=gethostbyname(n);
		update_alarm();
		abort_threshold = RUNNING_ABORT_THRESHOLD;
		if(h && (log_resolve || system_result)){
			// we have a match with one or more addresses
			char temp[8196];
			if(str_len(h->h_name)<8000){

				// offical name
				sprintf(result,"%s Resolve request for:`1    %s`1`1Official Name:`1    %s", 
					requesting_name, n, h->h_name);

				if(h->h_addrtype!=AF_INET){
					sprintf(temp, "`1Unrecognised address type %d result, can't display actual address/addresses.", 
						h->h_addrtype);
					strcat(result,temp);
				}else{
					// ip addresses
					if(h->h_addr_list[0]){			
						int ai=0;
						for( ; h->h_addr_list[ai]; ai++){						
							in_addr address;
							memcpy(&address.s_addr, h->h_addr_list[ai], h->h_length);

							sprintf(temp, "`1ipv4-'%s'", inet_ntoa(address));
							if(str_len(result)+ str_len(temp)<8000){
								strcat(result, temp);
							}
						}
					}

					// aliases
					if(h->h_aliases && *(h->h_aliases)){
						strcat(result, "`1`1Aliases include:");
						char **an=h->h_aliases;
						while(*an){
							sprintf(temp, "`1    '%s'", *an);
							if(str_len(result)+ str_len(temp)< 8000){
								strcat(result, temp);
							}
							an++;
						}
					}
				}

				return (log_resolve||system_result)?result:"";
			}
			snprintf( result, sizeof(result), "%s '%s' resolved, but was too long (%d characters).", 
				requesting_name, n, str_len(h->h_name));
		}else{
			snprintf( result, sizeof(result), "%s Failed to resolve: '%s'", 
				requesting_name, n);
		}
	}
#endif // not IPV6_SUPPORT_ENABLED
	return (log_resolve||system_result)?result:"";;
}
/**************************************************************************/
typedef const char *resolver_function(char *argument);
struct resolverlocal_command_type{
	char *name;
	char *syntax;
	resolver_function *func;
};
/**************************************************************************/
resolverlocal_command_type resolverlocal_command_table[]={
	{"resolve",		"<requesting_user> <ip|dns name>",	resolverlocal_resolve},
	{"resolveip",	"<ip>",				resolverlocal_resolveip},	
	{"", NULL}
};
/**************************************************************************/
void resolverlocal_interpret(const char *line)
{
	char entire_line[MSL];
	char command[MSL];
	char *argstart;
	strncpy(entire_line, line, sizeof(entire_line)-1);
	entire_line[sizeof(entire_line)-1]='\0';

	// find the first space in the table
	argstart=strstr(entire_line, " ");
	if(argstart){
		*argstart='\0'; // terminate the command
		argstart++; // args are the first word after the command
	}else{
		argstart="";
	}
	strcpy(command, entire_line);

	for(int i=0; !IS_NULLSTR(resolverlocal_command_table[i].name); i++){
		if(!strcmp(resolverlocal_command_table[i].name, command)){
			const char *response=(*(resolverlocal_command_table[i].func)) (argstart);
			if(!IS_NULLSTR(response)){
				resolver_process_response(FORMATF("%s_response %s", command, response));
			}
			return;
		}
	}
	resolver_process_response(
		FORMATF("%s_response unrecognised resolverlocal command", command));
	return;
}
/**************************************************************************/
struct resolverlocal_queue_type
{
	char *command;
	resolverlocal_queue_type *next;
};
resolverlocal_queue_type *resolverlocal_queue_list=NULL;
/**************************************************************************/
// all use of the local dns resolver is queued through here
// then called at the bottom of the game_loop() core
void resolverlocal_queue_command(const char *command)
{
	resolverlocal_queue_type *node, *tail;

	node= new resolverlocal_queue_type;

	node->command=str_dup(command);
	node->next=NULL;

	// if there is no list, create one
	if(!resolverlocal_queue_list){
		resolverlocal_queue_list=node;
		return;
	}

	// there is a list, add the new node to the end
	for(tail=resolverlocal_queue_list;tail->next; tail=tail->next){
		// loop until tail is the last in the list
	};
	tail->next=node;

}
/**************************************************************************/
// execute a queued dns command each time it is called
// if there are no commands queued just return
// called once per iteration of the game_loop() core
void resolverlocal_execute_queued_commands()
{
	resolverlocal_queue_type *nextnode;
	if(!resolverlocal_queue_list){
		return;
	}

	resolverlocal_interpret(resolverlocal_queue_list->command);

	// remove the node from the front of the list
	nextnode=resolverlocal_queue_list->next;
	free_string(resolverlocal_queue_list->command);
	delete resolverlocal_queue_list;
	resolverlocal_queue_list=nextnode;
}
/**************************************************************************/
/**************************************************************************/