/**************************************************************************/ // 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; } /**************************************************************************/ /**************************************************************************/