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