/**************************************************************************/ // netio.cpp - network related functions, Kal /*************************************************************************** * 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. * **************************************************************************/ #include "network.h" #define __SEE_NETIO_INTERNAL_STRUCTURES__ // we get some extra structs from netio.h #include "include.h" #include "nanny.h" #include "comm.h" //#define BIND_IPV4_BEFORE_IPV6 // with this set, ipv4 addresses will be bound before ipv6 /**************************************************************************/ void process_input(connection_data *c); // in comm.cpp bool websrv_process_input(connection_data *c); // in websrv.cpp void websrv_process_output(connection_data *c); // in websrv.cpp void greet_http(connection_data *c); // in websrv.cpp /**************************************************************************/ static fd_set fd_ipv4_in; static fd_set fd_ipv4_out; static fd_set fd_ipv4_exception; static fd_set fd_ipv6_in; static fd_set fd_ipv6_out; static fd_set fd_ipv6_exception; /**************************************************************************/ struct fd_set_group_data{ fd_set *in; fd_set *out; fd_set *exception; bool used; int maxdesc; }; /**************************************************************************/ fd_set_group_data fd_set_group[PROTOCOL_ALL]; /**************************************************************************/ void netio_init_fd_set_groups() { fd_set_group[PROTOCOL_IPV6].in=&fd_ipv6_in; fd_set_group[PROTOCOL_IPV6].out=&fd_ipv6_out; fd_set_group[PROTOCOL_IPV6].exception=&fd_ipv6_exception; fd_set_group[PROTOCOL_IPV4].in=&fd_ipv4_in; fd_set_group[PROTOCOL_IPV4].out=&fd_ipv4_out; fd_set_group[PROTOCOL_IPV4].exception=&fd_ipv4_exception; fd_set_group[PROTOCOL_ALL].in=NULL; fd_set_group[PROTOCOL_ALL].out=NULL; fd_set_group[PROTOCOL_ALL].exception=NULL; } /**************************************************************************/ char listen_on_source_text[MSL]; bool listen_on_source_text_set=false; /**************************************************************************/ // visual debug variables - in comm.cpp extern char *visual_debug_next_connection_autoon_ip; extern int visual_debug_next_connection_column_width; extern bool visual_debug_next_connection_hexoutput; void flush_cached_write_to_buffer(connection_data *c); /**************************************************************************/ _contype_lookup_types CONTYPE_table[]={ {"telnet", CONTYPE_TELNET}, {"http", CONTYPE_HTTP}, {"irc", CONTYPE_IRC}, {"mudftp", CONTYPE_MUDFTP}, {"", CONTYPE_UNSET} }; /**************************************************************************/ #define MAX_LISTENING_SOCKET_EXCEPTION 50 // above this, the socket gets ignored /**************************************************************************/ extern bool hotreboot_in_progress; /**************************************************************************/ char current_listen_address[MSL]; bool listening_on_ipv4_addresses; bool listening_on_ipv6_addresses; /**************************************************************************/ listen_on_type *listen_on_first=NULL; listen_on_type *listen_on_last=NULL; /**************************************************************************/ void listen_on_add(char *bind_address, PROTOCOL_TYPE protocol, int port_offset, int port, CONNECTION_TYPE contype) { listen_on_type *node=NULL; if(protocol==PROTOCOL_ALL){ // The only time we can get a PROTOCOL_ALL is if the bind_address was // if we got an empty string for the host. So we can force manual // binding to the any address on each protocol stack individually. #ifdef BIND_IPV4_BEFORE_IPV6 listen_on_add("0.0.0.0", PROTOCOL_IPV4, port_offset, port, contype); #endif listen_on_add("::", PROTOCOL_IPV6, port_offset, port, contype); #ifndef BIND_IPV4_BEFORE_IPV6 listen_on_add("0.0.0.0", PROTOCOL_IPV4, port_offset, port, contype); #endif return; } node=new listen_on_type; node->listening=false; node->psz_bind_address=str_dup(bind_address); node->psz_bound_pair=str_dup("unknown_at_this_stage"); if(protocol!=PROTOCOL_IPV4 && protocol!=PROTOCOL_IPV6){ logf("unexpected protocol type %d!", protocol); do_abort(); } node->protocol=protocol; node->parsed_port_offset=port_offset; node->parsed_port=port; node->contype=contype; node->next=NULL; // append the new node to the linked list of listen_on bindings if(listen_on_last){ listen_on_last->next=node; listen_on_last=node; }else{ listen_on_first=node; listen_on_last=node; } } /**************************************************************************/ /* // // Structure used in getaddrinfo() call. // typedef struct addrinfo { int ai_flags; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST. int ai_family; // PF_xxx. int ai_socktype; // SOCK_xxx. int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6. size_t ai_addrlen; // Length of ai_addr. char *ai_canonname; // Canonical name for nodename. struct sockaddr *ai_addr; // Binary address. struct addrinfo *ai_next; // Next structure in linked list. } ADDRINFO, FAR * LPADDRINFO; // // Flags used in "hints" argument to getaddrinfo(). // #define AI_PASSIVE 0x1 // Socket address will be used in bind() call. #define AI_CANONNAME 0x2 // Return canonical name in first ai_canonname. #define AI_NUMERICHOST 0x4 // Nodename must be a numeric address string. */ /**************************************************************************/ void startup_exit() { logf("exiting due to startup errors"); exit_error( 1 , "startup_exit", "exiting due to netio startup errors"); } /**************************************************************************/ CONNECTION_TYPE loparse_connection_type_lookup( char * contype) { for(int i=0; !IS_NULLSTR(CONTYPE_table[i].name); i++){ if(!str_cmp(contype, CONTYPE_table[i].name)){ return CONTYPE_table[i].contype_enum; } } return CONTYPE_UNSET; } /**************************************************************************/ // protocol_prefix = ( "telnet" | "http" | "irc" | "mudftp" ) "://" | "" char *loparse_connection_type_prefix( char * connection_type_prefix, CONNECTION_TYPE &contype) { contype=CONTYPE_TELNET; // default to telnet char *pstr=strstr(connection_type_prefix, "://"); if(!IS_NULLSTR(pstr)){ *pstr='\0'; // lookup the protocol specified contype=loparse_connection_type_lookup(connection_type_prefix); if(contype==CONTYPE_UNSET){ logf("Unrecognised connection_type prefix '%s://' in listen_on setting.", connection_type_prefix); logf("-"); logf("Valid connection_type_prefix's for the listen_on setting include:"); for(int i=0; !IS_NULLSTR(CONTYPE_table[i].name); i++){ logf(" '%s://'", CONTYPE_table[i].name); } logf("-"); startup_exit(); } return pstr+3; // skip over what was "://" } return connection_type_prefix; } /**************************************************************************/ // port_suffix = ( ":" [ ( "+" | "-" ) ] followed by a number in the range 1024 -> 65535 ) | "" // return the port number int loparse_port_suffix(char * port_suffix, int &port_offset) { char *pstr_end=port_suffix + str_len(port_suffix)-1; // assume there is no port offset port_offset=0; // loop from the end backwards, until we either // we keep going backwards until reaching the start // or we get something that isn't a digit while(pstr_end>=port_suffix && is_digit(*pstr_end)){ pstr_end--; } // check if we have a sign bit, if so, skip back another if(*pstr_end=='-' || *pstr_end=='+'){ pstr_end--; } if(pstr_end<port_suffix){ // we rewound past the start before finding something return 0; // no port_suffix specified } if(*pstr_end!=':'){ // we aren't dealing with a port_suffix return 0; // no port_suffix specified } *pstr_end='\0'; // terminate what comes before the ':' pstr_end++; if(*pstr_end=='-'){ port_offset=-1; pstr_end++; }else if(*pstr_end=='+'){ port_offset=1; pstr_end++; } int value=atoi(pstr_end); if(value<0 || value>65535){ logf("Invalid port_suffix %d specified in listen_on setting, must be in range 0->65535.", value); logf("listen_address text = '%s'", current_listen_address); startup_exit(); } return value; } /**************************************************************************/ // bind_address = bind_ipv4_address | "[" bind_ipv6_address "]" | "" // bind_ipv4_address = a valid ipv4 address | "0.0.0.0" // bind_ipv6_address = a valid ipv6 address | "" char *loparse_bind_address(char * bind_address, PROTOCOL_TYPE &protocol) { if(IS_NULLSTR(bind_address)){ // no address specified protocol=PROTOCOL_ALL; // bind to all IPv4 and IPv6 addresses // this is implemented as two separate bindings by // created by loparse_listen_address() *bind_address='\0'; return bind_address; } if(*bind_address=='[' && bind_address[str_len(bind_address)-1]==']'){ // we have an ipv6 address protocol=PROTOCOL_IPV6; // trim off the trailing ']' bind_address[str_len(bind_address)-1]='\0'; // return the address, less the leading '[' return bind_address+1; } // assume we have an ipv4 address protocol=PROTOCOL_IPV4; return bind_address; } /**************************************************************************/ // listen_address = connection_type_prefix bind_address port_suffix void loparse_listen_address(char * listen_address) { PROTOCOL_TYPE protocol; char *pstr_start_of_bind_address; int port; int port_offset; // -1 = negative offset, 0 = no offset, +1 = positive offset CONNECTION_TYPE contype; // parse off the the connection_type_prefix if one was specified pstr_start_of_bind_address = loparse_connection_type_prefix(listen_address, contype); // parse off the port_suffix if one was specified // - easier for error reporting to take it off before parsing the bind_address // even though it isn't as clean from a parsing perspective port= loparse_port_suffix(pstr_start_of_bind_address, port_offset); // parse the address pstr_start_of_bind_address=loparse_bind_address(pstr_start_of_bind_address, protocol); listen_on_add(pstr_start_of_bind_address, protocol, port_offset, port, contype); } /**************************************************************************/ // listen_addresses = listen_address *( "," listen_address ) void loparse_listen_addresses(const char * listen_addresses) { char buf[MSL]; char *pstr_start_of_listen_address; char *pstr_comma; strcpy(buf, listen_addresses); pstr_start_of_listen_address=buf; // check if we have multiple addresses specified pstr_comma=strstr(pstr_start_of_listen_address, ","); while(!IS_NULLSTR(pstr_comma)){ *pstr_comma='\0'; // terminate the previous listen_address // parse the previous listen_address loparse_listen_address(pstr_start_of_listen_address); // skip over where the comma was pstr_start_of_listen_address=pstr_comma+1; // see if there are any further addresses pstr_comma=strstr(pstr_start_of_listen_address, ","); } // parse the last address (or first (and last) if there is only one) loparse_listen_address(pstr_start_of_listen_address); } /**************************************************************************/ // /* // only one "listen_on" line is supported in gameset.txt // listen_address entries can be separated using a , as per the format specs below ------------------------------------------------------------------------------- == Input Format Specification: listen_on = [ listen_addresses ] listen_addresses = listen_address *( "," listen_address ) listen_address = connection_type_prefix bind_address port_suffix protocol_prefix = ( "telnet" | "http" | "irc" | "mudftp" ) "://" | "" bind_address = bind_ipv4_address | "[" bind_ipv6_address "]" | "" bind_ipv4_address = a valid ipv4 address | "0.0.0.0" bind_ipv6_address = a valid ipv6 address | "" port_suffix = ( ":" [ ( "+" | "-" ) ] followed by a number in the range 1024 -> 65535 ) | "" ------------------------------------------------------------------------------- e.g. ":4077,telnet://:4000,http://[feb0:0:0:ffff::42]:4001,irc://192.168.0.1:4002,mudftp://[::1]:4003" relates to: 1. telnet on port 4077 for all ipv4 & ipv6 addresses 2. telnet on port 4000 for all ipv4 & ipv6 addresses 3. http on port 4001 for the ipv6 address "feb0:0:0:ffff::42" 4. irc on port 4002 for the ipv4 address "192.168.0.1" 5. mudftp on port 4003 for the local loopback ipv6 address "::1" notes: - + or - in the port_suffix indicates an offset from the main port... the main port is defined as being the first telnet port used - if no connection_type is specified, telnet is assumed - if no address is specified, but a port is the mud will listen on all ipv4 and ipv6 addresses for that given port e.g. "http://:4080" - if the address "0.0.0.0" is specified, the mud will listen on all available ipv4 addresses for that given port. e.g. "http://0.0.0.0:4080" - if no address is specified but empty []'s are provided along with a port, the mud will listen on all ipv6 addresses for that given port. e.g. "http://[]:4080" Alternatively [::] can be used for all ipv6 addresses */ // listen_on = [ listen_addresses ] void netio_parse_listen_on(const char * listen_on) { char ltext[MSL]; // the listen_on input can be up to MSL in length if(str_len(listen_on)>MSL){ logf("listen_on text is %d characters - which is too long.", str_len(listen_on)); logf("listen_on text is '%s'", listen_on); do_abort(); } strcpy(listen_on_source_text, trim_string(listen_on)); listen_on_source_text_set=true; logf("parsing listen_on text '%s'", listen_on_source_text); strcpy(ltext, trim_string(listen_on)); loparse_listen_addresses(ltext); } /**************************************************************************/ // dump the parsed listen on database void netio_parsed_listen_on_output() { listen_on_type *node; logf("Parsed listen_on setting, after mainport %d has been applied:", mainport); for(node=listen_on_first; node; node=node->next){ assert(node->protocol==PROTOCOL_IPV6 || node->protocol==PROTOCOL_IPV4); logf(" %-6s port: %d, %s address: %s", CONTYPE_table[node->contype].name, node->bind_port, node->protocol==PROTOCOL_IPV6?"ipv6":"ipv4", node->psz_bind_address); } } /**************************************************************************/ // dump the successfully binded listen on database void netio_binded_listen_on_output() { listen_on_type *node; log_string("The mud is waiting for connections on the following addresses:"); for(node=listen_on_first; node; node=node->next){ assert(node->protocol==PROTOCOL_IPV6 || node->protocol==PROTOCOL_IPV4); if(node->listening){ logf(" s%d> %-6s port: %d, %s address: %s", (int)node->listening_socket, CONTYPE_table[node->contype].name, node->bind_port, node->protocol==PROTOCOL_IPV6?"ipv6":"ipv4", node->psz_bind_address); } } } /**************************************************************************/ // dump the successfully binded listen on database char *netio_return_binded_sockets() { static char result[MSL]; char temp[MSL]; listen_on_type *node; result[0]='\0'; for(node=listen_on_first; node; node=node->next){ assert(node->protocol==PROTOCOL_IPV6 || node->protocol==PROTOCOL_IPV4); if(node->listening){ sprintf(temp, "[contype='%s' protocol='%s' address='%s' port='%d']", CONTYPE_table[node->contype].name, node->protocol==PROTOCOL_IPV6?"ipv6":"ipv4", node->psz_bind_address, node->bind_port); } if((str_len(result) + str_len(temp))< MSL+1){ strcat(result, temp); } } return result; } /**************************************************************************/ char *netio_resolve_bound_pair_values(const struct sockaddr* sa, socklen_t salen, int &port) { static int i; static char buf[4][MSL*3]; ++i%=4; // update the bound pair text, for what we are about to bind to #ifdef IPV6_SUPPORT_ENABLED char sz_host[NI_MAXHOST+1], sz_serv[NI_MAXSERV+1]; int hostlen = NI_MAXHOST, servlen = NI_MAXSERV, result; result = getnameinfo( sa, salen, sz_host, hostlen, sz_serv, servlen, NI_NUMERICHOST | NI_NUMERICSERV); if(result!=0){ sprintf(buf[i],"netio_resolve_bound_pair()-error_%d_while_converting_address", result); port=0; }else{ strcpy(buf[i], sz_host); port=atoi(sz_serv); } #else const struct sockaddr_in *sa2=(struct sockaddr_in*)sa; strcpy(buf[i],inet_ntoa(sa2->sin_addr)); port=ntohs(sa2->sin_port); #endif return buf[i]; } /**************************************************************************/ // return text based on the type of ip address // ipv4 address = textip:portnum // ipv6 address = [textip]:portnum char *netio_format_tcp_pair(const char *textip, int port) { static int i; static char buf[4][MSL*3]; ++i%=4; if(count_char(textip,':')){ // has one or more colons in the textip, assume ipv6 address sprintf(buf[i], "[%s]:%d", textip, port); }else{ // doesn't have a :, therefore assume ipv4 address sprintf(buf[i], "%s:%d", textip, port); } return buf[i]; } /**************************************************************************/ char *netio_resolve_bound_pair(const struct sockaddr* sa, socklen_t salen) { int port; char *textip=netio_resolve_bound_pair_values(sa, salen, port); return netio_format_tcp_pair(textip, port); } /**************************************************************************/ // bind all the listen_on connections void netio_bind_connections() { int result; listen_on_type *node; bool successful_telnet_bind=false; int ai_family=AF_INET; int ai_socktype=SOCK_STREAM; for(node=listen_on_first; node; node=node->next) { int option_on = 1; dawn_socket listening_socket=0; node->listening_socket=0; node->listening_exception_count=0; if(node->protocol==PROTOCOL_IPV4){ ai_family=AF_INET; }else if(node->protocol==PROTOCOL_IPV6){ #ifdef IPV6_SUPPORT_ENABLED ai_family=AF_INET6; #else logf("ipv6 support not enabled, ignoring: '%s://[%s]:%s'", CONTYPE_table[node->contype].name, node->psz_bind_address, FORMATF("%s%d", node->parsed_port_offset==0?" ":(node->parsed_port_offset==1?"+":"-"), node->parsed_port ) ); continue; #endif }else{ logf("unrecognised protocol value %d!", node->protocol); do_abort(); } #ifdef IPV6_SUPPORT_ENABLED struct addrinfo hints, *res; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; // we want to use the result to bind if(!IS_NULLSTR(node->psz_bind_address)){ hints.ai_flags|=AI_NUMERICHOST; // we don't want to support host names for binding } hints.ai_family = ai_family; hints.ai_socktype = ai_socktype; res=NULL; int r = getaddrinfo(IS_NULLSTR(node->psz_bind_address)?NULL:node->psz_bind_address, // hostname FORMATF("%d",node->bind_port), // server &hints, &res); if (r) { #ifdef WIN32 if(r==WSAHOST_NOT_FOUND){ logf("getaddrinfo error %d - couldn't convert '%s' to a valid ip address.", r, node->psz_bind_address); }else if(r==WSAEAFNOSUPPORT && node->protocol==PROTOCOL_IPV6){ 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{ socket_error(FORMATF("netio_bind_connections(): getaddrinfo(%s) error %d", IS_NULLSTR(node->psz_bind_address)?"NULL":node->psz_bind_address, r)); } #else { logf("netio_bind_connections(): getaddrinfo(%s) error %d - '%s'", IS_NULLSTR(node->psz_bind_address)?"NULL":node->psz_bind_address, r, gai_strerror(r)); } #endif if(res){ freeaddrinfo(res); res=NULL; } continue; } #else // !IPV6_SUPPORT_ENABLED ipv4only_addrinfo *res; res=new ipv4only_addrinfo; res->ai_next=NULL; res->ai_family=ai_family; res->ai_socktype=ai_socktype; res->ai_protocol=0; struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = ai_family; sa.sin_port = htons( (u_short) node->bind_port ); if(IS_NULLSTR(node->psz_bind_address)){ sa.sin_addr.s_addr= INADDR_ANY; }else{ sa.sin_addr.s_addr= inet_addr( node->psz_bind_address); if(sa.sin_addr.s_addr==INADDR_NONE){ // we had an invalid address supplied // technically INADDR_NONE can be returned by the valid ip 255.255.255.255 // but 255.255.255.255 isn't useful as a bind address, so we assume the // input node->psz_bind_address was invalid. logf("inet_addr() couldn't convert '%s' to an ipv4 address - ignored.", node->psz_bind_address); replace_string(node->psz_bound_pair, FORMATF("invalid-'%s:%d'",node->psz_bind_address,node->bind_port)); continue; } } // update the bound pair text replace_string(node->psz_bound_pair, FORMATF("%s:%d",inet_ntoa(sa.sin_addr),node->bind_port)); res->ai_addr=(struct sockaddr *) &sa; res->ai_addrlen=sizeof(sa); #endif // ifdef IPV6_SUPPORT_ENABLED // loop thru till we successfully bind for(;res; res=res->ai_next){ listening_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol ); if( listening_socket == dawn_socket_INVALID_SOCKET){ socket_error( "netio_bind_connections(): socket() call" ); continue; } // set the socket so it doesn't block #ifdef unix #if !defined(FNDELAY) #define FNDELAY O_NDELAY #endif if(fcntl(listening_socket, F_SETFL, FNDELAY ) == -1) { socket_error( "connection_allocate: fcntl: FNDELAY" ); continue; } #endif #ifdef WIN32 { // Set Socket to non blocking mode unsigned long blockmode = 1; // no blocking if(ioctlsocket(listening_socket, FIONBIO, &blockmode)== SOCKET_ERROR) { bugf("ioctlsocket: error setting new socket to nonblocking, " "WSAGetLastError=%d", WSAGetLastError()); continue; } } #endif // Allow local port reuse while in TIME_WAIT state if( setsockopt( listening_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &option_on, sizeof(option_on) ) < 0 ) { socket_error( "netio_bind_connections(): setting SO_REUSEADDR" ); closesocket(listening_socket); continue; } // update the bound pair text, for what we are about to bind to replace_string(node->psz_bound_pair, netio_resolve_bound_pair(res->ai_addr, (socklen_t)res->ai_addrlen)); // now try to bind to it result=bind( listening_socket, res->ai_addr, (int)res->ai_addrlen); if( result != 0 ) { bugf("netio_bind_connections(): bind %s - error %d (%s)", node->psz_bound_pair, errno, strerror( errno)); socket_error( FORMATF("Init socket: bind '%s'", node->psz_bound_pair) ); closesocket(listening_socket); logf("Failed to bind '%s' to '%s'.", CONTYPE_table[node->contype].name, node->psz_bound_pair); { // update the bound pair text char tbuf[MIL]; sprintf(tbuf, "failed_bind-%s", node->psz_bound_pair); replace_string(node->psz_bound_pair, tbuf); } continue; } break; } if(!res){ // we didn't successfully bind, record the last failed to bind logf("Due to bind failure, ignoring listen_on entry: '%s://%s:%s'", CONTYPE_table[node->contype].name, node->protocol==PROTOCOL_IPV6?FORMATF("[%s]",node->psz_bind_address):node->psz_bind_address, FORMATF("%s%d", node->parsed_port_offset==0?" ":(node->parsed_port_offset==1?"+":"-"), node->parsed_port)); continue; } if(node->contype==CONTYPE_TELNET){ successful_telnet_bind=true; } if( listen( listening_socket, 100 ) != 0 ) { socket_error("netio_bind_connections: listen"); closesocket(listening_socket); exit_error( 1 , "netio_bind_connections", "listen failed"); } node->listening_socket=listening_socket; node->listening=true; if(node->protocol==PROTOCOL_IPV4){ listening_on_ipv4_addresses=true; }else if(node->protocol==PROTOCOL_IPV6){ listening_on_ipv6_addresses=true; } logf("socket %d listening as %s on %s.", node->listening_socket, CONTYPE_table[node->contype].name, node->psz_bound_pair); #ifdef IPV6_SUPPORT_ENABLED if(res){ freeaddrinfo(res); res=NULL; } #else delete res; #endif } // end of node loop // check we successfully bound at least one telnet port if(!successful_telnet_bind){ logf("The bootup process, failed to bind any telnet connections for listening... " "mud is considered unusable this state, aborting startup."); startup_exit(); } } /**************************************************************************/ /**************************************************************************/ void netio_allocate_bind_ports(int main_port) { listen_on_type *node; bool out_of_range_ports=false; for(node=listen_on_first; node; node=node->next) { if(node->parsed_port_offset){ node->bind_port=main_port + (node->parsed_port * node->parsed_port_offset); }else if(node->parsed_port){ node->bind_port=node->parsed_port; }else{ node->bind_port=main_port; } if(node->bind_port<1024 || node->bind_port>65535){ if(!out_of_range_ports){ logf("Allocating binding ports for listen_on entries... main_port value=%d.", main_port); } logf("Resulting bind port is %d for listen_on entry: '%s://[%s]:%s'", node->bind_port, CONTYPE_table[node->contype].name, node->psz_bind_address, FORMATF("%s%d", node->parsed_port_offset==0?" ":(node->parsed_port_offset==1?"+":"-"), node->parsed_port)); out_of_range_ports=true; } } if(out_of_range_ports){ logf("\n" "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" "One or more of the binding port values were outside the range 1024-65535.\n" "The mud will not start with out of range port values... aborting startup.\n" "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); startup_exit(); } } /**************************************************************************/ void greet_new_connection(connection_data *d); /**************************************************************************/ void netio_init_connection( int new_connection, PROTOCOL_TYPE protocol, CONNECTION_TYPE contype, char *local_ip, int local_port, char *remote_ip, int remote_port) { connection_data *c; bool resolve_address=true; // add the new connection to the list of existing connections c = connection_allocate(); // connection_allocate handles all allocation c->connected_socket = new_connection; c->protocol=protocol; c->contype=contype; // telnet/http/irc/mudftp etc replace_string(c->local_ip,local_ip); c->local_port=local_port; replace_string(c->local_tcp_pair, netio_format_tcp_pair(c->local_ip, c->local_port)); replace_string(c->remote_ip,remote_ip); c->remote_port=remote_port; replace_string(c->remote_tcp_pair, netio_format_tcp_pair(c->remote_ip, c->remote_port)); replace_string(c->remote_hostname,FORMATF("unresolved'%s'", remote_ip)); if(!IS_NULLSTR(game_settings->no_resolve_ip_text) && !strcmp(c->remote_ip, game_settings->no_resolve_ip_text)) { resolve_address=false; } // don't bother doing dns lookups on http requests if(c->contype==CONTYPE_HTTP){ resolve_address=false; replace_string(c->remote_hostname,FORMATF("webrequest'%s'", remote_ip)); } if(resolve_address){ if(resolver_running || GAMESETTING(GAMESET_PERFORM_LOCAL_DNS_LOOKUPS)){ // use resolver to do ip -> hostname conversion // or perform local resolution if the resolver isn't working resolver_query( c ); } } c->ident_raw_result=str_dup(""); c->ident_username=str_dup(""); if(check_connection_ban(c)){ return; } // automatically turn on visual debugging if required if( !IS_NULLSTR(visual_debug_next_connection_autoon_ip) && !strcmp(visual_debug_next_connection_autoon_ip, c->remote_ip)) { logf("Visual Debug automatically turned on for socket %d (%s)", c->connected_socket, c->remote_tcp_pair); c->visual_debugging_enabled=true; c->visual_debug_flush_before_prompt=true; c->visual_debug_strip_prompt=false; c->visual_debug_column_width=visual_debug_next_connection_column_width; c->visual_debug_hexoutput=visual_debug_next_connection_hexoutput; replace_string(visual_debug_next_connection_autoon_ip,""); } // add to the connection list c->next = connection_list; connection_list = c; // by default connections start in CON_DETECT_CLIENT_SETTINGS (35) switch(c->contype){ case CONTYPE_TELNET: default:{ // we deal with telnet connections below } break; case CONTYPE_HTTP:{ greet_http(c); c->connected_state = CON_WEB_REQUEST; return; } break; case CONTYPE_IRC:{ // IRC connections skip CON_DETECT_CLIENT_SETTINGS // and start at CON_GET_NAME greet_new_connection(c); // send them the greeting page c->connected_state = CON_GET_NAME; return; } case CONTYPE_MUDFTP: { // mudftp connections go to CON_FTP_AUTH greet_ftp(c); c->connected_state = CON_FTP_AUTH; return; } } assert(contype==CONTYPE_TELNET); // Connections that get this far are going thru // the CON_DETECT_CLIENT_SETTINGS process { bool t=c->fcommand; c->fcommand=true; // - advertise to them the telnet options we support c->advertise_supported_telnet_options(); #ifdef SHOW_CLIENT_DETECTION // - tell them we are detecting their client settings write_to_buffer( c, "checking for mxp and mccp support.", 0); #endif c->fcommand=t; } return; } /**************************************************************************/ // initialise a new connection and route it to the correct handler // return true if there was a socket waiting to be accepted bool netio_accept_new_connection(listen_on_type *bound_socket) { char sz_remote_pair[MSL]; char sz_local_pair[MSL]; dawn_socket nc; // new connection #ifdef IPV6_SUPPORT_ENABLED struct sockaddr_storage socket_address; #else struct sockaddr_in socket_address; #endif socklen_t sockaddrlen=sizeof(socket_address); nc = accept( bound_socket->listening_socket, (struct sockaddr *)&socket_address, &sockaddrlen); if( nc == dawn_socket_INVALID_SOCKET) { #ifdef WIN32 if(WSAGetLastError() == WSAEWOULDBLOCK) #else if(errno==EWOULDBLOCK) #endif { return false; } socket_error( "init_descriptor: accept" ); return false; } // find out the remote ip and port details char *remote_ip; int remote_port; remote_ip=netio_resolve_bound_pair_values((const struct sockaddr*)&socket_address, sockaddrlen, remote_port); strcpy(sz_remote_pair,netio_format_tcp_pair( remote_ip, remote_port)); // now find out the local ip and port details char *local_ip; int local_port; if(getsockname(nc, (struct sockaddr*)&socket_address, &sockaddrlen)!=0){ socket_error("netio_accept_new_connection: getsockname returned error."); local_ip="netio_accept_new_connection()getsockname_returned_error"; strcpy(sz_local_pair, bound_socket->psz_bound_pair); }else{ local_ip=netio_resolve_bound_pair_values((const struct sockaddr*)&socket_address, sockaddrlen, local_port); strcpy(sz_local_pair,netio_format_tcp_pair( local_ip, local_port)); } logf("incoming %s connection to '%s' from '%s'.", CONTYPE_table[bound_socket->contype].name, sz_local_pair, sz_remote_pair); // make the new connection socket non blocking #ifdef unix #if !defined(FNDELAY) #define FNDELAY O_NDELAY #endif if(fcntl(nc, F_SETFL, FNDELAY ) == -1) { socket_error( "netio_accept_new_connection: fcntl() FNDELAY" ); return true; } #endif #ifdef WIN32 { // Set Socket to non blocking mode unsigned long blockmode = 1; // no blocking if(ioctlsocket(nc, FIONBIO, &blockmode)== SOCKET_ERROR) { bugf("netio_accept_new_connection: ioctlsocket() error setting new socket to nonblocking, " "WSAGetLastError=%d", WSAGetLastError()); return true; } } #endif netio_init_connection( (int)nc, bound_socket->protocol, bound_socket->contype, local_ip, local_port, remote_ip, remote_port); /* // test banner code if(bound_socket->contype!=CONTYPE_HTTP){ { char banner[MSL]; sprintf(banner, "-= dawn site running version %s - %s=-\r\n" "%s" "##new %s connection to '%s' from '%s'", DAWN_RELEASE_VERSION, DAWN_RELEASE_DATE, get_compile_time(false), CONTYPE_table[bound_socket->contype].name, sz_local_pair, sz_remote_pair); send(nc, banner, str_len(banner), 0); } } */ return true; } /**************************************************************************/ void netio_check_for_and_accept_new_connections( ) { fd_set fd_in, fd_out, fd_exception; static struct timeval null_time; listen_on_type *node; int maxdesc=0; // dont accept new connections during a hotreboot if(hotreboot_in_progress){ return; } // because select doesn't seem to support checking for both ipv4 and ipv6 sockets // in a single call for winsock, we have to loop thru the sets of addresses // separately for(int i=0; i<2; i++){ PROTOCOL_TYPE pt= (i==0?PROTOCOL_IPV4:PROTOCOL_IPV6); if(pt==PROTOCOL_IPV4 && !listening_on_ipv4_addresses){ continue; } if(pt==PROTOCOL_IPV6 && !listening_on_ipv6_addresses){ continue; } // initialise the file descriptor sets FD_ZERO( &fd_in ); FD_ZERO( &fd_out ); FD_ZERO( &fd_exception); for(node=listen_on_first; node; node=node->next) { if(!node->listening || node->protocol!=pt){ continue; } if(node->listening_exception_count>MAX_LISTENING_SOCKET_EXCEPTION){ continue; } maxdesc = UMAX( maxdesc, (int)node->listening_socket); FD_SET( (dawn_socket)node->listening_socket, &fd_in ); FD_SET( (dawn_socket)node->listening_socket, &fd_exception); } if( select( maxdesc+1, &fd_in, NULL, &fd_exception, &null_time ) < 0 ) { socket_error( "netio_check_for_and_accept_new_connections: select()" ); do_abort(); exit_error( 1 , "netio_check_for_and_accept_new_connections", "select error"); } for(node=listen_on_first; node; node=node->next) { if( FD_ISSET( node->listening_socket, &fd_exception ) ){ node->listening_exception_count++; bugf( "Exception raised on controlling socket %d (%s), exception count at %d.", node->listening_socket, node->psz_bound_pair, node->listening_exception_count); if(node->listening_exception_count>MAX_LISTENING_SOCKET_EXCEPTION){ bugf("Exception count exceeded maximum for socket - disabling it."); // the code above effectively disables it } continue; } if( FD_ISSET( node->listening_socket, &fd_in ) ){ int concount=0; while(netio_accept_new_connection(node) && ++concount<5){ // loop thru until up to 4 connections are accepted } } } } } /**************************************************************************/ // poll all the connections for input, output and exceptions void netio_poll_connections() { static struct timeval null_time; connection_data *c; int protocol; // initialise the netio_poll file descriptor sets for(protocol=PROTOCOL_IPV6; protocol<PROTOCOL_ALL; protocol++){ FD_ZERO( fd_set_group[protocol].in); FD_ZERO( fd_set_group[protocol].out); FD_ZERO( fd_set_group[protocol].exception); fd_set_group[protocol].used=false; fd_set_group[protocol].maxdesc=0; } if(!connection_list){ // we only want to continue if we have something to poll return; } // populate the file descriptor sets with the list of connected sockets for(c=connection_list; c; c=c->next){ assert(c->protocol==PROTOCOL_IPV6 || c->protocol==PROTOCOL_IPV4); fd_set_group[c->protocol].maxdesc= UMAX( fd_set_group[c->protocol].maxdesc, (int)c->connected_socket); FD_SET( (dawn_socket)c->connected_socket, fd_set_group[c->protocol].in); FD_SET( (dawn_socket)c->connected_socket, fd_set_group[c->protocol].out); FD_SET( (dawn_socket)c->connected_socket, fd_set_group[c->protocol].exception); fd_set_group[c->protocol].used=true; } // find out what is doing what for(protocol=PROTOCOL_IPV6; protocol<PROTOCOL_ALL; protocol++){ if(fd_set_group[protocol].used){ if( select( fd_set_group[protocol].maxdesc+1, fd_set_group[protocol].in, fd_set_group[protocol].out, fd_set_group[protocol].exception, &null_time ) < 0 ) { socket_error( FORMATF("netio_poll_all_connections: select(), protocol=%d", protocol)); do_abort(); exit_error( 1 , "netio_poll_all_connections", "select error"); } } } // remaining processing is done by - called from game_loop() //netio_process_exceptions_from_polled_connections() //netio_process_input_from_polled_connections() //netio_process_output_from_polled_connections() } /**************************************************************************/ void netio_process_exceptions_from_polled_connections() { // Disconnect the connections with raised exceptions // or have been idle, then check for input. for ( connection_data *c = connection_list; c; c = c_next ) { c_next = c->next; if( FD_ISSET( c->connected_socket, fd_set_group[c->protocol].exception) ) { FD_CLR( (unsigned int) c->connected_socket, fd_set_group[c->protocol].in); FD_CLR( (unsigned int) c->connected_socket, fd_set_group[c->protocol].out); if( c->connected_state == CON_PLAYING && CH(c)){ save_char_obj( CH(c) ); } c->outtop = 0; connection_close( c ); } } } /**************************************************************************/ void netio_process_input_from_polled_connections() { // loop thru all connections processing their input for ( connection_data *c = connection_list; c; c = c_next ) { c_next = c->next; c->fcommand = false; if(c->connected_state==CON_RESOLVE_IP){ nanny_resolve_ip(c, ""); } if(c->connected_state==CON_DETECT_CLIENT_SETTINGS){ nanny_detect_client_settings(c, ""); } if(c->connected_state==CON_WEB_REQUEST){ if(!websrv_process_input(c)){ continue; } } if( FD_ISSET( c->connected_socket, fd_set_group[c->protocol].in) ){ if( c->character ) { c->character->timer = 0; c->character->idle = 0; if(c->original){ c->original->idle = 0; } } // check for link dead players if( !read_from_connection( c ) ) { FD_CLR( (dawn_socket)c->connected_socket, fd_set_group[c->protocol].out); if( CH(c)&& CH(c)->level> 1){ save_char_obj(CH(c)); } c->outtop = 0; connection_close(c); continue; } } // get input - upto 2 times if they are speed walking if( c->speedwalk_buf ){ for(int speed=0; speed<2; speed++){ if(c->speedwalk_buf){ process_input(c); }else{ break; } } }else{ process_input(c); } } } /**************************************************************************/ void netio_process_output_from_polled_connections() { // loop thru all connections processing their output for ( connection_data *c = connection_list; c; c = c_next ) { c_next = c->next; flush_cached_write_to_buffer(c); if(FD_ISSET(c->connected_socket, fd_set_group[c->protocol].out)) { if(c->contype==CONTYPE_HTTP ){ if(c->web_request->inHeaderLen){ websrv_process_output(c); } }else if( ( c->fcommand || c->outtop > 0 ) ){ if( !process_output( c, true ) ){ if( c->connected_state == CON_PLAYING && CH(c) ){ save_char_obj( CH(c) ); } c->outtop = 0; connection_close( c ); } } } } } /**************************************************************************/ void netio_close_all_binded_sockets() { listen_on_type *node; for(node=listen_on_first; node; node=node->next){ if(node->listening){ closesocket(node->listening_socket); } } } /**************************************************************************/ /**************************************************************************/