dawn/notes/
dawn/src/
dawn/src/docs/
/**************************************************************************/
// resolver.cpp 
// can manually compile with:  g++ -o resolver resolver.cpp
// The resolver binary should be in same directory as the dawn binary for
// dawn to take advantage of it.
/**************************************************************************/
// Please note: this should really be rewritten from scratch, but due to 
// timelines to release the codebase, is being released as is.
/**************************************************************************/
// some parameters 
#define DATE_IN_LOGS // if the date is in the log entires
#define RESOLVER_VERSION "1.5"
#define VERBOSE_LEVEL (0)
#define IDENT_CONNECT_TIMEOUT (15)
#define MSL (2048)
#define HSL (16384)

//#define RESOLVER_DEBUG_BRACKETS
/**************************************************************************/
#define __IN_RESOLVER_CPP__ // so resolver.h doesn't complain 
#include "resolver.h"

/**************************************************************************/
// caching stats
int iHostLookups=0;
int iHostLookupsFoundInCache=0;

// prototypes
void sleep_seconds(int seconds);
hnode *find_in_cache(sockaddr_type *socket_address, size_t address_length);
time_t current_time(void);
bool resolve_text_ip_into_address(sockaddr_type *socket_address,		
								size_t *address_length, char*pszAddress, 
								int *error_value, char *error_message);
hnode *lookup_host(sockaddr_type *socket_address, size_t address_length);

/**************************************************************************/
#ifdef WIN32
#define redirect_socket_error_text(r) get_socket_error_text(r) 
#else
#define redirect_socket_error_text(r) gai_strerror(r)
#endif
/**************************************************************************/
typedef const char *resolver_function(char *argument);
struct command_type{
	char *name;
	char *syntax;
	resolver_function *func;
};
/**************************************************************************/
const char *resolver_close(char *argument)
{
	resolver_logf( 0,"close command received - ending resolver");
	exit( 0 );
	return "";
}	
/**************************************************************************/
const char *resolver_stats(char *argument)
{	
	static char result[MSL];
	snprintf(result, sizeof(result), "HostLookups: %d, HostLookupsResolvedFromCache: %d (%0.2f%%)",
		iHostLookups, iHostLookupsFoundInCache,
		(iHostLookups?
			((float)iHostLookupsFoundInCache/(float)iHostLookups)*100
			:0.0)
		);
	return result;
}	
/**************************************************************************/
const char *resolver_version(char *argument)
{
	static char result[MSL];
	snprintf(result, sizeof(result), "%s", RESOLVER_VERSION);

	return result;
}

/**************************************************************************/
// look up an ip address or hostname 
const char *resolver_resolve(char *argument)
{
	bool system_result=false;
	bool log_resolve=true;
	hnode * pHost;
	static char result[HSL];
	char initial_request[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(strlen(name_start)>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){
		resolver_logf( 5,"resolving '%s' for '%s'", n, requesting_name);
	}

	// 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
	sockaddr_type socket_address;
	size_t address_length;
	initial_request[0]='\0';
	bool valid_ip=resolve_text_ip_into_address(&socket_address, &address_length, n, NULL, NULL);
	if(valid_ip){
		// we have an ip... look that up, get the dns name then resolve that
		pHost=lookup_host(&socket_address, address_length);
		if(strcmp(pHost->hostname,n)){
			sprintf(initial_request, "Initial request for %s resolves to '%s'`1",
				n, pHost->hostname);
		} 
		n=pHost->hostname;
	}

	// at this stage, n points to the string we want to resolve on the following basis:
	// - if the request was originally an ip address, that has been looked up, 
	//   and we are now resolving the hostname if it could be resolved (otherwise the ip)
	// - if the request was a hostname all along, we are resolving that.

#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; 

		r=getaddrinfo(n, "80", &hints, &res);
		if(r){
			// if resolution failed, try resolving once more incase it is due to slow DNS
			if(res){
				freeaddrinfo(res);
			}
			r=getaddrinfo(n, "80", &hints, &res);
		}

		if(r){
			// failed to resolve two times in a row, report the error and give up
			sprintf(result,"%s resolver_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 resolver_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 %sResolve request for:`1    %s", initial_request, requesting_name, n);
		}else{
			// canonical name of the specified node (offical name)
			sprintf(result, "%s %sResolve request for:`1    %s`1Official Name:`1    %s", 
				requesting_name, initial_request, 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, 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(strlen(result)>HSL-MSL){
				break;
			}
		}
	}
#else
	{
		// ipv4 lookup only code
		struct hostent *h;
		h=gethostbyname(n);
		if(h && (log_resolve || system_result)){
			// we have a match with one or more addresses
			char temp[8196];
			if(strlen(h->h_name)<8000){

				// offical name
				sprintf(result,"%s %sResolve request for:`1    %s`1`1Official Name:`1    %s", 
					requesting_name, initial_request, 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(strlen(result)+ strlen(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(strlen(result)+ strlen(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, strlen(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:"";;
}

/**************************************************************************/
// return what the text ip resolves to
// fill in the socket_address details based on the szAddress
const char *resolver_resolveip(char *argument)
{
	hnode * pHost;
	static char result[HSL];
	int errval;
	char errmsg[MSL];
	sockaddr_type socket_address;
	size_t address_length;

	// 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);

	// order to do things in is first dns (more important), then ident
	
	pHost= lookup_host(&socket_address, address_length); // looks up the host, and handles caching
	
	// display the dns results
//	resolver_responsef("ip %s %s", pHost->szAddr, pHost->hostname);
//	resolver_logf(2, "IP RESULT SENT: %s %s", pHost->szAddr, pHost->hostname);

	sprintf(result, "ip=%s lookup=%s reverse=%s", pHost->szAddr, pHost->hostname, pHost->szAddr);
	return result;
}

/**************************************************************************/
const char *resolver_commands(char *);
command_type command_table[]={
	{"close",		"",					resolver_close},
	{"commands",	"",					resolver_commands},
	{"resolve",		"<requesting_user> <ip|dns name>",	resolver_resolve},
	{"resolveip",	"<ip>",				resolver_resolveip},	
	{"stats",		"",					resolver_stats},	
	{"version",		"",					resolver_version},
	{"", NULL}
};
/**************************************************************************/
const char *resolver_commands(char *argument)
{
	static char result[MSL];
	result[0]='\0';
	for(int i=0; !IS_NULLSTR(command_table[i].name);){
		strcat(result,command_table[i].name);
		i++;
		if(!IS_NULLSTR(command_table[i].name)){
			strcat(result," ");
		}
	}
	return result;
}
/**************************************************************************/
// process a command (all commands are on a single line)
// return true if a match was found
bool interpret_line(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);

	// find the "command" in the command_table
//	resolver_logf(5, "'%s' command received: argstart='%s'", command, argstart);

	for(int i=0; !IS_NULLSTR(command_table[i].name); i++){
		if(!strcmp(command_table[i].name, command)){
			const char *response=(*(command_table[i].func)) (argstart);
			if(!IS_NULLSTR(response)){
				resolver_responsef("%s_response %s", command, response);
			}
			return true;
		}
	}
	resolver_responsef("%s_response unrecognised command", command);
	return false;
}

/**************************************************************************/
void resolve_ident_failed(hnode *host, char *reason)
{
	if(!IS_NULLSTR(reason)){
		resolver_logf(5, "%s",reason);	
	}
	host->ident_failure_count++;
	host->retry_ident_after=current_time()+ (host->ident_failure_count*60);
}
/**************************************************************************/
char *read_from_socket(SOCKET Socket, int timeout)
{
	static char data[1024];
	fd_set fdRead;
	fd_set fdWrite;
	fd_set fdException;
	FD_ZERO(&fdRead);
	FD_ZERO(&fdWrite);
	FD_ZERO(&fdException);
	FD_SET(Socket, &fdRead);
	FD_SET(Socket, &fdWrite);
	FD_SET(Socket, &fdException);
	
	struct timeval select_timeout;
	select_timeout.tv_usec=0;
	select_timeout.tv_sec=timeout;
	
	if(select(Socket+1, &fdRead, NULL, NULL, &select_timeout)>0){
		if(recv(Socket, data, 1023, 0)<0){
			return "";
		}
		data[1023]='\0';
		return data;
	}
	return "";

}

/**************************************************************************/
int connect_with_timeout(SOCKET s, const struct sockaddr * addr, int timeout)
{
#ifdef WIN32
	// set the socket to non-blocking mode
	unsigned long blockmode = 1; 
	if(ioctlsocket(s, FIONBIO, &blockmode)== SOCKET_ERROR)    
	{        
		resolver_logf(8, "ioctlsocket: error setting new socket to nonblocking, "
			"WSAGetLastError=%d", WSAGetLastError());        
		return SOCKET_ERROR;    
	}  

	// start the connection
	int nRet= connect(s, addr, sizeof(SOCKADDR_IN));
	if(nRet==0){
		return 0;
	}else{
		if(WSAGetLastError()!=WSAEWOULDBLOCK){
			socket_error("connect_with_timeout: connect()");
		}
	}

	// no use a select call to wait for the socket to be ready/timeout
	
	// set our select_timeout, to timeout seconds
	struct timeval select_timeout;
	select_timeout.tv_sec=timeout;
	select_timeout.tv_usec=0;

	// prepare our filedescriptor set
	fd_set fdWrite;
	FD_ZERO(&fdWrite);
	FD_SET(s, &fdWrite);

	// select and wait
	nRet=select(s+1, NULL, &fdWrite, NULL, &select_timeout);

	if(nRet<1){
		if(nRet==0){
			resolver_logf(5, "connect timeout.");
		}else{
			resolver_logf(5, "select() an error occured.");
		}
		return SOCKET_ERROR;
	}
	return 0; // successful connect
#else
// unix version of connect_with_timeout()
	// set the socket to non-blocking mode
	if ( fcntl(s, F_SETFL, O_NONBLOCK)< 0 ){
		resolver_logf(8,"fcntl: error setting new socket to nonblocking");
		return SOCKET_ERROR;
	}

	resolver_logf(9,"connecting to %d", s);

	// set our select_timeout, to sleep for 0.5 seconds
	struct timeval select_timeout;

	// start the connect
	int nRet;
	time_t end_time=current_time()+timeout;
	while(end_time>current_time()){
		// we keep looping till our time is done, 
		// or a positive connect or an unexpected error
		nRet= connect(s, addr, sizeof(SOCKADDR_IN));
		resolver_logf(9,"errno=%d, nRet=%d", errno, nRet);
		if(nRet==0){
			return 0;
		}
		if(nRet<0){
			#ifndef EISCONN
				#define EISCONN 56 // socket is already connected
			#endif
			if(errno==EISCONN){
				return 0;
			}
			if (errno != EINPROGRESS && errno != EALREADY) 
			{
				socket_error("connect_with_timeout(): connect()");
				return SOCKET_ERROR;
			}
		}

		// sleep for half a second
		select_timeout.tv_sec=0;
		select_timeout.tv_usec=500000;
		if(select(0, NULL, NULL, NULL, &select_timeout)<0){
			socket_error("select()");
		}
	}
	
	resolver_logf(5, "connect timeout.");
	return SOCKET_ERROR;
#endif
}
/**************************************************************************/
// return true if successful
bool resolve_ident(hnode *host, int LocalPort, int RemotePort)
{
	SOCKET Socket; 
	Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (Socket == INVALID_SOCKET)
	{
		resolve_ident_failed(host, "Error creating socket for ident!");
		return false;
	}

	resolve_ident_failed(host, "Ident support disabled for now.");
	return false; // not supported for now
}

/**************************************************************************/
#ifdef IPV6_SUPPORT_ENABLED
// if the host is an ipv4 inside an ipv6 address e.g. "::ffff:a.b.c.d"
// then strip off the front prefix and resolve as an ipv4 address
// return NULL if the address isn't of this type, 
// otherwise if it resolves sucessfully, return the dns name
char *resolve_ipv4_inside_ipv6(char *szAddress)
{
	static char result[MSL];
	int r;
	// check if the host is an ipv4 inside an ipv6 address
	// e.g. "::ffff:a.b.c.d"
	if(strncmp("::ffff:", szAddress, strlen("::ffff:"))){
		return NULL;
	}

	strcpy(result, &szAddress[strlen("::ffff:")]);

	sockaddr_type socket_address;
	size_t address_length;
	bool valid_ip=resolve_text_ip_into_address(&socket_address, &address_length, result, NULL, NULL);
	if(!valid_ip){
		return NULL;
	}

	const struct sockaddr* sa=(const struct sockaddr*)&socket_address;
	if(sa->sa_family!=AF_INET){
		return NULL;
	}

	// we have what appears to be a valid ipv4 address, 
	// look it up in the dns to see if we get a result
	char sz_host[NI_MAXHOST], sz_serv[NI_MAXSERV];
	int hostlen = NI_MAXHOST, servlen = NI_MAXSERV;
	size_t salen=address_length;

	r= getnameinfo( sa, salen, sz_host, hostlen, sz_serv, servlen, NI_NUMERICSERV);
	if(r!=0){
		// have 2 attempts at resolving address if first attempt failed
		sleep_seconds(5);
		r= getnameinfo( sa, salen, sz_host, hostlen, sz_serv, servlen, NI_NUMERICSERV);
	}

	if(r!=0){ 
		return NULL;
	}
		
	// success, return the result prefixed to make it clear how it was resolved
	sprintf(result, "::ffff:%s", sz_host);
	return result;
}
#endif

/**************************************************************************/
// attempt to do a dns lookup on a host
// the host structure already has its socket_address, and address_length fields filled in
// this process will fill in the host->hostname field with either the resolved
// name or a copy of the ip which is already in host->szAddr
void resolve_host(hnode *host)
{
#ifdef IPV6_SUPPORT_ENABLED
	const struct sockaddr* sa=(const struct sockaddr*)&host->socket_address;
	char sz_host[NI_MAXHOST], sz_serv[NI_MAXSERV];
	int hostlen = NI_MAXHOST, servlen = NI_MAXSERV, result;
	size_t salen=host->address_length;
	char *embedded_address;

	result = getnameinfo( sa, salen, sz_host, hostlen, sz_serv, servlen, NI_NUMERICSERV);
	if(result!=0){
		// have 2 attempts at resolving address if first attempt failed
		sleep_seconds(5);
		result = getnameinfo( sa, salen, sz_host, hostlen, sz_serv, servlen, NI_NUMERICSERV);
	}

	if(result==0){ // success, strdup the result - replacing the old result if necessary
		if(host->hostname){
			free(host->hostname);
		}
		host->hostname=strdup(sz_host);
		host->reresolve_after=current_time()+ (24*60*60); // cache for up to 1 day

		// if the hostname is actually the same as the ip address, 
		// check if it is an ipv4 address embeeded in an ipv6 address
		if(!strcmp(host->szAddr,host->hostname)){
			embedded_address=resolve_ipv4_inside_ipv6(host->szAddr);
			if(embedded_address){
				if(host->hostname){
					free(host->hostname);
				}
				host->hostname=strdup(embedded_address);
			}
		}
	}else{ // failed to resolve... 

		// check if it is an embeeded address
		char *embedded_address=resolve_ipv4_inside_ipv6(host->szAddr);
		if(embedded_address){
			if(host->hostname){
				free(host->hostname);
			}
			host->hostname=strdup(embedded_address);
			host->reresolve_after=current_time()+ (24*60*60); // cache for up to 1 day
		}else{
			// put in ip address if we dont already have one
			if(!host->hostname){
				host->hostname=strdup(host->szAddr);
			}
			host->reresolve_after=current_time()+ (30*60); // cache for up to 30 minutes
		}
	}
#else
	struct hostent *hostent;
	hostent= gethostbyaddr( 
		(const char *) &host->socket_address.sin_addr.s_addr, 
		sizeof(host->socket_address.sin_addr.s_addr), 
		host->socket_address.sin_family);
		
	if ( !hostent){ 	
		// have 2 attempts at resolving address if first attempt failed
		sleep_seconds(5);
		hostent= gethostbyaddr( 
			(const char *) &host->socket_address.sin_addr.s_addr, 
			sizeof(host->socket_address.sin_addr.s_addr), 
			host->socket_address.sin_family);
	}

	if (hostent){ // success, strdup the result - replacing the old result if necessary
		if(host->hostname){
			free(host->hostname);
		}
		host->hostname=strdup(hostent->h_name);
		host->reresolve_after=current_time()+ (24*60*60); // 1 day
	}else{ // failed to resolve... put in ip address if we dont already have one
		if(!host->hostname){
			host->hostname=strdup(host->szAddr);
		}
		host->reresolve_after=current_time()+ (30*60); // 30minutes
	}
#endif
	return;
}
/**************************************************************************/
hnode *lookup_host(sockaddr_type *socket_address, size_t address_length)
{
	iHostLookups++;
	hnode *result=find_in_cache(socket_address, address_length);

	if(!result){ 
		// details not found in the cache, allocate a new host node
		// in which the constructor sets its reresolve_after value to 0 
		// which in turn will trigger a resolve_host() call below
		result=new hnode(socket_address, address_length);
		
		// insert at the top of the correct hash table bucket
		unsigned int key=get_hash_key(socket_address, address_length);
		result->next=hosts_table[key];
		hosts_table[key]=result;
	}

	// check if the results are old or non existant, if so resolve now
	if(result->reresolve_after<current_time()){
		resolve_host(result);
	}
	return result;
}

/**************************************************************************/
// return true if text ip was resolved successfully
// fill in the socket_address details based on the szAddress
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, "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 if(r==WSAEAFNOSUPPORT && node->protocol==PROTOCOL_IPV6){
				resolver_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, node->psz_bind_address);
			}else
*/			
			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;
}
/**************************************************************************/
char *get_enabled_commands()
{
	static char result[MSL];
	char buf[1024];
	result[0]='\0';
	strcpy(result, "\n");
	for(int i=0; !IS_NULLSTR(command_table[i].name);){
		sprintf(buf, "      %s %s\n", 
			command_table[i].name,
			command_table[i].syntax);
		strcat(result, buf);
		i++;
	}
	return result;
}
/**************************************************************************/
int main(int argc, char *argv[])
{
    char buf[16384];
    char *p;    	

	resolver_logf(0, "\n"
		"============================================================================\n"
		"    Resolver v%s - Caching Host Name and Ident resolving tool\n"
#ifdef IPV6_SUPPORT_ENABLED
		"    Purpose:   Allow dns hostname resolving of ipv4 and ipv6 \n"
		"               addresses and ident lookups for a mud process.\n"
#else
		"    Purpose:   Allow dns hostname resolving of ipv4 addresses\n"
		"               and ident lookups for a mud process.\n"
#endif
		"               http://www.dawnoftime.org/\n"
		"               (c) Michael Garratt 1997-2004\n"
		"\n"
		"    Syntax: aaa.bbb.ccc.ddd                     (dns query only)\n"
		"            aaa.bbb.ccc.ddd,localport,remoteport (dns and ident)\n"
		"            resolve <username> <dns name>    (lookup a dns name)\n"
		"            version                (what version are we running)\n"
		"            stats   (show some stats about resolver performance)\n"
		"\n"
		"    Enabled commands: %s\n"
		"============================================================================", 
		RESOLVER_VERSION,
		get_enabled_commands());


	resolver_logf(1, "Verbose level: %d", VERBOSE_LEVEL);
	resolver_logf(1, "Ident connect timeout: %d second%s", 
		IDENT_CONNECT_TIMEOUT, IDENT_CONNECT_TIMEOUT==1?"":"s");

	if(init_network()==false){
		resolver_logf(0,
			"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
			"! %-10s COULD NOT INITIALISE WINSOCK SUPPORT !\n"
			"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
			argv[0]);
		exit(1);
	};
	

	// initialise the hash table to empty
	for(int i=0; i<HHK; i++){
		hosts_table[i]=NULL;
	}

	// main IO loop 
	while ( fgets( buf, 16383, stdin ) ) 
	{
//		resolver_logf(0, "STDIN(((%s)))", buf);	
		p = strchr( buf, '\n' ); // all commands are terminated with \n

		if ( IS_NULLSTR(p)){
			resolver_logf(5, "^^^^^invalid input - '%s'^^^^^", buf);	
			continue;
		}
		*p = '\0'; // chop off the \n

		if(IS_NULLSTR(buf)){
			continue;
		}

		interpret_line(buf);
    }
    return 0;
};

/**************************************************************************/
hnode *find_in_cache(sockaddr_type *socket_address, size_t address_length)
{
	hnode *host;
	unsigned int key=get_hash_key(socket_address, address_length);

	for (host= hosts_table[key]; host; host= host->next)
	{		
		if ( !memcmp(&host->socket_address, socket_address, address_length) ){
			iHostLookupsFoundInCache++;
			return host;
		}	
	}
	return NULL;
}
/**************************************************************************/
/*
* resolver - caching dns and ident lookup, by Michael Garratt (c)1997-2001
*
* USAGE:
*   accepts input from stdin in the form 
*     aaa.bbb.ccc.ddd\n   (dns query)
*   or 
*     aaa.bbb.ccc.ddd,localport,remoteport\n  (dns and ident query)
*   results are sent to stdout
*   <localport> = port mud is running on locally
*
* maximum input is:
*   15(ip) + 1(,) + 5(biggest short) + 1(,) + 5(biggest short) + 1(\n) = 28

* 
* DNS result:
*   aaa.bbb.ccc.ddd dns.host.name\n
* IDENT result:
*   aaa.bbb.ccc.ddd ,localport,remoteport ident_result\n
*
* an ident response always starts with a , 
* (because , is not valid in a domain name it is easy to detect idents)
* 
* NOTES:
* - Resolver does not bother to ditch old cached results, just relooks up
*   after they have aged a certain amount (1 day by default)
* - Resolver caches negative dns hits - if it cant resolve an ip address 
*   it wont attempt to reresolve the ip for 30 minutes.
* - Resolver caches failed ident attempts, if it cant contact an ident 
*   server, it wont retry an ident for 1minute*number of failures for the
*   specific ip.
* - The resolver will spend up to IDENT_CONNECT_TIMEOUT waiting for a 
*   connect to a ident server to establish.
* - The resolver gives ident servers 20 seconds to respond once connected.
* - The port ident lookups are directed to is take from /etc/services
*/
/**************************************************************************/
//  look at top of file for instructions on manual compilation
/**************************************************************************/