/* * IMC2 Liberty Server - Developed by Mud Domain. * Copyright (C)2004-2008 Roger Libiez(Samson) * Additional code Copyright (C)2004-2006 by Jonathan Walker (Xorith) * Comments and suggestions welcome: imc@intermud.us * Registered with the United States Copyright Office: TX 5-953-618 * * IMC2 MUD-Net versions 3.20 through 3.31 developed by Mud Domain. * Copyright (C) 2003-2004 Roger Libiez (Samson) * Additional code Copyright (C) 2003-2004 by Jonathan Walker (Xorith) * * Some portions of code from Hermes 3.17 Copyright (C)2003 Rogel * * IMC2 Hermes version 3.15 is developed by Rogel * Copyright (C) 2003 Rogel * * IMC2 MUD-Net versions 3.00 through 3.10 developed by Alsherok and Crimson Oracles * Copyright (C) 2002-2003 Roger Libiez (Samson) * Additional code Copyright (C) 2002 Orion Elder * Registered with the United States Copyright Office: TX 5-555-584 * * IMC2 Gold versions 1.00 through 2.00 developed by MudWorld. * Copyright (C) 1999 - 2002 * Scion Altera, Shogar, Kratas, Tagith, Noplex, * Senir, Trax, Samson, and Ntanel StormBlade( Anthony R. Haslage ) * * IMC2 version 0.10 - an inter-mud communications protocol * Copyright (C) 1996 & 1997 Oliver Jowett <oliver@randomly.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program (see the file COPYING); if not, write to the * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ using namespace std; #include <sys/stat.h> #include <fcntl.h> #include <sys/time.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #include <zlib.h> #include <cstdarg> #include <cerrno> #include <csignal> #include <string> #include <list> #include <vector> #include <map> #include <sstream> #include <fstream> #include <iomanip> #include "imc.h" #include "md5.h" #include "sha256.h" StreamWrapper logfile; ofstream logstream; bool imcpacketdebug = false; time_t imc_now; /* current time */ time_t imc_boot; /* startup time */ static int event_freecount; static int control; static int memory_head; /* next entry in memory table to use, wrapping */ imc_siteinfo *siteinfo; imc_statistics imc_stats; imc_event *imc_event_list, *imc_event_free; imc_memory imc_memory[IMC_MEMORY]; /* sequence memory */ unsigned long imc_sequencenumber; /* sequence# for outgoing packets */ list<imc_channel*> chanlist; list<connection*> connlist; list<imc_info*> infolist; list<imc_reminfo*> reminfolist; list<imc_laston*> lastonlist; list<packet_handler*> phanlderlist; list<string> banlist; char *const perm_names[] = { "Mort", "Imm", "Admin", "Imp" }; /******************************** * Low level utility functions. * ********************************/ // Compare: astr ><= bstr. // <0 = astr < bstr // 0 = astr == bstr // >0 = astr > bstr // Case insensitive. // -- Justice int str_cmp( const string& astr, const string& bstr ) { string::const_iterator a1, a2, b1, b2; a1 = astr.begin(); a2 = astr.end(); b1 = bstr.begin(); b2 = bstr.end(); while( a1 != a2 && b1 != b2 ) { if( std::toupper(*a1) != std::toupper(*b1) ) return( std::toupper(*a1) < std::toupper(*b1) ? -1 : 1 ); ++a1; ++b1; } return( bstr.size() == astr.size() ? 0 : ( astr.size() < bstr.size() ? -1 : 1 ) ); } /* Borrowed strlcpy and strlcat from OpenSSH, copyright notice and license is in STRLCPY-STRLCAT-LICENSE file */ #ifndef HAVE_STRLCPY /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy( char *dst, const char *src, size_t siz ) { register char *d = dst; register const char *s = src; register size_t n = siz; /* Copy as many bytes as will fit */ if( n != 0 && --n != 0 ) { do { if( ( *d++ = *s++ ) == 0 ) break; } while( --n != 0 ); } /* Not enough room in dst, add NUL and traverse rest of src */ if( n == 0 ) { if( siz != 0 ) *d = '\0'; /* NUL-terminate dst */ while( *s++ ) ; } return( s - src - 1 ); /* count does not include NUL */ } #endif /* !HAVE_STRLCPY */ #ifndef HAVE_STRLCAT /* * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(initial dst) + strlen(src); if retval >= siz, * truncation occurred. */ size_t strlcat( char *dst, const char *src, size_t siz ) { register char *d = dst; register const char *s = src; register size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while( n-- != 0 && *d != '\0' ) ++d; dlen = d - dst; n = siz - dlen; if( n == 0 ) return( dlen + strlen(s) ); while( *s != '\0' ) { if( n != 1 ) { *d++ = *s; --n; } ++s; } *d = '\0'; return( dlen + (s - src) ); /* count does not include NUL */ } #endif /* !HAVE_STRLCAT */ char *md5_crypt( const char *pwd ) { md5_state_t state; static md5_byte_t digest[17]; static char passwd[17]; md5_init( &state ); md5_append( &state, (const md5_byte_t *)pwd, strlen( pwd ) ); md5_finish( &state, digest ); strlcpy( passwd, (const char *)digest, 16 ); /* The listed exceptions below will fubar the MD5 authentication packets, so change them */ for( size_t x = 0; x < strlen( passwd ); ++x ) { if( passwd[x] == '\n' ) passwd[x] = 'n'; if( passwd[x] == '\r' ) passwd[x] = 'r'; if( passwd[x] == '\t' ) passwd[x] = 't'; if( passwd[x] == ' ' ) passwd[x] = 's'; if( (int)passwd[x] == 11 ) passwd[x] = 'x'; if( (int)passwd[x] == 12 ) passwd[x] = 'X'; } return( passwd ); } bool is_number( const string& arg ) { size_t x; bool first = true; if( arg.empty( ) ) return false; for( x = 0; x < arg.length( ); ++x ) { if( first && arg[x] == '-' ) { first = false; continue; } if( !isdigit( arg[x] ) ) return false; first = false; } return true; } /* Strips off leading spaces and tabs in strings. * Useful mainly for input file streams because they suck so much. * Samson 10-16-04 */ void strip_lspace( string & line ) { string::size_type space; space = line.find_first_not_of( ' ' ); if( space == string::npos ) space = 0; line = line.substr( space, line.length( ) ); space = line.find_first_not_of( '\t' ); if( space == string::npos ) space = 0; line = line.substr( space, line.length( ) ); } /* Strips off trailing spaces in strings. */ void strip_tspace( string & line ) { string::size_type space; space = line.find_last_not_of( ' ' ); if( space != string::npos ) line = line.substr( 0, space + 1 ); } /* Strip both leading and trailing spaces from a string */ void strip_spaces( string & line ) { strip_lspace( line ); strip_tspace( line ); } // Pick off one argument from a string and return the rest. string one_argument( const string& argument, string& first ) { string::size_type start, stop, stop2; char find; // Init start = 0; // Make sure first is clean first.clear(); // Empty? if( argument.empty() ) return ""; // Strip leading spaces if( argument[0] == ' ' ) { start = argument.find_first_not_of( ' ' ); // Empty? if( start == argument.npos ) return ""; } // Quotes or space? switch( argument[start] ) { case '\'': find = '\''; ++start; break; case '\"': find = '\"'; ++start; break; default: find = ' '; } // Find end of argument. stop = argument.find_first_of( find, start ); // Empty leftovers? if( stop == argument.npos ) { first = argument.substr( start ); return ""; } // Update first first = argument.substr( start, (stop-start) ); // Strip leading spaces from leftovers stop2 = argument.find_first_not_of( ' ', stop+1 ); // Empty leftovers? if( stop2 == argument.npos ) return ""; // Return leftovers. return argument.substr( stop2 ); } /* Does the list have the member in it? */ bool hasname( const string& list, const string& member ) { string::size_type x; if( list.empty() ) return false; if( ( x = list.find( member ) ) != string::npos ) return true; return false; } /* Add a new member to the list, provided it's not already there */ void addname( string& list, const string& member ) { if( hasname( list, member ) ) return; if( list.empty( ) ) list = member; else list.append( " " + member ); strip_lspace( list ); } /* Remove a member from a list, provided it's there. */ void removename( string& list, const string& member ) { if( !hasname( list, member ) ) return; // Implies the list has more than just this name. if( list.length() > member.length() ) { string die = " " + member; string::size_type pos = list.find( die ); list.erase( pos, die.length() ); } else list.clear(); strip_lspace( list ); } string imc_nameof( const string& src ) { string::size_type x; if( ( x = src.find( '@' ) ) != string::npos && x > 0 ) return src.substr( 0, x ); return src; } string imc_mudof( const string& src ) { string::size_type x; if( ( x = src.find( '@' ) ) != string::npos && x > 0 ) return src.substr( x+1, src.length() ); return src; } string ice_mudof( const string& fullname ) { string::size_type x; if( ( x = fullname.find_first_of( ':' ) ) == string::npos ) return fullname; return fullname.substr( 0, x ); } string imc_makename( const string& person, const string& mud ) { ostringstream name; name << person << "@" << mud; return name.str(); } string escape_string( const string& sData ) { string sDataReturn = ""; unsigned int i = 0; bool quote = false; if( sData.find( ' ' ) != string::npos ) quote = true; for( i = 0; i < sData.length(); ++i ) { if( sData[i] == '"' ) sDataReturn += "\\\""; else if( sData[i] == '\n' ) sDataReturn += "\\n"; else if( sData[i] == '\r' ) sDataReturn += "\\r"; else sDataReturn += sData[i]; } if( quote ) sDataReturn = '"' + sDataReturn + '"'; return sDataReturn; } string unescape_string( const string& sData ) { string sDataReturn = ""; for( size_t i = 0; i < sData.length(); ++i ) { if( sData[i] == '\\' && sData[i+1] == '\"' ) { sDataReturn += '"'; ++i; } else if( sData[i] == '\\' && sData[i+1] == 'n' ) { sDataReturn += "\n"; ++i; } else if( sData[i] == '\\' && sData[i+1] == 'r' ) { sDataReturn += "\r"; ++i; } else sDataReturn += sData[i]; } return sDataReturn; } string parseword( string& line ) { if( line.empty() ) return ""; // check for spaces; if there is no space check for line breaking; if all // else fails go directly to the end of the string ; length ; string::size_type iSpace = line.find( ' ' ); if( iSpace == string::npos ) { iSpace = line.find( '\n' ); if( iSpace == string::npos ) { iSpace = line.find( '\r' ); if( iSpace == string::npos ) iSpace = line.length(); } } // added the sequence to check for quotes; no packet should have a quote // character unless it has strings with spaces inside of it string::size_type iQuote = line.find( '"' ); if( iQuote >= 0 && iQuote < iSpace && line[iQuote-1] != '\\' ) { line = line.substr( iQuote+1, line.length() ); while( ( iQuote = line.find( '"', iSpace+1 ) ) != string::npos ) { iSpace = iQuote; if( iQuote > 0 && line[iQuote-1] == '\\' ) continue; else break; } } string sWord = line.substr( 0, iSpace ); if( iSpace < ( line.length()-1 ) ) line = line.substr( iSpace+1, line.length() ); return sWord; } /* return true if 'name' is a part of 'path' (internal) */ bool inpath( const string& path, const string& name ) { char buf[LSS]; if( path == name ) return true; snprintf( buf, LSS, "%s!", name.c_str() ); if( STRN_CEQL( path.c_str(), buf, strlen(buf) ) ) return true; snprintf( buf, LSS, "!%s", name.c_str() ); if( strlen(buf) < path.length() && STR_CEQL( path.c_str() + path.length() - strlen(buf), buf ) ) return true; snprintf( buf, LSS, "!%s!", name.c_str() ); if( strcasestr( path.c_str(), buf ) ) /* Ntanel */ return true; return false; } /* return 'e' from 'a!b!c!d!e' */ string imc_lastinpath( const string& path ) { string::size_type x; if( ( x = path.find_last_of( '!' ) ) != string::npos && x > 0 ) return path.substr( x+1, path.length() ); return path; } /* return 'b' from 'a!b!c!d!e' */ string imc_serverinpath( const string& path ) { string::size_type x, y; if( path.empty() ) return ""; if( ( x = path.find_first_of( '!' ) ) == string::npos ) return siteinfo->name; string piece = path.substr( x+1, path.length() ); if( ( y = piece.find_first_of( '!' ) ) == string::npos ) return piece; return piece.substr( 0, y ); } /* return 'a' from 'a!b!c!d!e' */ string imc_firstinpath( const string& path ) { string::size_type x; if( ( x = path.find_first_of( '!' ) ) != string::npos && x > 0 ) return path.substr( 0, x ); return path; } int get_permvalue( const char *flag ) { for( size_t x = 0; x < ( sizeof(perm_names) / sizeof(perm_names[0]) ); ++x ) if( STR_CEQL( flag, perm_names[x] ) ) return x; return -1; } bool exists_file( const string& name ) { struct stat fst; /* Stands to reason that if there ain't a name to look at, it damn well don't exist! */ if( name.empty() ) return false; if( stat( name.c_str(), &fst ) != -1 ) return true; else return false; } /* delete the info entry "p" */ void imc_delete_reminfo( imc_reminfo *p ) { imc_cancel_event( NULL, p ); reminfolist.remove( p ); deleteptr( p ); } /* create a new info entry, insert into list */ imc_reminfo *imc_new_reminfo( const string& mud ) { imc_reminfo *p = new imc_reminfo; p->name = mud; p->version = "Unknown"; p->path.clear(); p->top_sequence = 0; p->expired = false; p->url = "Unknown"; p->network = "Unknown"; p->sha256 = false; list<imc_reminfo*>::iterator rin; for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin ) { imc_reminfo *r = *rin; if( strcasecmp( r->name.c_str(), mud.c_str() ) >= 0 ) { reminfolist.insert( rin, p ); return p; } } reminfolist.push_back( p ); return p; } /* * imc_reminfo handling */ /* find an info entry for "name" */ imc_reminfo *imc_find_reminfo( const string& name ) { list<imc_reminfo*>::iterator rin; for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin ) { imc_reminfo *r = *rin; if( !str_cmp( r->name, name ) ) return r; } return NULL; } /* get info struct for given mud */ imc_info *imc_getinfo( const string& mud ) { list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( !str_cmp( i->name, mud ) ) return i; } return NULL; } imc_info *imc_new_info( void ) { imc_info *i = new imc_info; i->port = 0; i->last_connected = 0; i->sha256 = false; i->server = false; i->conn = NULL; i->url = "Unknown"; infolist.push_back( i ); return i; } void imc_delete_info( imc_info *i ) { list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ) { connection *c = *conn; ++conn; if( c->info == i ) do_close( c ); } imc_cancel_event( NULL, i ); infolist.remove( i ); deleteptr( i ); --siteinfo->connects; } imc_channel *iced_findchannel( const string& name ) { list<imc_channel*>::iterator chn; for( chn = chanlist.begin(); chn != chanlist.end(); ++chn ) { imc_channel *c = *chn; if( !str_cmp( c->name, name ) ) return c; } return NULL; } /* get name of a connection */ string imc_getconnectname( const connection *c ) { ostringstream buf; string n; if( c->info ) n = c->info->name; else if( !c->host.empty() ) n = c->host; else n = "Unknown"; buf << n << "[" << c->desc << "]"; return buf.str(); } /* set up a new imc_connect struct, and link it into imc_connect_list */ connection *imc_new_connect( void ) { connection *c = new connection; c->info = NULL; c->host = "Unknown"; c->desc = -1; c->disconnect = false; c->state = CONN_NONE; c->version = 2; c->auth_value = 0; c->newoutput = false; c->is_compressing = false; c->insize = IMC_MINBUF; c->outsize = IMC_MINBUF; CREATE( c->inbuf, char, c->insize ); CREATE( c->outbuf, char, c->outsize ); c->inbuf[0] = c->outbuf[0] = '\0'; c->info = NULL; connlist.push_back( c ); return c; } /* free buffers and extract 'c' from imc_connect_list * called from imc_idle_select when we're done with a connection with * c->state==CONN_NONE */ void imc_extract_connect( connection *c ) { if( c->state != CONN_NONE ) { logfile << __FUNCTION__ << ": non-closed connection" << endl; return; } DISPOSE( c->inbuf ); DISPOSE( c->outbuf ); imc_cancel_event( NULL, c ); connlist.remove( c ); deleteptr( c ); } /****************************************** * Packet handling and routing functions. * ******************************************/ /* accept a connection on the control port */ void do_accept( void ) { connection *c; struct sockaddr_storage ss; socklen_t ss_len = sizeof(ss); char host[NI_MAXHOST]; char serv[NI_MAXSERV]; int n, r, fd; fd = accept( control, (struct sockaddr *) &ss, &ss_len ); if( fd < 0 ) { logfile << __FUNCTION__ << ": accept failed: " << strerror(errno) << endl; return; } n = getnameinfo( (struct sockaddr *) &ss, ss_len, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST ); if( n ) { strlcpy( host, "Unknown", NI_MAXHOST ); strlcpy( serv, "Unknwon", NI_MAXSERV ); } r = fcntl( fd, F_GETFL, 0 ); if( r < 0 || fcntl( fd, F_SETFL, O_NONBLOCK | r ) < 0 ) { logfile << __FUNCTION__ << ": fcntl call failed" << endl; close(fd); return; } c = imc_new_connect(); c->state = CONN_WAITCLIENTPWD; c->desc = fd; c->host = host; c->disconnect = false; imc_add_event( IMC_LOGIN_TIMEOUT, ev_login_timeout, c ); logfile << "Connection from " << host << ":" << serv << " on descriptor " << fd << endl; } /* close given connection */ void do_close( connection *c ) { imc_reminfo *r; if( c->state == CONN_NONE ) return; c->state = CONN_NONE; c->is_compressing = false; if( c->desc ) { logfile << imc_getconnectname( c ) << ": closing link" << endl; close( c->desc ); c->desc = 0; } if( c->info ) { c->info->conn = NULL; /* dont announce a simple reboot - shogar - 2/2/2000 */ imc_add_event( 60, ev_close_notify, c->info->name.c_str() ); if( ( r = imc_find_reminfo( c->info->name ) ) != NULL ) imc_delete_reminfo( r ); c->info = NULL; } c->inbuf[0] = '\0'; c->outbuf[0] = '\0'; } /* char *string_compress( char *buf ) { int len = strlen(buf)+1, comprLen = IMC_MAXBUF-1; int status; static char compr[IMC_MAXBUF-1]; memset( compr, 0, IMC_MAXBUF-1 ); status = compress( (Bytef*)compr, (uLong *)&comprLen, (const Bytef*)buf, len ); if( status != Z_OK ) { switch( status ) { default: logfile << "Unknown error state" << endl; break; case Z_MEM_ERROR: logfile << "Error state was Z_MEM_ERROR" << endl; break; case Z_BUF_ERROR: logfile << "Error state was Z_BUF_ERROR" << endl; break; case Z_DATA_ERROR: logfile << "Error state was Z_DATA_ERROR" << endl; break; } return "ERROR"; } compr[comprLen] = '\0'; return compr; } char *string_uncompress( char *buf ) { int comprLen = IMC_MAXBUF; int status; static char uncompr[IMC_MAXBUF]; int uncomprLen = IMC_MAXBUF; memset( uncompr, 0, IMC_MAXBUF ); status = uncompress( (Bytef*) uncompr, (uLong*)&uncomprLen, (Bytef*)buf, comprLen ); if( status != Z_OK ) { switch( status ) { case Z_MEM_ERROR: logfile << "Error state was Z_MEM_ERROR" << endl; case Z_BUF_ERROR: logfile << "Error state was Z_BUF_ERROR" << endl; case Z_DATA_ERROR: logfile << "Error state was Z_DATA_ERROR" << endl; } logfile << "Compressed data : " << buf << endl; logfile << "Uncompressed data: " << uncompr << endl; return "ERROR"; } uncompr[uncomprLen] = '\0'; return uncompr; } */ /* read waiting data from descriptor. * read to a temp buffer to avoid repeated allocations */ void do_read( connection *c ) { char temp[IMC_MAXBUF]; char *newbuf; int r = read( c->desc, temp, IMC_MAXBUF - 1 ); if( !r || ( r < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) ) { if( !c->info ) { if( r < 0 ) /* read error */ logfile << imc_getconnectname( c ) << ": read error" << endl; else /* socket was closed */ logfile << imc_getconnectname( c ) << ": EOF" << endl; } do_close( c ); return; } if( r < 0 ) /* EAGAIN error */ return; temp[r] = '\0'; // logfile << "RAW DATA READ : " << temp << endl; // logfile << "RAW BYTES READ: " << strlen(temp) << endl; /* if( c->is_compressing ) { c->inflate_buf = string_uncompress( temp ); if( STR_CEQL( c->inflate_buf, "ERROR" ) ) { logfile << __FUNCTION__ << ": Decompression error on socket: " << imc_getconnectname(c) << endl; do_close(c); return; } logfile << "Decompressed data: " << c->inflate_buf << endl; } */ int size = strlen( c->inbuf ) + r + 1; int newsize; if( size >= c->insize ) { newsize = c->insize; while( newsize < size ) newsize *= 2; CREATE( newbuf, char, newsize ); strlcpy( newbuf, c->inbuf, newsize ); DISPOSE( c->inbuf ); c->inbuf = newbuf; c->insize = newsize; } if( size < c->insize/2 && size >= IMC_MINBUF ) { newsize = c->insize; newsize /= 2; CREATE( newbuf, char, newsize ); strlcpy( newbuf, c->inbuf, newsize ); DISPOSE( c->inbuf ); c->inbuf = newbuf; c->insize = newsize; } if( c->is_compressing ) { strlcat( c->inbuf, c->inflate_buf, c->insize ); imc_stats.rx_cmpbytes += r; imc_stats.rx_bytessaved += ( strlen( c->inbuf ) - r ); } else { strlcat( c->inbuf, temp, c->insize ); imc_stats.rx_bytes += r; } // logfile << "INPUT BUFFER: " << c->inbuf << endl; } /* write to descriptor */ void do_write( connection *c ) { int w; if( c->state == CONN_SERVERCONNECT ) { /* Wait for server password */ c->state = CONN_WAITSERVERPWD; return; } int size = strlen( c->outbuf ); if( !size ) /* nothing to write */ return; /* if( c->is_compressing ) { c->deflate_buf = string_compress( c->outbuf ); if( STR_CEQL( c->deflate_buf, "ERROR" ) ) { logfile << __FUNCTION__ << "Compression error on socket: " << imc_getconnectname(c) << endl; do_close(c); return; } w = write( c->desc, c->deflate_buf, strlen( c->deflate_buf ) ); if( !w || ( w < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) ) { if( !c->info ) { if( w < 0 ) // write error logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": write error" << endl; else // socket was closed logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": EOF" << endl; } do_close( c ); return; } } else */ { w = write( c->desc, c->outbuf, strlen( c->outbuf ) ); if( !w || ( w < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) ) { if( !c->info ) { if( w < 0 ) /* write error */ logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": write error" << endl; else /* socket was closed */ logfile << __FUNCTION__<< ": " << imc_getconnectname(c) << ": EOF" << endl; } do_close( c ); return; } } if( w < 0 ) /* EAGAIN */ return; if( imcpacketdebug ) logfile << "Packet sent: " << c->outbuf << endl; if( c->is_compressing ) { imc_stats.tx_cmpbytes += w; imc_stats.tx_bytessaved += ( size - w ); logfile << "Compressed buffer out: " << c->deflate_buf << endl; } else imc_stats.tx_bytes += w; strlcpy( c->outbuf, c->outbuf + size, c->outsize ); } /* put a line onto descriptors output buffer */ void write_buffer( connection *c, const string& line ) { char *newbuf; int newsize = c->outsize; if( c->state == CONN_NONE ) return; if( !c->outbuf[0] ) c->newoutput = true; int len = strlen( c->outbuf ) + line.length() + 3; if( len > c->outsize ) { while( newsize < len ) newsize *= 2; CREATE( newbuf, char, newsize ); strlcpy( newbuf, c->outbuf, newsize ); DISPOSE( c->outbuf ); c->outbuf = newbuf; c->outsize = newsize; } if( len < c->outsize/2 && len >= IMC_MINBUF ) { newsize = c->outsize/2; CREATE( newbuf, char, newsize ); strlcpy( newbuf, c->outbuf, newsize ); DISPOSE( c->outbuf ); c->outbuf = newbuf; c->outsize = newsize; } strlcat( c->outbuf, line.c_str(), c->outsize ); strlcat( c->outbuf, "\n\r", c->outsize ); } /* * Convert a packet to text to then send to the buffer */ void imc_packet::writepacket( connection *c ) { ostringstream txt; string newpath; if( i.path.empty() ) newpath = siteinfo->name; else newpath = i.path + "!" + siteinfo->name; /* Assemble your buffer, and at the same time disassemble the packet struct to free the memory */ txt << i.from << " " << i.sequence << " " << newpath << " " << type << " " << i.to; txt << " " << data.str(); ++imc_stats.tx_pkts; if( txt.str().length() > imc_stats.max_pkt ) imc_stats.max_pkt = txt.str().length(); write_buffer( c, txt.str() ); return; } imc_packet::imc_packet( const string& pfrom, const string& ptype, const string& pto ) { if( ptype.empty() ) { logfile << __FUNCTION__ << ": Attempt to build packet with no type field." << endl; } if( pfrom.empty() ) { logfile << __FUNCTION__ << ": Attempt to build " << ptype << " packet with no from field." << endl; } if( pto.empty() ) { logfile << __FUNCTION__ << ": Attempt to build " << ptype << " packet with no to field." << endl; } from = pfrom.empty() ? "BORKED" : pfrom + "@" + siteinfo->name; type = ptype.empty() ? "BORKED" : ptype; to = pto.empty() ? "BORKED" : pto; stop = false; data.str(""); i.path.clear(); i.sequence = imc_sequencenumber++; if( !imc_sequencenumber ) ++imc_sequencenumber; i.to = to; i.from = from; } imc_packet::imc_packet() { from.clear(); type.clear(); to.clear(); stop = false; data.str(""); i.path.clear(); i.to = to; i.from = from; } /* try to read a line from the input buffer, NULL if none ready * all lines are \n\r terminated in theory, but take other combinations */ static const char *imc_getline( char *buffer, int len ) { int i; static char buf[IMC_PACKET_LENGTH]; /* copy until \n, \r, end of buffer, or out of space */ for( i = 0; buffer[i] && buffer[i] != '\n' && buffer[i] != '\r' && i+1 < IMC_PACKET_LENGTH; ++i ) buf[i] = buffer[i]; /* end of buffer and we haven't hit the maximum line length */ if( !buffer[i] && i+1 < IMC_PACKET_LENGTH ) { buf[0] = '\0'; return NULL; /* so no line available */ } /* terminate return string */ buf[i] = 0; /* strip off extra control codes */ while( buffer[i] && ( buffer[i] == '\n' || buffer[i] == '\r' ) ) ++i; /* remove the line from the input buffer */ strlcpy( buffer, buffer+i, len ); return buf; } /* checkrepeat: check for repeats in the memory table */ bool checkrepeat( const string& mud, unsigned long seq ) { for( int i = 0; i < IMC_MEMORY; ++i ) if( !imc_memory[i].from.empty() && !str_cmp( mud, imc_memory[i].from ) && seq == imc_memory[i].sequence ) return true; /* not a repeat, so log it */ imc_memory[memory_head].from = mud; imc_memory[memory_head].sequence = seq; ++memory_head; if( memory_head == IMC_MEMORY ) memory_head = 0; return false; } bool can_forward( const imc_packet *p ) { if( p->type == "chat" || p->type == "emote" ) { /* The only valid one left these days is 15, don't forward others */ if( p->data.str().find( "channel=15" ) == string::npos ) return false; } /* Contain these only to the server a mud is connected to */ if( p->type == "imc-laston" ) return false; // Temporary hack to kill processing of channel notification packets. if( p->type == "channel-notify" ) return false; /* Stop forwarding packets if they've been flagged */ if( p->stop ) return false; return true; } /* update our routing table based on a packet received with path "path" */ void updateroutes( const string& path ) { imc_reminfo *r; string sender, temp; string::size_type x; /* loop through each item in the path, and update routes to there */ temp = path; while( !temp.empty() ) { sender = imc_firstinpath( temp ); if( sender != siteinfo->name ) { /* not from us */ /* check if its in the list already */ if( !( r = imc_find_reminfo( sender ) ) ) /* not in list yet, create a new entry */ r = imc_new_reminfo( sender ); r->expired = false; r->path = temp; } /* get the next item in the path */ if( ( x = temp.find( '!' ) ) != string::npos ) temp = temp.substr( x+1, temp.length() ); else break; } } /* forward a packet - main routing function, all packets pass through here */ void imc_packet::forward() { imc_reminfo *route = NULL; imc_info *in = NULL, *direct = NULL; string pto; bool isbroadcast = false; bool ismulticast = false; /* check for duplication, and register the packet in the sequence memory */ if( i.sequence && checkrepeat( imc_mudof( i.from ), i.sequence ) ) { delete this; return; } /* check for packets we've already forwarded */ if( inpath( i.path, siteinfo->name ) ) { delete this; return; } /* check for really old packets */ if( ( route = imc_find_reminfo( imc_mudof( i.from ) ) ) != NULL ) { if( ( i.sequence + IMC_PACKET_LIFETIME ) < route->top_sequence ) { ++imc_stats.sequence_drops; delete this; return; } if( i.sequence > route->top_sequence ) route->top_sequence = i.sequence; } /* update our routing info */ updateroutes( i.path ); pto = imc_mudof( i.to ); isbroadcast = ( pto == "*" ); /* broadcasts are, well, broadcasts */ ismulticast = ( pto == "$" ); /* Limited broadcast for servers only */ /* forward to our mud if it's for us */ if( isbroadcast || ismulticast || !str_cmp( pto, siteinfo->name ) ) { to = imc_nameof( i.to ); /* strip the name from the 'pto' */ from = i.from; imc_recv( this ); /* if its only to us (ie. not broadcast) don't forward it */ if( !isbroadcast && !ismulticast ) { delete this; return; } } /* check if we should just drop it (policy rules) */ if( !can_forward( this ) ) { delete this; return; } /* convert 'to' fields that we have a route for to a hop along the route */ if( !isbroadcast && !ismulticast && ( route = imc_find_reminfo( pto ) ) != NULL && !route->path.empty() && !inpath( i.path, imc_lastinpath( route->path ) ) ) /* avoid circular routing */ { /* check for a direct connection: if we find it, and the route isn't * to it, then the route is a little suspect.. also send it direct */ if( pto != imc_lastinpath( route->path ) && ( in = imc_getinfo( pto ) ) != NULL && in->conn ) direct = in; pto = imc_lastinpath( route->path ); } /* check for a direct connection */ if( !isbroadcast && !ismulticast && ( ( in = imc_getinfo( pto ) ) == NULL || !in->conn ) && ( !direct || !direct->conn ) ) { logfile << "Unable to forward packet sent from " << imc_mudof( i.from ) << ": No path available." << endl; logfile << "Packet: " << i.from << " " << i.sequence << " " << i.path << " " << type << " " << i.to << " : Alledgedly destined for: " << pto << endl; delete this; return; } if( isbroadcast ) { list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ++conn ) { connection *c = (*conn); if( c->state == CONN_COMPLETE ) { if( !c->info ) continue; /* don't forward to sites that have already received it, or sites that don't need this packet */ if( inpath( i.path, c->info->name ) ) continue; /* end SPAM fix */ writepacket( c ); } } } /* Multicasts go only to flagged servers in the info list */ else if( ismulticast ) { list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ++conn ) { connection *c = (*conn); if( c->state == CONN_COMPLETE ) { if( !c->info ) continue; if( !c->info->server ) continue; if( inpath( i.path, c->info->name ) ) continue; writepacket( c ); } } } /* forwarding to a specific connection */ else { /* but only if they haven't seen it (sanity check) */ if( in && in->conn && !inpath( i.path, in->name ) ) writepacket( in->conn ); /* send on direct connection, if we have one */ if( direct && direct != in && direct->conn && !inpath( i.path, direct->name ) ) writepacket( direct->conn ); } delete this; } /* notify everyone of the closure - shogar */ void imc_close_notify( const string& host ) { imc_packet *p = new imc_packet( "*", "close-notify", "*@*" ); p->data << "host=" << host; p->forward(); } /* send a keepalive to everyone */ PFUN( imc_send_keepalive ) { imc_packet *p; if( q ) p = new imc_packet( "*", "is-alive", q->from ); else p = new imc_packet( "*", "is-alive", packet ); p->data << "versionid=" << escape_string( IMC_VERSIONID ); p->data << " networkname=" << escape_string( siteinfo->network ); p->data << " url=" << escape_string( siteinfo->url ); p->data << " sha256=1"; p->forward(); } void iced_gannounce( const char *fmt, ... ) { char buf[IMC_DATA_LENGTH]; va_list ap; strlcpy( buf, "announces that ", IMC_DATA_LENGTH ); va_start( ap, fmt ); vsnprintf( buf, IMC_DATA_LENGTH, fmt, ap ); va_end( ap ); imc_send_emote( 15, buf, "*" ); } void iced_privmsg( imc_channel *c, imc_packet *p, const string& exclude ) { string active = c->active, v2; imc_packet *q; while( !active.empty() ) { active = one_argument( active, v2 ); if( ( exclude.empty() || str_cmp( v2, exclude ) ) && imc_find_reminfo( v2 ) ) { q = new imc_packet( p->from, p->type, "*@" + v2 ); q->from = p->from; q->i.from = p->i.from; q->data << p->data.str(); q->forward(); } } deleteptr( p ); } void iced_announce( imc_channel *c, const char *fmt, ... ) { string txt; va_list ap; char buf[IMC_DATA_LENGTH]; txt = "announces that "; va_start( ap, fmt ); vsnprintf( buf, IMC_DATA_LENGTH, fmt, ap ); va_end( ap ); txt += buf; if( !c->open ) { imc_packet *p = new imc_packet( "ICE", "ice-msg-r", "*" ); p->data << "realfrom=ICE@" << siteinfo->name; p->data << " text=" << escape_string( txt ); p->data << " channel=" << c->name; p->data << " emote=1"; iced_privmsg( c, p, "" ); } else { imc_packet *p = new imc_packet( "ICE", "ice-msg-b", "*" ); p->data << "text=" << escape_string( txt ); p->data << " channel=" << c->name; p->data << " emote=1"; p->forward(); } } /* send a keepalive request to everyone - shogar */ void imc_request_keepalive( void ) { imc_packet *p = new imc_packet( "*", "keepalive-request", "*@*" ); p->forward(); } void fixactive( imc_channel *c ) { string invited = c->invited; string operators = c->operators; string buf, v3; if( c->open ) { c->active = ""; return; } buf = ""; while( !invited.empty() ) { invited = one_argument( invited, v3 ); if( !hasname( buf, imc_mudof( v3 ) ) ) addname( buf, imc_mudof( v3 ) ); } while( !operators.empty() ) { operators = one_argument( operators, v3 ); if( !hasname( buf, imc_mudof( v3 ) ) ) addname( buf, imc_mudof( v3 ) ); } if( !hasname( buf, imc_mudof( c->owner ) ) ) addname( buf, imc_mudof( c->owner ) ); c->active = buf; } /* update a channel */ void iced_update( imc_channel *c, const string& to ) { imc_packet *p; fixactive( c ); if( !c->open ) { string active = c->active; if( !to.empty() ) { if( c->active.find( imc_mudof( to ) ) != string::npos ) { p = new imc_packet( "ICE", "ice-update", to ); p->data << "channel=" << c->name; p->data << " owner=" << c->owner; p->data << " policy=" << ( c->open ? "open" : "private" ); if( !c->operators.empty() ) p->data << " operators=" << escape_string( c->operators ); if( !c->invited.empty() ) p->data << " invited=" << escape_string( c->invited ); if( !c->excluded.empty() ) p->data << " excluded=" << escape_string( c->excluded ); if( !c->level.empty() ) p->data << " level=" << c->level; if( !c->lname.empty() ) p->data << " localname=" << c->lname; p->forward(); } } else { string v2; while( !active.empty() ) { active = one_argument( active, v2 ); if( imc_find_reminfo( v2 ) ) { p = new imc_packet( "ICE", "ice-update", "*@" + v2 ); p->data << "channel=" << c->name; p->data << " owner=" << c->owner; p->data << " policy=" << ( c->open ? "open" : "private" ); if( !c->operators.empty() ) p->data << " operators=" << escape_string( c->operators ); if( !c->invited.empty() ) p->data << " invited=" << escape_string( c->invited ); if( !c->excluded.empty() ) p->data << " excluded=" << escape_string( c->excluded ); if( !c->level.empty() ) p->data << " level=" << c->level; if( !c->lname.empty() ) p->data << " localname=" << c->lname; p->forward(); } } } } else { p = new imc_packet( "ICE", "ice-update", !to.empty() ? to : "*@*" ); p->data << "channel=" << c->name; p->data << " owner=" << c->owner; p->data << " policy=" << ( c->open ? "open" : "private" ); if( !c->operators.empty() ) p->data << " operators=" << escape_string( c->operators ); if( !c->invited.empty() ) p->data << " invited=" << escape_string( c->invited ); if( !c->excluded.empty() ) p->data << " excluded=" << escape_string( c->excluded ); if( !c->level.empty() ) p->data << " level=" << c->level; if( !c->lname.empty() ) p->data << " localname=" << c->lname; p->forward(); } logfile << "Channel update for " << c->name << " sent to " << ( !to.empty() ? to : "entire network" ) << endl; } void iced_send_destroy( const string& cname, const string& to ) { imc_packet *p = new imc_packet( "ICE", "ice-destroy", !to.empty() ? to : "*" ); p->data << "channel=" << cname; p->forward(); } void send_reminfo_destroy( const string& mudname ) { imc_packet *p = new imc_packet( "ICE", "reminfo-destroy", "*@$" ); p->data << "mudname=" << mudname; p->forward(); } /* send an emote out on a channel */ void imc_send_emote( int channel, const string& argument, const string& to ) { imc_packet *p = new imc_packet( "ICE", "emote", "*@*" ); p->data << "channel=" << channel; p->data << " text=" << escape_string( argument ); p->forward(); } /* send a tell to a remote player */ void imc_send_tell( const string& to, const string& argument ) { if( imc_mudof(to) == "*" ) return; /* don't let them do this */ imc_packet *p = new imc_packet( "ICE", "tell", to ); p->data << "text=" << escape_string( argument ); p->data << " isreply=1"; p->forward(); } /* respond to a who request with the given data */ void imc_send_whoreply( const string& to, const string& data ) { if( imc_mudof(to) == "*" ) return; /* don't let them do this */ imc_packet *p = new imc_packet( "*", "who-reply", to ); p->data << "text=" << escape_string( data ); p->forward(); } map<string,string> imc_getData( string packet ) { map<string,string> dataMap; string::size_type iEqual = 0; dataMap.clear(); while( ( iEqual = packet.find( '=' ) ) != string::npos ) { string sKey = packet.substr( 0, iEqual ); if( iEqual < ( packet.length() -1 ) ) packet = packet.substr( iEqual+1, packet.length() ); else { sKey.clear(); break; } strip_lspace( sKey ); strip_lspace( packet ); string sValue = parseword( packet ); dataMap[sKey] = unescape_string( sValue ); } return dataMap; } void imc_addnewmud( connection *c, const string& name, const string& pw, const string& spw, const string& encrypt ) { imc_info *i; ostringstream response; list<imc_reminfo*>::iterator rin; /* Check the rest of the remote connections to be sure they aren't on another server already. */ for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin ) { imc_reminfo *r = *rin; if( r->name == name ) { logfile << name << " is already on the network. Autosetup failed." << endl; response << "autosetup " << siteinfo->name << " reject connected"; write_buffer( c, response.str() ); c->disconnect = true; return; } } /* Reject immediately if this server is set private */ if( siteinfo->priv == true ) { logfile << "Privacy: Rejecting autosetup from " << name << endl; response << "autosetup " << siteinfo->name << " reject private"; write_buffer( c, response.str() ); c->disconnect = true; return; } /* Reject if the server has reached it's connection limit. * Have it bounce to a new one it knows about at some point. */ if( siteinfo->connects >= siteinfo->maxconnects ) { logfile << "Rejecting autosetup from " << name << " - Server is full" << endl; response << "autosetup " << siteinfo->name << " reject full"; write_buffer( c, response.str() ); c->disconnect = true; return; } /* Check for a banned connection here */ list<string>::iterator ban; for( ban = banlist.begin(); ban != banlist.end(); ++ban ) { string b = *ban; if( b == c->host ) { logfile << name << ": Banned connection attempt." << endl; response << "autosetup " << siteinfo->name << " reject ban"; write_buffer( c, response.str() ); c->disconnect = true; return; } } /* Autosetup was accepted */ if( !encrypt.empty() ) { if( encrypt == "MD5" ) response << "autosetup " << siteinfo->name << " accept " << siteinfo->network << " MD5-SET"; else response << "autosetup " << siteinfo->name << " accept " << siteinfo->network << " SHA256-SET"; } else response << "autosetup " << siteinfo->name << " accept " << siteinfo->network; write_buffer( c, response.str() ); i = imc_new_info(); i->name = name; i->clientpw = pw; i->serverpw = spw; if( !encrypt.empty() ) { if( encrypt == "MD5" ) { i->md5 = true; i->sha256 = false; } else { i->sha256 = true; i->md5 = false; } } else { i->md5 = false; i->sha256 = false; } /* register them */ i->conn = c; i->login_time = imc_now; i->last_connected = imc_now; c->state = CONN_COMPLETE; c->info = i; ++siteinfo->connects; imc_saveconfig(); imc_cancel_event( ev_login_timeout, c ); imc_cancel_event( ev_close_notify, i->name.c_str() ); return; } void send_sha256_auth( connection *c, const string& name ) { ostringstream auth; c->auth_value = (unsigned long)imc_now; auth << "SHA256-AUTH-INIT " << siteinfo->name << " " << (unsigned long)imc_now; write_buffer( c, auth.str() ); logfile << imc_getconnectname(c) << ": Sending SHA-256 Authentication request to " << name << endl; return; } void send_md5_auth( connection *c, const string& name ) { ostringstream auth; c->auth_value = (unsigned long)imc_now; auth << "MD5-AUTH-INIT " << siteinfo->name << " " << (unsigned long)imc_now; write_buffer( c, auth.str() ); logfile << imc_getconnectname(c) << ": Sending MD5 Authentication request to " << name << endl; return; } /* handle a password from a client */ void clientpassword( connection *c, string& argument ) { string type, mudname, password, version, command, data, encryption; imc_info *i; ostringstream response; argument = one_argument( argument, type ); argument = one_argument( argument, mudname ); if( type.empty() || mudname.empty() ) { logfile << "Invalid packet format from " << c->host << endl; write_buffer( c, "Missing required packet data." ); do_write( c ); c->disconnect = true; return; } argument = one_argument( argument, password ); argument = one_argument( argument, version ); argument = one_argument( argument, command ); argument = one_argument( argument, data ); argument = one_argument( argument, encryption ); /* Verify the version. * Only version 2 is valid, so toss anything else. * There is no plan to actually raise this anyway. */ if( !version.empty() ) { if( str_cmp( version, "version=2" ) ) { logfile << c->host << ": Unsupported version: " << version << endl; write_buffer( c, "Client version is unsupported." ); do_write( c ); c->disconnect = true; return; } } /* Why do I get the feeling this is going to be a VERY BAD IDEA(tm) */ if( type == "COMPRESSME" ) { if( !siteinfo->can_compress ) { write_buffer( c, "COMPRESSION UNAVAILABLE" ); do_write( c ); return; } write_buffer( c, "COMPRESSION ENABLED" ); do_write( c ); c->is_compressing = true; return; } /* Check for a banned connection here */ list<string>::iterator ban; for( ban = banlist.begin(); ban != banlist.end(); ++ban ) { string b = *ban; if( b == c->host ) { logfile << c->host << ": Banned mud attempting to connect: " << mudname << endl; write_buffer( c, "Your mud is banned." ); do_write( c ); c->disconnect = true; return; } } i = imc_getinfo( mudname ); if( type == "MD5-AUTH-REQ" ) { if( !i ) { logfile << imc_getconnectname(c) << ": Unregistered MD5 connection attempt by " << mudname << endl; write_buffer( c, "Unregistered MD5 connection attempt." ); do_write( c ); c->disconnect = true; return; } send_md5_auth( c, i->name ); return; } else if( type == "MD5-AUTH-RESP" ) { char pwd[IMC_DATA_LENGTH]; char *cryptpwd; if( !i ) { logfile << imc_getconnectname(c) << ": Bloody hell! How is " << mudname << " not already registered?" << endl; write_buffer( c, "Possible malformed packet sent." ); do_write( c ); c->disconnect = true; return; } if( !i->md5 ) { logfile << imc_getconnectname(c) << ": " << i->name << " attempted to use MD5 authentication but is not configured for it." << endl; write_buffer( c, "MD5 is not configured." ); do_write( c ); c->disconnect = true; return; } if( password.empty() ) { logfile << imc_getconnectname(c) << ": MD5 Authentication failure: No password was returned by " << i->name << endl; write_buffer( c, "No MD5 password provided." ); do_write( c ); c->disconnect = true; return; } /* Lets encrypt this bastard now! */ snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", c->auth_value, i->clientpw.c_str(), i->serverpw.c_str() ); cryptpwd = md5_crypt( pwd ); if( !STR_CEQL( cryptpwd, password.c_str() ) ) { logfile << imc_getconnectname(c) << ": MD5 Authentication failure: Invalid password response by " << i->name << endl; write_buffer( c, "Wrong MD5 password provided." ); do_write( c ); c->disconnect = true; return; } /* Well, ok. Lets at least see if they're calling from the same IP. If not, bounce it. */ if( i->conn ) { if( i->conn->host != c->host ) { logfile << i->name << ": Already connected." << endl; write_buffer( c, "Mud is already connected." ); do_write( c ); c->disconnect = true; return; } else do_close( i->conn ); } /* register them */ i->conn = c; c->state = CONN_COMPLETE; c->info = i; /* send our response */ response << "MD5-AUTH-APPR " << siteinfo->name << " " << siteinfo->network << " version=2"; write_buffer( c, response.str() ); logfile << imc_getconnectname(c) << ": MD5 Authentication complete." << endl; c->info->login_time = imc_now; c->info->last_connected = imc_now; imc_cancel_event( ev_login_timeout, c ); imc_cancel_event( ev_close_notify, i->name.c_str() ); return; } else if( type == "SHA256-AUTH-REQ" ) { if( !i ) { logfile << imc_getconnectname(c) << ": Unregistered SHA-256 connection attempt by " << mudname << endl; write_buffer( c, "Unregistered SHA-256 connection attempt." ); do_write( c ); c->disconnect = true; return; } send_sha256_auth( c, i->name ); return; } else if( type == "SHA256-AUTH-RESP" ) { char pwd[IMC_DATA_LENGTH]; char *cryptpwd; if( !i ) { logfile << imc_getconnectname(c) << ": Bloody hell! How is " << mudname << " not already registered?" << endl; write_buffer( c, "Possible malformed packet sent." ); do_write( c ); c->disconnect = true; return; } if( !i->sha256 && !i->md5 ) // Hack to accomadate upgraders from MD5 { logfile << imc_getconnectname(c) << ": " << i->name << " attempted to use SHA-256 authentication but is not configured for it." << endl; write_buffer( c, "SHA-256 is not configured." ); do_write( c ); c->disconnect = true; return; } if( password.empty() ) { logfile << imc_getconnectname(c) << ": SHA-256 Authentication failure: No password was returned by " << i->name << endl; write_buffer( c, "No SHA-256 password provided." ); do_write( c ); c->disconnect = true; return; } /* Lets encrypt this bastard now! */ snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", c->auth_value, i->clientpw.c_str(), i->serverpw.c_str() ); cryptpwd = sha256_crypt( pwd ); if( !STR_CEQL( cryptpwd, password.c_str() ) ) { logfile << imc_getconnectname(c) << ": SHA-256 Authentication failure: Invalid password response by " << i->name << endl; write_buffer( c, "Wrong SHA-256 password provided." ); do_write( c ); c->disconnect = true; return; } /* Well, ok. Lets at least see if they're calling from the same IP. If not, bounce it. */ if( i->conn ) { if( i->conn->host != c->host ) { logfile << i->name << ": Already connected." << endl; write_buffer( c, "Mud is already connected." ); do_write( c ); c->disconnect = true; return; } else do_close( i->conn ); } /* register them */ i->conn = c; c->state = CONN_COMPLETE; c->info = i; /* send our response */ response << "SHA256-AUTH-APPR " << siteinfo->name << " " << siteinfo->network << " version=2"; write_buffer( c, response.str() ); logfile << imc_getconnectname(c) << ": SHA-256 Authentication complete." << endl; c->info->login_time = imc_now; c->info->last_connected = imc_now; imc_cancel_event( ev_login_timeout, c ); imc_cancel_event( ev_close_notify, i->name.c_str() ); if( !i->sha256 && i->md5 ) // Another hack to accomadate MD5 upgrade { i->sha256 = true; i->md5 = false; imc_saveconfig(); } return; } else if( type != "PW" ) { logfile << imc_getconnectname(c) << ": Invalid authentication packet received from " << mudname << endl; write_buffer( c, "Invalid authentication packet sent." ); do_write( c ); c->disconnect = true; return; } /* They don't exist - lets add them to the server config if they supplied all the needed data */ if( !i && !command.empty() && command == "autosetup" ) { if( !data.empty() ) { if( !encryption.empty() ) imc_addnewmud( c, mudname, password, data, encryption ); else imc_addnewmud( c, mudname, password, data, "" ); return; } logfile << imc_getconnectname(c) << ": malformed autosetup command received from " << mudname << endl; response << "autosetup " << siteinfo->name << " reject data format invalid"; write_buffer( c, response.str() ); do_write( c ); c->disconnect = true; return; } if( !i ) { logfile << imc_getconnectname(c) << ": Unregistered connection attempt for " << mudname << endl; write_buffer( c, "No autosetup. Consider upgrading." ); do_write( c ); c->disconnect = true; return; } /* They're in the list already. Authenticate them. */ if( i->md5 ) { logfile << imc_getconnectname(c) << ": MD5 authentication failure for " << i->name << ": Required authorization not provided." << endl; write_buffer( c, "MD5 authentication is required." ); do_write( c ); c->disconnect = true; return; } if( i->sha256 ) { logfile << imc_getconnectname(c) << ": SHA-256 authentication failure for " << i->name << ": Required authorization not provided." << endl; write_buffer( c, "SHA-256 authentication is required." ); do_write( c ); c->disconnect = true; return; } if( i->clientpw != password ) { logfile << imc_getconnectname(c) << ": Plain text password failure for " << i->name << endl; write_buffer( c, "Invalid client password sent." ); do_write( c ); c->disconnect = true; return; } /* Well, ok. Lets at least see if they're calling from the same IP. If not, bounce it. */ if( i->conn ) { if( i->conn->host != c->host ) { logfile << i->name << ": Already connected." << endl; write_buffer( c, "Mud is already connected." ); do_write( c ); c->disconnect = true; return; } else do_close( i->conn ); } /* register them */ i->conn = c; c->state = CONN_COMPLETE; c->info = i; /* Once MD5 is set, it will be an enforced requirement from then on unless the server is told otherwise. */ if( !encryption.empty() && encryption == "MD5" ) { i->md5 = true; i->sha256 = false; imc_saveconfig(); } else if( !encryption.empty() && encryption == "SHA256" ) { i->sha256 = true; i->md5 = false; imc_saveconfig(); } /* send our response */ if( i->md5 ) response << "PW " << siteinfo->name << " " << i->serverpw << " version=2 " << siteinfo->network << " MD5-SET"; else if( i->sha256 ) response << "PW " << siteinfo->name << " " << i->serverpw << " version=2 " << siteinfo->network << " SHA256-SET"; else response << "PW " << siteinfo->name << " " << i->serverpw << " version=2 " << siteinfo->network; write_buffer( c, response.str() ); logfile << imc_getconnectname(c) << ": Standard authentication complete." << endl; c->info->login_time = imc_now; c->info->last_connected = imc_now; imc_cancel_event( ev_login_timeout, c ); imc_cancel_event( ev_close_notify, i->name.c_str() ); } void finalize_connection( connection *c, imc_info *i ) { if( i->conn ) /* kill old connections */ do_close( i->conn ); i->conn = c; c->state = CONN_COMPLETE; logfile << imc_getconnectname(c) << ": connected." << endl; c->info->login_time = imc_now; c->info->last_connected = imc_now; imc_cancel_event( ev_login_timeout, c ); imc_cancel_event( ev_reconnect, c->info ); imc_cancel_event( ev_close_notify, i->name.c_str() ); imc_request_keepalive(); return; } /* * Handles authentication for 2.x and 1.x clients the server connected out to. */ void serverpassword( connection *c, string& packet ) { string type, servername, password, version; ostringstream response; imc_info *i; packet = one_argument( packet, type ); packet = one_argument( packet, servername ); packet = one_argument( packet, password ); packet = one_argument( packet, version ); if( type.empty() || servername.empty() || password.empty() ) { logfile << "Invalid packet format from " << c->host << endl; write_buffer( c, "Missing required packet data." ); c->disconnect = true; return; } /* Verify the version. * Only version 2 is valid, so toss anything else. * There is no plan to actually raise this anyway. */ if( !version.empty() ) { if( str_cmp( version, "version=2" ) ) { logfile << c->host << ": unsupported version!" << endl; write_buffer( c, "Client version is unsupported." ); c->disconnect = true; return; } } if( !( i = imc_getinfo( servername ) ) ) { logfile << imc_getconnectname(c) << ": Unresgistered connection attempt: " << servername << endl; write_buffer( c, "Your connection isn't registered." ); c->disconnect = true; return; } if( type == "MD5-AUTH-INIT" ) { char pwd[IMC_DATA_LENGTH]; char *cryptpwd; long auth_value = 0; if( password.empty() ) { logfile << "MD5 Authentication failure: No auth_value was returned by " << servername << endl; write_buffer( c, "Missing MD5 auth value." ); c->disconnect = true; return; } /* Lets encrypt this bastard now! */ auth_value = atol( password.c_str() ); snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", auth_value, i->clientpw.c_str(), i->serverpw.c_str() ); cryptpwd = md5_crypt( pwd ); response << "MD5-AUTH-RESP " << siteinfo->name << " " << cryptpwd << " version=2"; write_buffer( c, response.str() ); return; } /* MD5 response is pretty simple. */ if( type == "MD5-AUTH-APPR" ) { logfile << imc_getconnectname(c) << ": MD5 Authentication completed." << endl; finalize_connection( c, i ); return; } if( type == "SHA256-AUTH-INIT" ) { char pwd[IMC_DATA_LENGTH]; char *cryptpwd; long auth_value = 0; if( password.empty() ) { logfile << "SHA256 Authentication failure: No auth_value was returned by " << servername << endl; write_buffer( c, "Missing SHA256 auth value." ); c->disconnect = true; return; } /* Lets encrypt this bastard now! */ auth_value = atol( password.c_str() ); snprintf( pwd, IMC_DATA_LENGTH, "%ld%s%s", auth_value, i->clientpw.c_str(), i->serverpw.c_str() ); cryptpwd = sha256_crypt( pwd ); response << "SHA256-AUTH-RESP " << siteinfo->name << " " << cryptpwd << " version=2"; write_buffer( c, response.str() ); return; } /* SHA256 response is pretty simple. */ if( type == "SHA256-AUTH-APPR" ) { logfile << imc_getconnectname(c) << ": SHA256 Authentication completed." << endl; finalize_connection( c, i ); return; } if( type != "PW" ) { logfile << imc_getconnectname(c) << ": non-PW password packet in serverpassword" << endl; write_buffer( c, "Invalid authentication packet sent." ); c->disconnect = true; return; } if( password != i->serverpw ) { logfile << imc_getconnectname(c) << ": Invalid plain text authentication for " << servername << endl; write_buffer( c, "Invalid authentication password sent." ); c->disconnect = true; return; } finalize_connection( c, i ); } /* get some IMC stats, return a string describing them */ string imc_getstats( const string& choice ) { ostringstream buf; int evcount = 0; imc_event *ev = imc_event_list; if( choice.empty() || choice == "network" ) { while( ev ) { ++evcount; ev = ev->next; } buf << "~WGeneral IMC Statistics" << endl; buf << "~cReceived packets : ~W" << imc_stats.rx_pkts << endl; buf << "~cReceived bytes : ~W" << imc_stats.rx_bytes << endl; buf << "~cCompressed bytes recvd : ~W" << imc_stats.rx_cmpbytes << endl; buf << "~cBytes saved : ~W" << imc_stats.rx_bytessaved << endl; buf << "~cTransmitted packets : ~W" << imc_stats.tx_pkts << endl; buf << "~cTransmitted bytes : ~W" << imc_stats.tx_bytes << endl; buf << "~cCompressed bytes sent : ~W" << imc_stats.tx_cmpbytes << endl; buf << "~cBytes saved : ~W" << imc_stats.tx_bytessaved << endl; buf << "~cMaximum packet size : ~W" << imc_stats.max_pkt << endl; buf << "~cPending events : ~W" << evcount << endl; buf << "~cSequence drops : ~W" << imc_stats.sequence_drops << endl; buf << "~cLast IMC Boot : ~W" << ctime( &imc_boot ) << endl; return buf.str(); } if( choice == "general" ) { buf << "~WSite Information:" << endl; buf << "~cName :~W " << siteinfo->name << endl; buf << "~cIMC Version:~W " << IMC_VERSIONID << endl; buf << "~cAddress :~W " << siteinfo->url << endl; return buf.str(); } return "Bad invocation of imc_getstats."; } string process_directconnection_list( void ) { ostringstream buf; string strtime; buf << setiosflags( ios::left ); buf << "~CDirect Connections to " << siteinfo->name << ":" << endl; buf << "~W" << setw(20) << "Name" << setw(15) << "Conn Type" << setw(14) << "Conn State" << "Last Connected" << endl; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( i->conn ) strtime = ctime( &(i->login_time) ); else strtime = ctime( &(i->last_connected) ); strtime = strtime.substr( 0, strtime.length()-1 ); buf << endl << "~c" << setw(20) << i->name << setw(17) << ( i->server ? "~CServer" : "~BClient" ); buf << setw(16) << ( i->conn ? "~GOnline" : "~ROffline" ); buf << "~c" << strtime; } buf << endl; return buf.str(); } PFUN( imc_recv_who ) { map<string,string> keymap = imc_getData( packet ); if( keymap["type"] == "who" ) imc_send_whoreply( q->from, process_directconnection_list() ); else if( keymap["type"] == "info" ) imc_send_whoreply( q->from, imc_getstats( "general" ) ); else if( keymap["type"] == "istats" ) imc_send_whoreply( q->from, imc_getstats( "network" ) ); else if( keymap["type"] == "help" || keymap["type"] == "services" ) imc_send_whoreply( q->from, "~WAvailable imcminfo types:\r\n" "~chelp ~W- ~Cthis list\r\n" "~cwho ~W- ~Clist directly connected muds\r\n" "~cinfo ~W- ~Cserver information\r\n" "~cistats ~W- ~Cnetwork traffic statistics\r\n" ); else imc_send_whoreply( q->from, "Sorry, no information of that type is available." ); } /* called when a keepalive has been received */ PFUN( imc_recv_isalive ) { imc_reminfo *p; map<string,string> keymap = imc_getData( packet ); if( imc_mudof( q->from ) == siteinfo->name ) return; /* this should never fail, imc.c should create an entry if one doesn't exist (in the path update code) */ if( !( p = imc_find_reminfo( imc_mudof( q->from ) ) ) ) return; if( keymap["versionid"] != p->version ) p->version = keymap["versionid"]; /* Not sure how or why this might change, but just in case it does */ if( keymap["networkname"] != p->network ) p->network = keymap["networkname"]; imc_info *i = imc_getinfo( p->name ); if( keymap["url"] != p->url ) { p->url = keymap["url"]; if( i ) i->url = keymap["url"]; } /* If this causes a breakage.... */ p->sha256 = atoi( keymap["sha256"].c_str() ); if( !keymap["host"].empty( ) ) p->host = keymap["host"]; if( !keymap["port"].empty( ) ) p->port = keymap["port"]; } PFUN( imc_recv_reminfo_destroy ) { imc_reminfo *p; map<string,string> keymap = imc_getData( packet ); /* Do nothing if it's not in the list */ if( !( p = imc_find_reminfo( keymap["mudname"] ) ) ) return; logfile << "Destroying remote info for " << p->name << " by request of " << q->from << endl; imc_delete_reminfo( p ); } PFUN( imc_recv_laston ) { map<string,string> keymap = imc_getData( packet ); ostringstream buf; string strtime; bool found = false; if( keymap["username"].empty() ) { imc_send_tell( q->from, "You must specify a username, or all, to get a listing." ); return; } list<imc_laston*>::iterator lst; for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst ) { imc_laston *l = *lst; if( !str_cmp( l->name, keymap["username"] ) ) { strtime = ctime( &(l->last_time) ); strtime = strtime.substr( 0, strtime.length()-1 ); buf << l->name << " was last seen using " << l->chan << " on " << strtime; imc_send_tell( q->from, buf.str() ); return; } } for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst ) { imc_laston *l = *lst; if( !str_cmp( keymap["username"], "all" ) || !str_cmp( imc_nameof( l->name ), keymap["username"] ) ) { strtime = ctime( &(l->last_time) ); strtime = strtime.substr( 0, strtime.length()-1 ); buf << l->name << " was last seen using " << l->chan << " on " << strtime; imc_send_tell( q->from, buf.str() ); buf.str(""); found = true; } } if( !found ) { buf << keymap["username"] << " has not been seen online recently."; imc_send_tell( q->from, buf.str() ); } } /* Only updated by broadcast channel messages */ void imc_update_laston( const string& from, const string& chan ) { imc_laston *laston; list<imc_laston*>::iterator lst; for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst ) { imc_laston *l = *lst; if( !str_cmp( from, l->name ) ) { l->name = from; l->chan = chan; l->last_time = imc_now; return; } } laston = new imc_laston; laston->name = from; laston->chan = chan; laston->last_time = imc_now; lastonlist.push_back( laston ); } /* see if someone can talk on a channel - lots of string stuff here! */ bool ice_audible( imc_channel *c, const string& who ) { if( !c || who.empty() ) return false; /* owners and operators always can */ if( c->owner == who || hasname( c->operators, who ) ) return true; /* ICE locally can use any channel */ if( imc_nameof(who) == "ICE" && imc_mudof(who) == siteinfo->name ) return true; if( c->open ) { /* open policy. default yes. override with excludes, then invites */ if( ( hasname( c->excluded, who ) || hasname( c->excluded, imc_mudof(who) ) ) && !hasname( c->invited, who ) && !hasname( c->invited, imc_mudof(who) ) ) return false; else return true; } /* closed or private. default no, override with invites, then excludes */ if( ( hasname( c->invited, who ) || hasname( c->invited, imc_mudof(who) ) ) && !hasname( c->excluded, who ) && !hasname( c->excluded, imc_mudof(who) ) ) return true; else return false; } /* broadcast message - complain if its a private channel */ PFUN( iced_recv_broadcast ) { imc_channel *c; imc_packet *p; map<string,string> keymap = imc_getData( packet ); if( keymap["channel"].find( ':' ) == string::npos ) return; /* If this server isn't the channel owner... well... we'll just have to assume this is a legal move */ if( str_cmp( ice_mudof( keymap["channel"] ), siteinfo->name ) ) { imc_update_laston( q->from, keymap["channel"] ); return; } if( !( c = iced_findchannel( keymap["channel"] ) ) ) { imc_send_tell( q->from, "You're trying to talk on a nonexistant channel." ); iced_send_destroy( keymap["channel"], imc_mudof( q->from ) ); q->stop = true; return; } if( !ice_audible( c, q->from ) ) { imc_send_tell( q->from, "You're trying to talk on a channel that you don't have access to." ); iced_send_destroy( c->name, imc_mudof( q->from ) ); q->stop = true; return; } if( !c->open ) { imc_send_tell( q->from, "Misconfiguration, sending broadcast message on private channel. Try again." ); iced_update( c, imc_mudof( q->from ) ); q->stop = true; return; } imc_update_laston( q->from, keymap["channel"] ); if( keymap["echo"] == "0" ) return; p = new imc_packet( imc_nameof( q->from ) + "-" + imc_mudof( q->from ), "ice-msg-b", "*@" + imc_mudof( q->from ) ); p->data << "text=" << escape_string( keymap["text"] ); p->data << " channel=" << keymap["channel"]; p->data << " sender=" << q->from; p->data << " emote=" << keymap["emote"]; p->forward(); } PFUN( iced_recv_icerefresh ) { list<imc_channel*>::iterator chn; for( chn = chanlist.begin(); chn != chanlist.end(); ++chn ) { imc_channel *c = (*chn); iced_update( c, q->from ); } } PFUN( imc_recv_closenotify ) { imc_reminfo *r; imc_info *i; map<string,string> keymap = imc_getData( packet ); // More or less: Bullshit, I'm not closed you dumbass, I'm right here! But give it about 2 seconds or so. if( !str_cmp( keymap["host"], siteinfo->name ) ) { imc_add_event( 2, ev_keepalive, NULL ); return; } if( !( r = imc_find_reminfo( keymap["host"] ) ) ) return; r->expired = true; if( !( i = imc_getinfo( r->name ) ) ) return; /* Give it one shot at a 30 second reconnect, don't pursue it if this fails though. */ imc_add_event( 30, ev_reconnect, i ); } const char *channel_accesstring[4] = { "none", "creator", "operator", "owner" }; int iced_getaccess( imc_channel *c, const string& from ) { if( !c ) { if( hasname( siteinfo->chancreators, "*" ) || hasname( siteinfo->chancreators, from ) || hasname( siteinfo->chancreators, imc_mudof(from) ) ) return ACCESS_CREATOR; // If you're a serveradmin, you have automatic owner rights now. // No person strictly a channel admin should trump server admin abilities. if( hasname( siteinfo->serveradmins, from ) || hasname( siteinfo->serveradmins, imc_mudof(from) ) ) return ACCESS_CREATOR; return ACCESS_NONE; } // If you're a serveradmin, you have automatic owner rights now. // No person strictly a channel admin should trump server admin abilities. else if( from == c->owner || ( hasname( siteinfo->serveradmins, from ) || hasname( siteinfo->serveradmins, imc_mudof(from) ) ) ) return ACCESS_OWNER; else if( hasname( c->operators, from ) ) return ACCESS_OPERATOR; else return ACCESS_NONE; } /* list commands */ void run_iced_list( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; int i; buf << endl << "~WAvailable Channel Administration Commands:" << endl; buf << "~c" << setw(10) << "Name" << " Access Level" << endl; for( i = 0; i < MAX_ICED_COMMAND; ++i ) { buf << "~C" << setw(10) << iced_cmdtable[i].name << " " << channel_accesstring[iced_cmdtable[i].level] << endl; } buf << "~cYour access level for this channel: ~W" << channel_accesstring[iced_getaccess( c, from )] << endl; imc_send_tell( from, buf.str() ); } /* destroy a channel */ void run_iced_destroy( imc_channel *c, const string& cname, const string& from, const string& data ) { logfile << "Channel named " << c->name << " destroyed by " << from << endl; iced_gannounce( "the channel called %s has been destroyed by %s.", c->name.c_str(), from.c_str() ); /* Moved to up here to fix a bug -- Kratas */ /* send destroy notification */ iced_send_destroy( c->name, "" ); imc_send_tell( from, "The channel has been destroyed successfully." ); /* remove/free the channel from our list */ chanlist.remove( c ); deleteptr( c ); } /* Set default localname */ void run_iced_perm( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; int value; value = get_permvalue( data.c_str() ); if( value < PERM_MORT || value > PERM_IMP ) { buf << data << " is not a valid permission setting."; imc_send_tell( from, buf.str() ); return; } buf << "Default permission level for " << c->name << " has been changed to " << perm_names[value] << "."; imc_send_tell( from, buf.str() ); c->level = perm_names[value]; iced_update( c, "" ); } /* Set default localname */ void run_iced_localname( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; buf << "Default local name for " << c->name << " has been changed to " << data << "."; imc_send_tell( from, buf.str() ); c->lname = data; iced_update( c, "" ); } /* set channel policy */ void run_iced_policy( imc_channel *c, const string& cname, const string& from, const string& data ) { if( data == "open" ) { if( c->open ) { imc_send_tell( from, "This channel already has an open policy." ); return; } c->open = true; iced_gannounce( "The channel called %s now has an open policy.", c->name.c_str() ); iced_update( c, "" ); } else if( data == "private" ) { if( !c->open ) { imc_send_tell( from, "This channel already has a private policy." ); return; } c->open = false; iced_gannounce( "The channel called %s now has a private policy.", c->name.c_str() ); iced_update( c, "" ); } else imc_send_tell( from, "The currently available channel policies are open and private." ); } /* add operator */ void run_iced_addop( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; if( data.find( '@' ) == string::npos ) { imc_send_tell( from, "The name of the operator must be in the format of person@mud." ); return; } if( hasname( c->operators, data ) ) { imc_send_tell( from, "That person is already an operator." ); return; } addname( c->operators, data ); fixactive( c ); buf << data << " is now an operator of " << c->name << "."; imc_send_tell( from, buf.str() ); iced_announce( c, "%s is now an operator for this channel.", data.c_str() ); iced_update( c, "" ); } /* remove operator */ void run_iced_removeop( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; if( data.find( '@' ) == string::npos ) { imc_send_tell( from, "The name of the operator must be in the format of person@mud." ); return; } if( !hasname( c->operators, data ) ) { imc_send_tell( from, "That person is not an operator." ); return; } removename( c->operators, data ); fixactive( c ); buf << data << " is no longer an operator of " << c->name << "."; imc_send_tell( from, buf.str() ); iced_announce( c, "%s is no longer an operator for this channel.", data.c_str() ); iced_update( c, "" ); } /* invite mud or player */ void run_iced_invite( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; if( hasname( c->invited, data ) ) { imc_send_tell( from, "That person is already on the invite list for that channel." ); return; } addname( c->invited, data ); fixactive( c ); buf << data << " has now been invited to " << c->name << "."; imc_send_tell( from, buf.str() ); iced_announce( c, "%s has invited %s to this channel.", from.c_str(), data.c_str() ); iced_update( c, "" ); } /* uninvite mud or player */ void run_iced_uninvite( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; if( !hasname( c->invited, data ) ) { imc_send_tell( from, "That person is not currently on the invite list for that channel." ); return; } removename( c->invited, data ); fixactive( c ); buf << data << " is no longer invited on " << c->name << "."; imc_send_tell( from, buf.str() ); iced_announce( c, "%s has uninvited %s from this channel.", from.c_str(), data.c_str() ); iced_update( c, "" ); } /* exclude mud or player */ void run_iced_exclude( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; if( hasname( c->excluded, data ) ) { imc_send_tell( from, "That person is already on the exclude list for that channel." ); return; } addname( c->excluded, data ); buf << data << " is now excluded from " << c->name << "."; imc_send_tell( from, buf.str() ); iced_announce( c, "%s has excluded %s from this channel.", from.c_str(), data.c_str() ); iced_update( c, "" ); } /* unexclude mud or player */ void run_iced_unexclude( imc_channel *c, const string& cname, const string& from, const string& data ) { ostringstream buf; if( !hasname( c->excluded, data ) ) { imc_send_tell( from, "That person is not on the exclude list for that channel." ); return; } removename( c->excluded, data ); buf << data << " is no longer excluded from " << c->name << "."; imc_send_tell( from, buf.str() ); iced_announce( c, "%s has unexcluded %s from this channel.", from.c_str(), data.c_str() ); iced_update( c, "" ); } /* create a channel */ void run_iced_create( imc_channel *c, const string& cname, const string& from, const string& data ) { imc_channel *p; string lname; if( cname.empty() || cname.find( ':' ) == string::npos || str_cmp( ice_mudof(cname), siteinfo->name ) ) { imc_send_tell( from, "You must supply a new channel name in the format of servername:channame" ); return; } string::size_type x = cname.find_first_of( ':' ); lname = cname.substr( x+1, cname.length() ); p = new imc_channel; p->name = cname; p->owner = from; p->operators.clear(); p->invited.clear(); p->excluded.clear(); p->active.clear(); p->level = "Admin"; p->lname = lname; p->open = false; /* Channels should be private in the beginning */ chanlist.push_back( p ); iced_update( p, "" ); logfile << "Channel named " << p->name << " created by " << from << " with localname " << lname << endl; iced_gannounce( "a channel named %s has been created by %s.", p->name.c_str(), from.c_str() ); imc_send_tell( from, "The channel has been created successfully." ); } PFUN( iced_recv_command ) { imc_channel *c; map<string,string> keymap = imc_getData( packet ); int i; bool foundCmd = false; for( i = 0; i < MAX_ICED_COMMAND; ++i ) if( !str_cmp( iced_cmdtable[i].name, keymap["command"] ) ) { foundCmd = true; break; } c = iced_findchannel( keymap["channel"] ); if( !foundCmd ) { run_iced_list( c, keymap["channel"], q->from, keymap["data"] ); return; } if( !c && iced_cmdtable[i].level > ACCESS_CREATOR ) { imc_send_tell( q->from, "You need to input a valid channel name to use this command." ); return; } if( c && iced_cmdtable[i].level == ACCESS_CREATOR ) { imc_send_tell( q->from, "You must make up a new channel for this command." ); return; } if( iced_getaccess( c, q->from ) < iced_cmdtable[i].level ) { if( iced_cmdtable[i].level > ACCESS_CREATOR ) imc_send_tell( q->from, "You do not have the access level to use this command with that channel." ); else if( iced_cmdtable[i].level == ACCESS_CREATOR ) imc_send_tell( q->from, "You do not have the access level to create a new channel." ); return; } if( keymap["data"].empty() && iced_cmdtable[i].need_data ) { imc_send_tell( q->from, "This command requires additional data." ); return; } logfile << "Channel command issued by " << q->from << ". Command: " << keymap["command"] << " on channel " << keymap["channel"] << " " << keymap["data"] << endl; (*iced_cmdtable[i].cmdfn)( c, keymap["channel"], q->from, keymap["data"] ); iced_save_channels(); } const struct iced_cmd_table iced_cmdtable[MAX_ICED_COMMAND] = { { "list", ACCESS_NONE, run_iced_list, false }, { "create", ACCESS_CREATOR, run_iced_create, false }, { "destroy", ACCESS_OWNER, run_iced_destroy, false }, { "invite", ACCESS_OPERATOR, run_iced_invite, true }, { "uninvite", ACCESS_OPERATOR, run_iced_uninvite, true }, { "exclude", ACCESS_OPERATOR, run_iced_exclude, true }, { "unexclude", ACCESS_OPERATOR, run_iced_unexclude, true }, { "policy", ACCESS_OWNER, run_iced_policy, true }, { "addop", ACCESS_OWNER, run_iced_addop, true }, { "removeop", ACCESS_OWNER, run_iced_removeop, true }, { "localname", ACCESS_OWNER, run_iced_localname, true }, { "perm", ACCESS_OWNER, run_iced_perm, true } }; /* private message - for forwarding */ PFUN( iced_recv_msg_p ) { imc_channel *c; imc_packet *p; map<string,string> keymap = imc_getData( packet ); c = iced_findchannel( keymap["channel"] ); if( !c ) { imc_send_tell( q->from, "You're trying to talk on a nonexistant channel." ); iced_send_destroy( keymap["channel"], imc_mudof( q->from ) ); q->stop = true; return; } if( !ice_audible( c, q->from ) ) { imc_send_tell( q->from, "You're trying to talk on a channel that you don't have access to." ); iced_send_destroy( c->name, imc_mudof( q->from ) ); q->stop = true; return; } if( c->open ) { imc_send_tell( q->from, "Misconfiguration, sending private message on nonprivate channel. Try again." ); iced_update( c, imc_mudof( q->from ) ); q->stop = true; return; } p = new imc_packet( "ICE", "ice-msg-r", imc_mudof( q->from ) ); p->data << "realfrom=" << q->from; p->data << " channel=" << keymap["channel"]; p->data << " text=" << escape_string( keymap["text"] ); p->data << " emote=" << keymap["emote"]; if( keymap["echo"] == "1" ) iced_privmsg( c, p, "" ); else iced_privmsg( c, p, imc_mudof( q->from ) ); } void imc_set_maxconnect( const string& from, const string& data ) { ostringstream buf; if( !str_cmp( data, "show" ) ) { buf << "The server currently allows up to " << siteinfo->maxconnects << " connections."; imc_send_tell( from, buf.str() ); return; } if( !is_number( data ) ) { imc_send_tell( from, "Maxconnections must be a numeric value." ); return; } siteinfo->maxconnects = atoi( data.c_str() ); buf << "~cMaximum allowed connections has been changed to: ~W" << siteinfo->maxconnects; imc_send_tell( from, buf.str() ); imc_saveconfig(); } void imc_set_infourl( const string& from, const string& data ) { ostringstream buf; if( !str_cmp( data, "show" ) ) { if( siteinfo->url.empty() ) buf << "There is no server URL defined."; else buf << "~cCurrent server URL: ~W" << siteinfo->url; imc_send_tell( from, buf.str() ); return; } siteinfo->url = data; buf << "~cServer URL has been changed to: ~W" << siteinfo->url; imc_send_tell( from, buf.str() ); imc_saveconfig(); imc_send_tell( from, "Issuing is-alive packet to update network listings." ); imc_send_keepalive( NULL, "*@*" ); } void imc_set_infohost( const string& from, const string& data ) { ostringstream buf; if( !str_cmp( data, "show" ) ) { if( siteinfo->host.empty() ) buf << "There is no network URL defined."; else buf << "~cCurrent network URL: ~W" << siteinfo->host; imc_send_tell( from, buf.str() ); return; } siteinfo->host = data; buf << "~cNetwork URL has been changed to: ~W" << siteinfo->host; imc_send_tell( from, buf.str() ); imc_saveconfig(); } void imc_set_webfile( const string& from, const string& data ) { ostringstream buf; if( !str_cmp( data, "show" ) ) { if( siteinfo->statsfile.empty() ) buf << "There is no webstats file path defined."; else buf << "~cCurrent webstats file path: ~W" << siteinfo->statsfile; imc_send_tell( from, buf.str() ); return; } siteinfo->statsfile = data; buf << "~cWebstats file path has been changed to: ~W" << siteinfo->statsfile; imc_send_tell( from, buf.str() ); imc_saveconfig(); } void imc_toggle_webstats( const string& from, const string& data ) { siteinfo->webstats = !siteinfo->webstats; if( siteinfo->webstats ) { if( siteinfo->statsfile.empty() ) { siteinfo->webstats = false; imc_send_tell( from, "There is no webstats file path defined. Cannot enable statdump." ); return; } imc_send_tell( from, "Webstats information will now be updated every 15 seconds." ); imc_add_event( 1, ev_statdump, NULL ); } else { imc_send_tell( from, "Webstats information will no longer be updated." ); imc_cancel_event( ev_statdump, NULL ); } imc_saveconfig(); } void imc_toggle_autoupdate( const string& from, const string& data ) { siteinfo->autoupdate = !siteinfo->autoupdate; if( siteinfo->autoupdate ) { imc_send_tell( from, "Autoupdate file will now be checked every 15 seconds." ); imc_add_event( 15, ev_mudconnections, NULL ); } else { imc_send_tell( from, "Autoupdate file will no longer be checked." ); imc_cancel_event( ev_mudconnections, NULL ); } imc_saveconfig(); } void imc_toggle_compression( const string& from, const string& data ) { siteinfo->can_compress = !siteinfo->can_compress; if( siteinfo->can_compress ) imc_send_tell( from, "Packet compression is now enabled." ); else imc_send_tell( from, "Packet compression is now disabled." ); imc_saveconfig(); } void imc_toggle_prune( const string& from, const string& data ) { siteinfo->prune = !siteinfo->prune; if( siteinfo->prune ) { imc_send_tell( from, "Outdated connections will now be self-pruned every 14 days." ); imc_add_event( 900, ev_purgeinfo, NULL ); } else { imc_send_tell( from, "Outdated connections will no longer be self-pruned." ); imc_cancel_event( ev_purgeinfo, NULL ); } imc_saveconfig(); } void imc_toggle_autosetup( const string& from, const string& data ) { siteinfo->priv = !siteinfo->priv; if( siteinfo->priv ) imc_send_tell( from, "Autosetup connections will no longer be accepted." ); else imc_send_tell( from, "Autosetup connections will now be allowed." ); imc_saveconfig(); } void imc_toggle_debug( const string& from, const string& data ) { imcpacketdebug = !imcpacketdebug; if( imcpacketdebug ) imc_send_tell( from, "~cPacket debugging has been `WENABLED~!~c. Don't leave this on for too long." ); else imc_send_tell( from, "Packet debugging has been disabled." ); } void imc_disable_remote( const string& from, const string& data ) { siteinfo->remoteadmin = false; imc_send_tell( from, "Remote administration functions have been disabled." ); imc_saveconfig(); } void imc_ban_ip( const string& from, const string& data ) { ostringstream buf; list<string>::iterator ban; if( !str_cmp( data, "list" ) || !str_cmp( data, "show" ) ) { buf << endl << "~cThe following IPs are banned:" << endl; buf << "~C-----------------------------" << endl << endl; for( ban = banlist.begin(); ban != banlist.end(); ++ban ) { string b = *ban; buf << "~W" << b << endl; } imc_send_tell( from, buf.str() ); return; } for( ban = banlist.begin(); ban != banlist.end(); ++ban ) { string b = *ban; if( !str_cmp( b, data ) ) { buf << "~cIP ~W" << data << " ~chas been unbanned."; banlist.remove( data ); imc_savebans(); imc_send_tell( from, buf.str() ); return; } } banlist.push_back( data ); imc_savebans(); list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ) { connection *c = *conn; ++conn; if( c->host == data ) { if( c->info ) imc_delete_info( c->info ); else do_close( c ); } } buf << "~cIP ~W" << data << " ~chas been banned."; imc_send_tell( from, buf.str() ); } void imc_destroy_info( const string& from, const string& data ) { imc_info *i; ostringstream buf; if( !( i = imc_getinfo( data ) ) ) { buf << "~cThere is no connection named ~W" << data << " ~cregistered here."; imc_send_tell( from, buf.str() ); return; } buf << "~W" << i->name << " ~chas been removed from listings."; imc_send_tell( from, buf.str() ); send_reminfo_destroy( i->name ); imc_delete_info( i ); imc_saveconfig(); } void imc_discon_mud( const string& from, const string& data ) { imc_info *i; ostringstream buf; if( !( i = imc_getinfo( data ) ) ) { buf << "~cThere is no connection named ~W" << data << " ~cregistered here."; imc_send_tell( from, buf.str() ); return; } if( !i->conn ) { buf << "~W" << i->name << " ~cis not currently connected."; imc_send_tell( from, buf.str() ); return; } buf << "~W" << i->name << " ~chas been disconnected."; imc_send_tell( from, buf.str() ); do_close( i->conn ); } void imc_list_remcommands( const string& from, const string& data ) { ostringstream buf; int i; buf << endl << "~cAvailable remote commands:" << endl; buf << "~C--------------------------" << endl << endl; for( i = 0; i < MAX_REMOTE_COMMAND; ++i ) buf << "~W" << imc_remote_cmdtable[i].name << endl; imc_send_tell( from, buf.str() ); } void imc_serverreboot( const string& from, const string& data ) { imc_send_tell( from, "The server is rebooting." ); logfile << "Reboot called by remote admin command." << endl; imc_shutdown(); } void imc_serverhotboot( const string& from, const string& data ) { imc_send_tell( from, "The server is executing a hotboot." ); logfile << "Hotboot called via remote amdin command." << endl; imc_hotboot(); } void imc_servershutdown( const string& from, const string& data ) { ofstream fp; imc_send_tell( from, "The server is shutting down and will not reboot." ); logfile << "Shutdown called by remote admin command." << endl; fp.open( "shutdown" ); if( !fp.is_open() ) { logfile << "Unable to drop shutdown file! Server will reboot instead." << endl; imc_send_tell( from, "Unable to drop shutdown file! Server will reboot instead." ); imc_shutdown(); } fp << "SHUTDOWN" << endl; fp.close(); imc_shutdown(); } void imc_serveradmins( const string& from, const string& data ) { ostringstream buf; if( data.find( '@' ) == string::npos ) { imc_send_tell( from, "Server administrators must be specified in the form of user@mud." ); return; } if( hasname( siteinfo->serveradmins, data ) ) { removename( siteinfo->serveradmins, data ); buf << "~W" << data << " ~chas been removed as a server administrator."; imc_send_tell( from, buf.str() ); } else { addname( siteinfo->serveradmins, data ); buf << "~W" << data << " ~chas been added as a server administrator."; imc_send_tell( from, buf.str() ); } imc_saveconfig(); } void imc_chancreators( const string& from, const string& data ) { ostringstream buf; if( data.find( '@' ) == string::npos ) { imc_send_tell( from, "Channel creators must be specified in the form of user@mud." ); return; } if( hasname( siteinfo->chancreators, data ) ) { removename( siteinfo->chancreators, data ); buf << "~W" << data << " ~chas been removed as a channel creator."; imc_send_tell( from, buf.str() ); } else { addname( siteinfo->chancreators, data ); buf << "~W" << data << " ~chas been added as a channel creator."; imc_send_tell( from, buf.str() ); } imc_saveconfig(); } void imc_set_netname( const string& from, const string& data ) { ostringstream buf; if( !str_cmp( data, "show" ) ) { if( siteinfo->network.empty() ) buf << "There is no network name defined."; else buf << "~cCurrent network name: ~W" << siteinfo->network; imc_send_tell( from, buf.str() ); return; } siteinfo->network = data; buf << "~cNetwork name has been changed to: ~W" << siteinfo->network; imc_send_tell( from, buf.str() ); imc_saveconfig(); imc_send_tell( from, "Issuing is-alive packet to update network listings." ); imc_send_keepalive( NULL, "*@*" ); } void imc_show_serverconfig( const string& from, const string& data ) { ostringstream buf; buf << endl << "~W" << siteinfo->name << " ~chas the following configuration:" << endl; buf << "~C-------------------------------------" << endl << endl; buf << "~cNetname: ~W" << siteinfo->network << endl; buf << "~cMax Connections: ~W" << siteinfo->maxconnects << endl; buf << "~cChannel Creators: ~W" << siteinfo->chancreators << endl; buf << "~cServer Administrators: ~W" << siteinfo->serveradmins << endl; buf << "~cNetwork URL: ~W" << siteinfo->host << endl; buf << "~cServer URL: ~W" << siteinfo->url << endl; buf << "~cAutosetup: ~W" << ( siteinfo->priv ? "Disabled" : "Enabled" ) << endl; buf << "~cAutoupdate File: ~W" << ( siteinfo->autoupdate ? "Enabled" : "Disabled" ) << endl; buf << "~cInfo Pruning: ~W" << ( siteinfo->prune ? "Enabled" : "Disabled" ) << endl; buf << "~cWebstats Dump: ~W" << ( siteinfo->webstats ? "Enabled" : "Disabled" ) << endl; buf << "~cWebstats File: ~W" << siteinfo->statsfile << endl; buf << "~cPacket Compression: ~W" << ( siteinfo->can_compress ? "Enabled" : "Disabled" ) << endl; buf << "~cPacket Debugging: " << ( imcpacketdebug ? "`WENABLED!" : "~WDisabled" ) << endl; imc_send_tell( from, buf.str() ); } /* The remote administration handler */ PFUN( imc_remote_admin ) { imc_reminfo *r; map<string,string> keymap = imc_getData( packet ); ostringstream buf; char pwd[IMC_DATA_LENGTH]; char *cryptpwd; int i; bool foundCmd = false; if( !( r = imc_find_reminfo( imc_mudof( q->from ) ) ) ) { logfile << "Invalid source mud sending remote admin command: " << q->from << endl; return; } if( !r->sha256 ) /* Let them eat static - don't return a tell */ { logfile << r->name << " does not support SHA256 - rejecting remote admin command from " << q->from << endl; return; } if( !siteinfo->remoteadmin ) { logfile << "Remote admin command from " << q->from << " with remote admin disabled." << endl; imc_send_tell( q->from, "Remote admin commands are not enabled here." ); return; } if( !hasname( siteinfo->serveradmins, q->from ) ) { logfile << "Non permitted user attempting to issue remote admin command: " << q->from << endl; imc_send_tell( q->from, "You do not have permission to use remote commands here." ); return; } if( keymap["hash"].empty() ) { logfile << "Required password for remote admin not provided by " << q->from << endl; imc_send_tell( q->from, "Required password was not provided." ); return; } snprintf( pwd, IMC_DATA_LENGTH, "%ld%s", q->i.sequence, siteinfo->serveradminpwd.c_str() ); cryptpwd = sha256_crypt( pwd ); if( !STR_CEQL( cryptpwd, keymap["hash"].c_str() ) ) { logfile << "Invalid remote admin password from " << q->from << endl; logfile << "Hash received: " << q->i.sequence << ", " << keymap["hash"] << endl; imc_send_tell( q->from, "Invalid password." ); return; } if( keymap["command"].empty() ) { imc_send_tell( q->from, "You must specify some sort of command." ); return; } for( i = 0; i < MAX_REMOTE_COMMAND; ++i ) if( !str_cmp( imc_remote_cmdtable[i].name, keymap["command"] ) ) { foundCmd = true; break; } if( !foundCmd ) { buf << keymap["command"] << " ~cis not a valid remote command."; imc_send_tell( q->from, buf.str() ); imc_list_remcommands( q->from, keymap["data"] ); return; } if( keymap["data"].empty() && imc_remote_cmdtable[i].need_data ) { imc_send_tell( q->from, "This command requires additional data." ); return; } logfile << "Remote command issued by " << q->from << ". Command: " << keymap["command"] << " " << keymap["data"] << endl; (*imc_remote_cmdtable[i].cmdfn)( q->from, keymap["data"] ); } const struct imc_rcmd_table imc_remote_cmdtable[MAX_REMOTE_COMMAND] = { { "list", imc_list_remcommands, false }, { "reboot", imc_serverreboot, false }, { "hotboot", imc_serverhotboot, false }, { "shutdown", imc_servershutdown, false }, { "disconnect", imc_discon_mud, true }, { "destroy", imc_destroy_info, true }, { "ban", imc_ban_ip, true }, { "remoteadmin", imc_disable_remote, false }, { "autosetup", imc_toggle_autosetup, false }, { "prune", imc_toggle_prune, false }, { "autoupdate", imc_toggle_autoupdate, false }, { "webstats", imc_toggle_webstats, false }, { "debug", imc_toggle_debug, false }, { "webstatsfile", imc_set_webfile, true }, { "networkurl", imc_set_infohost, true }, { "serverurl", imc_set_infourl, true }, { "maxconnect", imc_set_maxconnect, true }, { "chancreator", imc_chancreators, true }, { "serveradmin", imc_serveradmins, true }, { "netname", imc_set_netname, true }, { "compression", imc_toggle_compression, false }, { "config", imc_show_serverconfig, false } }; void register_packet_handler( const string& name, PACKET_FUN *func ) { packet_handler *ph; list<packet_handler*>::iterator phn; for( phn = phanlderlist.begin(); phn != phanlderlist.end(); ++phn ) { ph = *phn; if( ph->name == name ) // No str_cmp here, we want an exact name match { logfile << "Unable to register packet type " << name << ". Another module has already registered it." << endl; return; } } ph = new packet_handler; ph->name = name; ph->func = func; phanlderlist.push_back( ph ); } void imc_register_default_packets( void ) { register_packet_handler( "who", imc_recv_who ); register_packet_handler( "wHo", imc_recv_who ); register_packet_handler( "is-alive", imc_recv_isalive ); register_packet_handler( "keepalive-request", imc_send_keepalive ); register_packet_handler( "reminfo-destroy", imc_recv_reminfo_destroy ); register_packet_handler( "close-notify", imc_recv_closenotify ); register_packet_handler( "imc-laston", imc_recv_laston ); register_packet_handler( "ice-cmd", iced_recv_command ); register_packet_handler( "ice-msg-p", iced_recv_msg_p ); register_packet_handler( "ice-msg-b", iced_recv_broadcast ); register_packet_handler( "ice-refresh", iced_recv_icerefresh ); register_packet_handler( "remote-admin", imc_remote_admin ); } PACKET_FUN *pfun_lookup( const string& type ) { list<packet_handler*>::iterator phn; for( phn = phanlderlist.begin(); phn != phanlderlist.end(); ++phn ) { packet_handler *ph = *phn; if( type == ph->name ) // No str_cmp here, we need an exact match return ph->func; } return NULL; } /* handle a packet destined for us, or a broadcast */ void imc_recv( imc_packet *p ) { PACKET_FUN *pfun; if( imcpacketdebug ) logfile << "ALTERED Packet received: " << p->from << " " << p->i.sequence << " " << p->i.path << " " << p->type << " " << p->to << " " << p->data.str() << endl; pfun = pfun_lookup( p->type ); if( !pfun ) { logfile << "No packet handler function has been defined for " << p->type << endl; return; } try { (*pfun)( p, p->data.str() ); } catch( exception& e ) { logfile << "EXCEPTION GENERATED: " << e.what() << ". Caused by packet: " << p->from << " " << p->i.sequence << " " << p->i.path << " " << p->type << " " << p->to << " " << p->data.str() << endl; } catch(...) { logfile << "UNKNOWN EXCEPTION: Generated by packet: " << p->from << " " << p->i.sequence << " " << p->i.path << " " << p->type << " " << p->to << " " << p->data.str() << endl; } } imc_packet *disassemble_packet( string& packet ) { imc_packet *p; string from, sequence, path, type, to, data; try { packet = one_argument( packet, from ); packet = one_argument( packet, sequence ); packet = one_argument( packet, path ); packet = one_argument( packet, type ); packet = one_argument( packet, to ); if( from.empty() || sequence.empty() || path.empty() || type.empty() || to.empty() ) { logfile << __FUNCTION__ << ": Invalid packet format!" << endl; return NULL; } } catch( exception& e ) { logfile << "EXCEPTION GENERATED: " << e.what() << " by packet!" << endl; return NULL; } catch(...) { logfile << "UNKNOWN EXCEPTION: Generated by packet!" << endl; return NULL; } if( imcpacketdebug ) logfile << "PACKET BREAKDOWN: From=" << from << " Seq=" << sequence << " Path=" << path << " Type=" << type << " To=" << to << " Data=" << packet << endl; p = new imc_packet; p->i.from = from; p->i.sequence = atol( sequence.c_str() ); p->i.path = path; p->type = type; p->i.to = to; if( !packet.empty() ) p->data << packet; else p->data.clear(); return p; } /* low-level idle function: read/write buffers as needed, etc */ void imc_idle_select( fd_set *iread, fd_set *iwrite, fd_set *exc, time_t now ) { const char *command; if( imc_sequencenumber < (unsigned long)imc_now ) imc_sequencenumber = (unsigned long)imc_now; imc_run_events( now ); /* handle results of the select */ if( FD_ISSET( control, iread ) ) do_accept(); list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ) { connection *c = *conn; ++conn; if( c->state != CONN_NONE && FD_ISSET( c->desc, exc ) ) do_close( c ); if( c->state != CONN_NONE && FD_ISSET( c->desc, iread ) ) do_read( c ); while( c->state != CONN_NONE && ( command = imc_getline( c->inbuf, c->insize ) ) != NULL ) { string packet = command; if( packet.length() > imc_stats.max_pkt ) imc_stats.max_pkt = packet.length(); if( imcpacketdebug ) logfile << "UNPROCESSED PACKET RECEIVED: " << command << endl; switch( c->state ) { default: break; case CONN_WAITCLIENTPWD: clientpassword( c, packet ); break; case CONN_WAITSERVERPWD: serverpassword( c, packet ); break; case CONN_COMPLETE: imc_packet *p = disassemble_packet( packet ); if( p ) { ++imc_stats.rx_pkts; /* Direct connection is not a server. * Check for ! in path and verify source is where it claims to be from. * Also inserts network name into is-alives from clients who don't send one. * Place any server sensative packets to check in here. * Muds should never have any reason to use them. * Samson 2-13-04 */ if( !c->info->server ) { if( p->i.path.find( '!' ) != string::npos ) logfile << "ALERT: Packet from " << c->info->name << " forged to look like it was from " << imc_mudof( p->i.from ) << ". Possible spoof attempt." << endl; else if( imc_mudof( p->i.from ) != c->info->name ) logfile << "ALERT: Packet from " << c->info->name << " forged to look like it was from " << imc_mudof( p->i.from ) << endl; /* Packets only a server should send go here */ else if( p->type == "reminfo-destroy" ) logfile << "ALERT: " << c->info->name << " attempted to send reminfo-destroy packet." << endl; else if( p->type == "close-notify" ) logfile << "ALERT: " << c->info->name << " attempted to send close-notify packet." << endl; else if( p->type == "ice-update" ) logfile << "ALERT: " << c->info->name << " attempted to send ice-update packet." << endl; /* Go ahead and forward it now */ else { /* Oh, we're really going to town on this shit now */ if( p->type == "is-alive" ) { ostringstream Nsha256; string::size_type x = p->data.str().find( "sha256=" ); if( x != string::npos ) p->data.str() = p->data.str().erase( x, 5 ); Nsha256 << c->info->sha256; p->data << " sha256=" << Nsha256.str(); if( p->data.str().find( "networkname" ) == string::npos ) p->data << " networkname=" << siteinfo->network; } if( p->type == "ice-refresh" ) { /* Modify incoming ice-refresh packets to use multicast if they're still using broadcast */ if( imc_mudof( p->i.to ) == "*" ) p->i.to = "IMC@$"; } p->forward(); } } else { /* Check the last entry in the path is the same as the sending mud. * Also check the first entry to see that it matches the sender. */ if( c->info->name != imc_lastinpath( p->i.path ) ) logfile << "ALERT: Packet passed through " << c->info->name << " forged to look like it was passed through " << imc_lastinpath( p->i.path ) << endl; else if( imc_mudof( p->i.from ) != imc_firstinpath( p->i.path ) ) logfile << "ALERT: Packet originally from " << p->i.from << " forged to look like it was originally from " << imc_firstinpath( p->i.path ) << endl; else p->forward(); /* only forward if its a valid packet! */ } } break; } } } for( conn = connlist.begin(); conn != connlist.end(); ) { connection *c = (*conn); ++conn; if( c->state != CONN_NONE && ( FD_ISSET( c->desc, iwrite ) || c->newoutput ) ) { do_write( c ); if( c->outbuf[0] != '\0' ) c->newoutput = true; else c->newoutput = false; if( c->disconnect ) do_close( c ); } } for( conn = connlist.begin(); conn != connlist.end(); ) { connection *c = (*conn); ++conn; if( c->state == CONN_NONE ) imc_extract_connect( c ); } } int imc_fill_fdsets( int maxfd, fd_set *iread, fd_set *iwrite, fd_set *exc ) { /* set up fd_sets for select */ if( maxfd < control ) maxfd = control; FD_SET( control, iread ); list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ++conn ) { connection *c = (*conn); if( maxfd < c->desc ) maxfd = c->desc; if( c->state == CONN_NONE ) continue; if( c->state == CONN_SERVERCONNECT ) { FD_SET( c->desc, iwrite ); continue; } FD_SET( c->desc, iread ); if( c->outbuf[0] ) FD_SET( c->desc, iwrite ); } return maxfd; } /* shell around imc_idle_select */ void imc_idle( int s ) { fd_set iread, iwrite, exc; int maxfd; struct timeval timeout; int i; FD_ZERO( &iread ); FD_ZERO( &iwrite ); FD_ZERO( &exc ); maxfd = imc_fill_fdsets( 0, &iread, &iwrite, &exc ); timeout.tv_sec = s; timeout.tv_usec = 0; if( maxfd ) while( ( i = select( maxfd+1, &iread, &iwrite, &exc, &timeout ) ) < 0 && errno == EINTR ) ; else while( ( i = select( 0, NULL, NULL, NULL, &timeout ) ) < 0 && errno == EINTR ) ; if( i < 0 ) { logfile << __FUNCTION__ << ": select stalled" << endl; imc_shutdown(); return; } imc_idle_select( &iread, &iwrite, &exc, time( NULL ) ); } void server_loop( struct timeval last_time ) { struct timeval now_time; long secDelta; long usecDelta; imc_idle( imc_get_event_timeout() ); /* * Synchronize to a clock - make sure that it takes at least a second plus a 1/4 second * delay time to go through the loop. Keeps sequence numbers from increasing too fast. * Sleep( last_time + 1/PULSE_PER_SECOND - now ). * Careful here of signed versus unsigned arithmetic. */ gettimeofday( &now_time, NULL ); usecDelta = ((int) last_time.tv_usec) - ((int) now_time.tv_usec) + 1000000 / 4; secDelta = ((int) last_time.tv_sec ) - ((int) now_time.tv_sec ); while( usecDelta < 0 ) { usecDelta += 1000000; secDelta -= 1; } while( usecDelta >= 1000000 ) { usecDelta -= 1000000; secDelta += 1; } if( secDelta >= 0 && usecDelta >= 0 ) { struct timeval stall_time; stall_time.tv_usec = usecDelta; stall_time.tv_sec = secDelta; if( select( 0, NULL, NULL, NULL, &stall_time ) < 0 && errno != EINTR ) { perror( "server_loop: select: stall" ); exit( 1 ); } } gettimeofday( &last_time, NULL ); server_loop( last_time ); return; } /******************** * Event Management * ********************/ /* time out a login */ void ev_login_timeout( const void *data ) { connection *c = (connection *)data; logfile << imc_getconnectname(c) << ": login timeout" << endl; if( c->info && c->info->port > 0 ) imc_add_event( 60, ev_reconnect, c->info ); do_close( c ); } /* make it an event, dont announce a simple reboot - shogar - 2/1/2000 */ void ev_close_notify( const void *data ) { const char *host = (const char *)data; imc_cancel_event( ev_close_notify, host ); imc_close_notify( host ); } /* try a reconnect to the given imc_info */ void ev_reconnect( const void *data ) { imc_info *info = (imc_info *)data; if( !info->conn && info->port > 0 ) imc_connect_to( info->name ); } /* generate a channel listing */ void ev_iced_refresh( const void *data ) { list<imc_channel*>::iterator chn; for( chn = chanlist.begin(); chn != chanlist.end(); ++chn ) { imc_channel *c = *chn; iced_update( c, "" ); } } void ev_keepalive( const void *data ) { imc_send_keepalive( NULL, "*@*" ); } /* Check all type 1 connections to see if they are up. * If not, try to connect. Will trigger every 30 minutes. */ void ev_autoconnects( const void *data ) { list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( i->port > 0 && i->conn == NULL ) imc_connect_to( i->name ); } imc_add_event( 900, ev_autoconnects, NULL ); } /* Dump our stats into a file that a webscript can use -- Xorith */ void imc_statdump( ) { ofstream stream; string netname; char urldump[LSS]; int evcount = 0; imc_event *ev = imc_event_list; long secondspast; if( !siteinfo->webstats ) return; stream.open( siteinfo->statsfile.c_str() ); if( !stream.is_open() ) { logfile << "Error opening stat dump file: " << siteinfo->statsfile << endl; return; } while( ev ) { ++evcount; ev = ev->next; } if( ( imc_now - imc_stats.start ) > 0 ) secondspast = imc_now - imc_stats.start; else secondspast = 1; stream << "RXPKTS " << imc_stats.rx_pkts << endl; stream << "RXBYTES " << imc_stats.rx_bytes << endl; stream << "RXBPS " << ( imc_stats.rx_bytes / secondspast ) << endl; stream << "TXPKTS " << imc_stats.tx_pkts << endl; stream << "TXBYTES " << imc_stats.tx_bytes << endl; stream << "TXBPS " << ( imc_stats.tx_bytes / secondspast ) << endl; if( siteinfo->can_compress ) { stream << "TXCMPBYTES " << imc_stats.tx_cmpbytes << endl; stream << "RXCMPBYTES " << imc_stats.rx_cmpbytes << endl; stream << "RXBYTESSAVED " << imc_stats.rx_bytessaved << endl; stream << "TXBYTESSAVED " << imc_stats.tx_bytessaved << endl; } stream << "MAXPKTSZ " << imc_stats.max_pkt << endl; stream << "EVENTS " << evcount << endl; stream << "SEQDROPS " << imc_stats.sequence_drops << endl; stream << "IMCBOOT " << imc_boot << endl; stream << "LASTDUMP " << imc_now << endl; stream << "IMCUPFOR " << ( imc_now - imc_boot ) << endl; stream << "NAME " << siteinfo->name << endl; stream << "VERS " << IMC_VERSIONID << endl; stream << "ADDR " << siteinfo->host << endl; stream << "NETWK " << siteinfo->network << endl; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( i->url.empty() || i->url == "??" || i->url == "Unknown" || i->url.find( "http://" ) == string::npos ) strlcpy( urldump, "", LSS ); else strlcpy( urldump, i->url.c_str(), LSS ); stream << "CONN " << i->name << " " << ( i->conn ? "1" : "0" ) << " " << ( i->server ? "1" : "0" ) << " " << urldump << endl; } stream.close(); stream.open( IMC_DUMP_FILE ); if( !stream.is_open() ) { logfile << "Error opening info dump file: " << IMC_DUMP_FILE << endl; return; } if( siteinfo->url.empty() || siteinfo->url == "??" || siteinfo->url == "Unknown" || siteinfo->url.find( "http://" ) == string::npos ) snprintf( urldump, LSS, "~c%-30.30s~!", siteinfo->name.c_str() ); else { char nspaces[30]; unsigned int x, namelen; namelen = 30 - siteinfo->name.length(); for( x = 0; x < namelen; ++x ) nspaces[x] = ' '; nspaces[x] = '\0'; snprintf( urldump, LSS, "~c<a href=\"%s\">%s</a>~!%s", siteinfo->url.c_str(), siteinfo->name.c_str(), nspaces ); } stream << urldump << " ~B" << setw(50) << IMC_VERSIONID << "~! ~g" << setw(15) << siteinfo->network << "~! ~G" << siteinfo->name << "~!" << endl; list<imc_reminfo*>::iterator rin; for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin ) { imc_reminfo *r = *rin; if( r->expired ) continue; if( r->url.empty() || r->url == "??" || r->url =="Unknown" || r->url.find( "http://" ) == string::npos ) snprintf( urldump, LSS, "~c%-30.30s~!", r->name.c_str() ); else { char nspaces[30]; int x, namelen; namelen = 30 - r->name.length(); for( x = 0; x < namelen; ++x ) nspaces[x] = ' '; nspaces[x] = '\0'; snprintf( urldump, LSS, "~c<a href=\"%s\">%s</a>~!%s", r->url.c_str(), r->name.c_str(), nspaces ); } if( !str_cmp( r->network, "unknown" ) ) netname = siteinfo->network; else netname = r->network; stream << urldump << " ~B" << setw(40) << r->version << "~! ~g" << setw(15) << netname << "~! ~G" << imc_serverinpath( r->path ) << "~!" << endl; } stream.close(); } void ev_statdump( const void *data ) { imc_statdump(); imc_add_event( 15, ev_statdump, NULL ); } void check_mudsfile( void ) { ifstream stream; string key; bool found = false; if( !siteinfo->autoupdate ) return; stream.open( IMC_UPDATE_FILE ); if( !stream.is_open() ) return; unlink( IMC_UPDATE_FILE ); // In case something crashes during reading do { stream >> key; if( key == "\n" || key == "\r" || key.empty() ) break; else if( key == "#" ) { string host; bool ifound = false; stream >> host; list<string>::iterator ban; for( ban = banlist.begin(); ban != banlist.end(); ++ban ) { string b = (*ban); if( b == host ) { ifound = true; break; } } if( ifound ) { logfile << "Ban listed in a \'#\' line in the update file already exists." << endl; continue; } logfile << "Adding ban for " << host << " via autoupdate file." << endl; banlist.push_back( host ); imc_savebans(); list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ) { connection *c = (*conn); ++conn; if( c->host == host ) { if( c->info ) imc_delete_info( c->info ); else do_close( c ); } } continue; } else if( key == "$" ) { string host; bool ifound = false; stream >> host; list<string>::iterator ban; for( ban = banlist.begin(); ban != banlist.end(); ++ban ) { string b = *ban; if( b == host ) { ifound = true; break; } } if( !ifound ) { logfile << "Ban listed in a \'$\' line in the update file could not be found." << endl; continue; } logfile << "Removing ban for " << host << " via autoupdate file." << endl; banlist.remove( host ); imc_savebans(); continue; } else if( key == "-" ) { string name; imc_info *i = NULL; bool ifound = false; stream >> name; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( !ifound ) { logfile << "Connection referenced in a \'-\' line in the update file could not be found." << endl; continue; } logfile << "Removing connection for " << name << " via autoupdate file." << endl; send_reminfo_destroy( name ); imc_delete_info( i ); found = true; continue; } else if( key == "=" ) { imc_info *i; string name, host, pw1, pw2; unsigned short port; bool ifound = false; stream >> name >> host >> port >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { logfile << "Connection listed in a \'=\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->host = host; i->clientpw = pw1; i->serverpw = pw2; i->port = port; i->md5 = false; i->sha256 = false; i->last_connected = imc_now; i->server = true; logfile << "Adding Server-Type1 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl; found = true; continue; } else if( key == "&" ) { imc_info *i; string name, pw1, pw2; bool ifound = false; stream >> name >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { logfile << "Connection listed in a \'&\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->clientpw = pw1; i->serverpw = pw2; i->server = true; i->md5 = false; i->sha256 = false; i->last_connected = imc_now; logfile << "Adding Server-Type2 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl; found = true; continue; } else if( key == "%" ) { imc_info *i = NULL; string name, host, pw1, pw2; unsigned short port; bool ifound = false; stream >> name >> host >> port >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { if( !i->md5 ) { i->md5 = true; i->sha256 = false; logfile << "Updating connection for " << i->name << " to use MD5 Authentication via autoupdate file." << endl; if( i->conn ) imc_send_keepalive( NULL, "*@" + i->name ); found = true; continue; } logfile << "Connection listed in a \'%\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->host = host; i->clientpw = pw1; i->serverpw = pw2; i->port = port; i->md5 = true; i->last_connected = imc_now; i->server = true; logfile << "Adding Server-Type1 connection for " << i->name << " via autoupdate file. Using MD5 Authentication." << endl; found = true; continue; } else if( key == "@" ) { imc_info *i = NULL; string name, pw1, pw2; bool ifound = false; stream >> name >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { if( !i->md5 ) { i->md5 = true; i->sha256 = false; logfile << "Updating connection for " << i->name << " to use MD5 Authentication via autoupdate file." << endl; if( i->conn ) imc_send_keepalive( NULL, "*@" + i->name ); found = true; continue; } logfile << "Connection listed in a \'@\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->clientpw = pw1; i->serverpw = pw2; i->server = true; i->md5 = true; i->last_connected = imc_now; logfile << "Adding Server-Type2 connection for " << i->name << " via autoupdate file. Using MD5 Authentication." << endl; found = true; continue; } else if( key == "^" ) { imc_info *i = NULL; string name, host, pw1, pw2; unsigned short port; bool ifound = false; stream >> name >> host >> port >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { if( !i->sha256 ) { i->sha256 = true; i->md5 = false; logfile << "Updating connection for " << i->name << " to use SHA-256 Authentication via autoupdate file." << endl; if( i->conn ) imc_send_keepalive( NULL, "*@" + i->name ); found = true; continue; } logfile << "Connection listed in a \'^\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->host = host; i->clientpw = pw1; i->serverpw = pw2; i->port = port; i->sha256 = true; i->last_connected = imc_now; i->server = true; logfile << "Adding Server-Type1 connection for " << i->name << " via autoupdate file. Using SHA-256 Authentication." << endl; found = true; continue; } else if( key == "!" ) { imc_info *i = NULL; string name, pw1, pw2; bool ifound = false; stream >> name >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { if( !i->sha256 ) { i->sha256 = true; i->md5 = false; logfile << "Updating connection for " << i->name << " to use SHA-256 Authentication via autoupdate file." << endl; if( i->conn ) imc_send_keepalive( NULL, "*@" + i->name ); found = true; continue; } logfile << "Connection listed in a \'@\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->clientpw = pw1; i->serverpw = pw2; i->server = true; i->sha256 = true; i->last_connected = imc_now; logfile << "Adding Server-Type2 connection for " << i->name << " via autoupdate file. Using SHA-256 Authentication." << endl; found = true; continue; } else if( key == "+" ) { imc_info *i; string name, host, pw1, pw2; unsigned short port; bool ifound = false; stream >> name >> host >> port >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { logfile << "Connection listed in a \'+\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->host = host; i->clientpw = pw1; i->serverpw = pw2; i->port = port; i->md5 = false; i->sha256 = false; i->last_connected = imc_now; logfile << "Adding Mud-Type1 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl; found = true; continue; } else if( key == "*" ) { imc_info *i; string name, pw1, pw2; bool ifound = false; stream >> name >> pw1 >> pw2; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { i = *inf; if( !str_cmp( name, i->name ) ) { ifound = true; break; } } if( ifound ) { logfile << "Connection listed in a \'*\' line in the update file already exists." << endl; continue; } i = imc_new_info(); i->name = name; i->clientpw = pw1; i->serverpw = pw2; i->md5 = false; i->sha256 = false; i->last_connected = imc_now; logfile << "Adding Mud-Type2 connection for " << i->name << " via autoupdate file. Using Standard Authentication." << endl; found = true; continue; } else if( key == "~" ) { string name; stream >> name; siteinfo->serveradminpwd = name; logfile << "Remote administration password has been changed via autoupdate file." << endl; found = true; continue; } else { char buf[LSS]; stream.getline( buf, LSS ); logfile << "Bad \'start of line\' character found in the update file." << endl; } } while( !stream.eof() ); stream.close(); if( found ) imc_saveconfig(); } void ev_mudconnections( const void *data ) { check_mudsfile(); imc_add_event( 15, ev_mudconnections, NULL ); } void ev_bootcheck( const void *data ) { if( exists_file( IMC_BOOT_FILE ) ) { unlink( IMC_BOOT_FILE ); logfile << "Reboot called from boot file." << endl; imc_shutdown(); } if( exists_file( IMC_HBOOT_FILE ) ) { unlink( IMC_HBOOT_FILE ); logfile << "Hotboot called from boot file." << endl; imc_hotboot(); } imc_add_event( 15, ev_bootcheck, NULL ); } void imc_free_event( imc_event *p ) { if( p != imc_event_list ) { imc_event *c = NULL; for( c = imc_event_list; c; c = c->next ) if( c->next == p ) { c->next = p->next; break; } } else imc_event_list = NULL; if( event_freecount > 10 ) /* pick a number, any number */ deleteptr( p ); else { p->next = imc_event_free; imc_event_free = p; ++event_freecount; } } void imc_add_event( int when, void (*callback)( const void *), const void *data ) { imc_event *p, *cur = NULL, *last = NULL; if( imc_event_free ) { p = imc_event_free; imc_event_free = p->next; --event_freecount; } else p = new imc_event; p->when = imc_now + when; p->callback = callback; p->data = data; for( cur = imc_event_list; cur; cur = cur->next ) { if( cur->when > p->when ) break; last = cur; } p->next = cur; if( !last ) imc_event_list = p; else last->next = p; } void imc_cancel_event( void (*callback)( const void *), const void *data ) { imc_event *p, *p_next, **last; for( last = &imc_event_list, p = *last; p; p = p_next ) { p_next = p->next; if( (!callback) && p->data == data ) { *last = p_next; imc_free_event( p ); } else if( (callback) && p->data == data && data != NULL ) { *last = p_next; imc_free_event( p ); } else if( p->callback == callback && data == NULL ) { *last = p_next; imc_free_event( p ); } else last = &p->next; } } /* added this to help control open and close announcements, tie them to the 60 second grace period on close-notify shogar 2/26/2000 */ imc_event *imc_find_event( void (*callback)( const void *), const void *data ) { imc_event *p; for( p = imc_event_list; p; p = p->next ) if( p->callback == callback && p->data == data ) return p; return NULL; } int imc_next_event( void (*callback)( const void *), const void *data ) { imc_event *p; for( p = imc_event_list; p; p = p->next ) if( p->callback == callback && p->data == data ) return p->when - imc_now; return -1; } int imc_get_event_timeout( void ) { if( imc_event_list != NULL ) return( imc_event_list->when - imc_now ); /* make sure we don't get too backlogged with events */ return 60; } void imc_run_events( time_t newtime ) { imc_event *p; void (*callback)( const void *); const void *data; while( imc_event_list ) { p = imc_event_list; if( p->when > newtime ) break; imc_event_list = p->next; callback = p->callback; data = p->data; imc_now = p->when; imc_free_event(p); if( callback ) (*callback)(data); else logfile << __FUNCTION__ << ": NULL callback" << endl; } imc_now = newtime; } /* Junks inactive connections after 14 days and informs other servers to update their reminfo lists */ void ev_purgeinfo( const void *data ) { imc_reminfo *p; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ) { imc_info *i = *inf; ++inf; if( i->conn ) { i->last_connected = imc_now; continue; } if( i->last_connected < 1 ) { i->last_connected = imc_now; continue; } if( i->server ) continue; if( i->last_connected + 1209600 < imc_now && !i->conn ) { if( ( p = imc_find_reminfo( i->name ) ) ) imc_delete_reminfo( p ); send_reminfo_destroy( i->name ); logfile << "Deleting configuration for " << i->name << ". Expired for non-connection." << endl; imc_delete_info( i ); } } imc_saveconfig(); siteinfo->connects = infolist.size(); // This is a hackish fix since the servers seem to be maintaining bad count data elsewhere. imc_add_event( 900, ev_purgeinfo, NULL ); } /******************************************* * Network Startup and Shutdown functions. * *******************************************/ /* connect to given mud */ bool imc_connect_to( const string& mud ) { imc_info *i; connection *c; struct addrinfo hints, *ai_list, *ai; char rport[SSS]; int n, r, desc = -1; ostringstream buf; if( !( i = imc_getinfo( mud ) ) ) { logfile << __FUNCTION__ << ": " << mud << " - unknown mud name" << endl; return false; } if( i->conn ) { logfile << __FUNCTION__ << ": " << mud << " - already connected" << endl; return false; } snprintf( rport, SSS, "%hu", i->port ); memset( &hints, 0, sizeof(struct addrinfo) ); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; n = getaddrinfo( i->host.c_str(), rport, &hints, &ai_list ); if( n ) { logfile << __FUNCTION__ << ": getaddrinfo: " << gai_strerror(n) << endl; return false; } for( ai = ai_list; ai; ai = ai->ai_next ) { desc = socket( ai->ai_family, ai->ai_socktype, ai->ai_protocol ); if( desc < 0 ) continue; if( connect( desc, ai->ai_addr, ai->ai_addrlen ) == 0 ) break; close( desc ); } freeaddrinfo( ai_list ); if( ai == NULL ) { logfile << __FUNCTION__ << ": socket or connect: failed for " << i->host << " port " << rport << endl; return false; } r = fcntl( desc, F_GETFL, 0 ); if( r < 0 || fcntl( desc, F_SETFL, O_NONBLOCK | r ) < 0 ) { perror( "imc_connect: fcntl" ); close( desc ); return false; } logfile << __FUNCTION__ << ": Connecting to " << mud << endl; c = imc_new_connect(); c->desc = desc; c->state = CONN_SERVERCONNECT; c->info = i; imc_add_event( IMC_LOGIN_TIMEOUT, ev_login_timeout, c ); if( i->md5 && !i->sha256 ) buf << "MD5-AUTH-REQ " << siteinfo->name; else if( i->sha256 ) buf << "SHA256-AUTH-REQ " << siteinfo->name; else buf << "PW " << siteinfo->name << " " << i->clientpw << " version=2"; write_buffer( c, buf.str() ); return true; } /* Save current ban list. Short, simple. */ void imc_savebans( void ) { ofstream stream; stream.open( IMC_BAN_FILE ); if( !stream.is_open() ) { logfile << "Error opening ban file for write" << endl; return; } stream << "#BANLIST" << endl; list<string>::iterator ban; for( ban = banlist.begin(); ban != banlist.end(); ++ban ) { string b = *ban; stream << b << endl; } stream.close(); } /* read an IMC ban file */ void imc_readbans( void ) { ifstream stream; string word; stream.open( IMC_BAN_FILE ); if( !stream.is_open() ) { logfile << "Couldn't open ban file for read" << endl; return; } banlist.clear(); do { stream >> word; if( word[0] == '#' ) continue; banlist.push_back( word ); } while( !stream.eof() ); stream.close(); } void imc_save_reminfo_table( void ) { ofstream stream; stream.open( REMINFO_FILE ); if( !stream.is_open() ) { logfile << "Could not write to reminfo file" << endl; return; } logfile << "Saving remote information table..." << endl; list<imc_reminfo*>::iterator rin; for( rin = reminfolist.begin(); rin != reminfolist.end(); ++rin ) { imc_reminfo *r = *rin; stream << "#REMINFO" << endl; stream << "Name " << r->name << endl; if( !(*rin)->path.empty() ) stream << "Path " << r->path << endl; stream << "Expired " << r->expired << endl; stream << "SHA256 " << r->sha256 << endl; stream << "SeqNum " << r->top_sequence << endl; stream << "End" << endl << endl; } stream.close(); } void imc_load_reminfo_table( void ) { imc_reminfo *r; ifstream stream; reminfolist.clear(); logfile << "Loading remote information table..." << endl; stream.open( REMINFO_FILE ); if( !stream.is_open() ) { logfile << "No remote information table found." << endl; return; } /* Limit crash damage potential in case file is corrupted */ unlink( REMINFO_FILE ); do { string line, key, value; char buf[MSS]; stream.getline( buf, MSS ); line = buf; if( line.empty() ) continue; istringstream lstream( line ); lstream >> key >> value; strip_lspace( key ); strip_lspace( value ); if( key == "#REMINFO" ) r = new imc_reminfo; else if( key == "Name" ) r->name = value; else if( key == "Path" ) r->path = value; else if( key == "Expired" ) r->expired = atoi( value.c_str() ); else if( key == "SHA256" ) r->sha256 = atoi( value.c_str() ); else if( key == "SeqNum" ) r->top_sequence = atol( value.c_str() ); else if( key == "End" ) reminfolist.push_back( r ); else logfile << "Bad data in reminfo table." << endl; } while( !stream.eof() ); stream.close(); } void imc_save_laston_table( void ) { ofstream stream; stream.open( LASTON_FILE ); if( !stream.is_open() ) { logfile << "Could not write to laston file" << endl; return; } logfile << "Saving last_on information table..." << endl; list<imc_laston*>::iterator lst; for( lst = lastonlist.begin(); lst != lastonlist.end(); ++lst ) { imc_laston *l = *lst; stream << "#LINFO" << endl; stream << "Name " << l->name << endl; stream << "Channel " << l->chan << endl; stream << "Time " << l->last_time << endl; stream << "End" << endl << endl; } stream.close(); } void imc_load_laston_table( void ) { imc_laston *r; ifstream stream; lastonlist.clear(); logfile << "Loading last_on information table..." << endl; stream.open( LASTON_FILE ); if( !stream.is_open() ) { logfile << "No last_on information table found." << endl; return; } /* Limit crash damage potential in case file is corrupted */ unlink( LASTON_FILE ); do { string line, key, value; char buf[MSS]; stream.getline( buf, MSS ); line = buf; if( line.empty() ) continue; istringstream lstream( line ); lstream >> key >> value; strip_lspace( key ); strip_lspace( value ); if( key == "#LINFO" ) r = new imc_laston; else if( key == "Name" ) r->name = value; else if( key == "Channel" ) r->chan = value; else if( key == "Time" ) r->last_time = atol( value.c_str() ); else if( key == "End" ) lastonlist.push_back( r ); else logfile << "Bad data in laston table." << endl; } while( !stream.eof() ); stream.close(); } void iced_save_channels( void ) { ofstream stream; stream.open( IMC_CHANNEL_FILE ); if( !stream.is_open() ) { logfile << "Can't write to channel file" << endl; return; } list<imc_channel*>::iterator chn; for( chn = chanlist.begin(); chn != chanlist.end(); ++chn ) { imc_channel *c = *chn; stream << "#IMCCHAN" << endl; stream << "ChanName " << c->name << endl; stream << "ChanPolicy " << c->open << endl; if( !c->lname.empty() ) stream << "ChanLocal " << c->lname << endl; if( !c->level.empty() ) stream << "ChanLevel " << c->level << endl; if( !c->owner.empty() ) stream << "ChanOwner " << c->owner << endl; if( !c->operators.empty() ) stream << "ChanOps " << c->operators << endl; if( !c->invited.empty() ) stream << "ChanInvited " << c->invited << endl; if( !c->excluded.empty() ) stream << "ChanExcluded " << c->excluded << endl; stream << "End" << endl << endl; } stream.close(); } void iced_loadchannels( void ) { ifstream stream; imc_channel *c; logfile << "Loading iced channel configuration..." << endl; chanlist.clear(); stream.open( IMC_CHANNEL_FILE ); if( !stream.is_open() ) { logfile << "Can't open iced channel file" << endl; return; } do { string key, value; char buf[LSS]; stream >> key; stream.getline( buf, LSS ); value = buf; strip_lspace( key ); strip_lspace( value ); if( key == "#IMCCHAN" ) c = new imc_channel; else if( key == "ChanName" ) c->name = value; else if( key == "ChanPolicy" ) c->open = atoi( value.c_str() ); else if( key == "ChanLocal" ) c->lname = value; else if( key == "ChanLevel" ) c->level = value; else if( key == "ChanOwner" ) c->owner = value; else if( key == "ChanOps" ) c->operators = value; else if( key == "ChanInvited" ) c->invited = value; else if( key == "ChanExcluded" ) c->excluded = value; else if( key == "End" ) { chanlist.push_back( c ); logfile << "Channel " << c->name << " loaded. Localname: " << ( !c->lname.empty() ? c->lname : "not set" ) << ". Permission: " << c->level << ". Policy: " << ( c->open ? "open" : "private" ) << endl; fixactive( c ); } else logfile << "Bad data in channel file." << endl; } while( !stream.eof() ); stream.close(); } /* Save config file - So nonrebooted updates save */ bool imc_saveconfig( void ) { ofstream stream; stream.open( IMC_CONFIG_FILE ); if( !stream.is_open() ) { logfile << "Configuration file could not be opened for writing." << endl; return false; } stream << "#" << IMC_VERSIONID << " config file" << endl; stream << "FileVersion 4" << endl; stream << "ServerName " << siteinfo->name << endl; stream << "ServerPort " << siteinfo->port << endl; stream << "Network " << siteinfo->network << endl; stream << "MaxConnect " << siteinfo->maxconnects << endl; if( !siteinfo->chancreators.empty() ) stream << "ChannelCreators " << siteinfo->chancreators << endl; if( !siteinfo->serveradmins.empty() ) stream << "ServerAdmins " << siteinfo->serveradmins << endl; if( !siteinfo->serveradminpwd.empty() ) stream << "ServerAdminPwd " << siteinfo->serveradminpwd << endl; if( siteinfo->bind > 0 ) { struct sockaddr_in binds; binds.sin_addr.s_addr = siteinfo->bind; stream << "BindIP " << inet_ntoa( binds.sin_addr ) << endl; } if( !siteinfo->host.empty() ) stream << "InfoHost " << siteinfo->host << endl; if( !siteinfo->url.empty() ) stream << "InfoURL " << siteinfo->url << endl; stream << endl << "#Optional flags for the server. Usage details in the install.txt file." << endl; stream << "#These can also be toggled via remote admin if it is enabled." << endl; stream << ( siteinfo->remoteadmin ? "RemoteAdmin" : "#RemoteAdmin" ) << endl; stream << ( siteinfo->priv ? "Private" : "#Private" ) << endl; stream << ( siteinfo->autoupdate ? "Autoupdate" : "#Autoupdate" ) << endl; stream << ( siteinfo->prune ? "PruneLinks" : "#PruneLinks" ) << endl; stream << ( siteinfo->can_compress ? "Zlib" : "#Zlib" ) << endl; stream << ( siteinfo->webstats ? "Webstats" : "#Webstats" ) << endl; if( !siteinfo->statsfile.empty() ) stream << "WebstatsFile " << siteinfo->statsfile << endl; stream << endl << "#Network connection listings." << endl; stream << "#Server-Type1 connections will attempt to connect at every server reboot and at set intervals while running." << endl; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty() || i->host.empty() || i->port == 0 || i->server == false ) continue; stream << "Server-Type1 " << i->name << " " << i->host << " " << i->port << " "; stream << i->clientpw << " " << i->serverpw << " " << i->md5 << " " << i->sha256 << " "; stream << i->last_connected << endl; } stream << endl <<"#Server-Type2 connections will be left to their own means to connect." << endl; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty() || i->port > 0 || i->server == false ) continue; stream << "Server-Type2 " << i->name << " " << i->clientpw << " "; stream << i->serverpw << " " << i->md5 << " " << i->sha256 << " " << i->last_connected << endl; } stream << endl << "#Mud-Type1 connections will attempt to connect at every server reboot and at set intervals while running." << endl; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty() || i->host.empty() || i->port == 0 || i->server == true ) continue; stream << "Mud-Type1 " << i->name << " " << i->host << " " << i->port; stream << " " << i->clientpw << " " << i->serverpw << " " << i->md5 << " " << i->sha256 << " "; stream << i->last_connected << endl; } stream << endl << "#Mud-Type2 connections will be left to their own means to connect." << endl; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = *inf; if( i->name.empty() || i->clientpw.empty() || i->serverpw.empty() || i->port > 0 || i->server == true ) continue; stream << "Mud-Type2 " << i->name << " " << i->clientpw << " "; stream << i->serverpw << " " << i->md5 << " " << i->sha256 << " " << i->last_connected << endl; } stream << endl; stream.close(); return true; } /* read config file */ bool imc_readconfig( void ) { imc_info *i; ifstream stream; char buf[LSS]; time_t last_conn = 0; int clcount = 0, rtcount = 0, fileversion = 0; stream.open( IMC_CONFIG_FILE ); if( !stream.is_open() ) { logfile << "Configuration file could not be opened for reading." << endl; return false; } siteinfo = new imc_siteinfo; /* Default value unless specified in the config file */ siteinfo->maxconnects = 40; siteinfo->bind = 0; siteinfo->prune = false; siteinfo->remoteadmin = false; siteinfo->autoupdate = false; siteinfo->can_compress = false; siteinfo->priv = false; siteinfo->webstats = false; do { string key, value; stream >> key; stream.getline( buf, LSS ); value = buf; if( key.empty() || key[0] == '#' ) continue; strip_lspace( key ); strip_lspace( value ); if( key == "FileVersion" ) fileversion = atoi( value.c_str() ); else if( key == "ServerName" || key == "RouterName" ) siteinfo->name = value; else if( key == "BindIP" ) siteinfo->bind = inet_addr( value.c_str() ); else if( key == "ServerPort" || key == "RouterPort" ) siteinfo->port = atoi( value.c_str() ); else if( key == "MaxConnect" ) siteinfo->maxconnects = atoi( value.c_str() ); else if( key == "RemoteAdmin" ) siteinfo->remoteadmin = true; else if( key == "PruneLinks" ) siteinfo->prune = true; else if( key == "Private" ) siteinfo->priv = true; else if( key == "Autoupdate" ) siteinfo->autoupdate = true; else if( key == "Zlib" ) siteinfo->can_compress = true; else if( key == "Webstats" ) siteinfo->webstats = true; else if( key == "WebstatsFile" ) siteinfo->statsfile = value; else if( key == "Network" ) siteinfo->network = value; else if( key == "ChannelCreators" ) siteinfo->chancreators = value; else if( key == "ServerAdmins" || key == "RouterAdmins" ) siteinfo->serveradmins = value; else if( key == "ServerAdminPwd" || key == "RouterAdminPwd" ) siteinfo->serveradminpwd = value; else if( key == "InfoHost" ) siteinfo->host = value; else if( key == "InfoURL" ) siteinfo->url = value; else if( key == "Server-Type1" || key == "Router-Type1" ) { istringstream line( value ); string name; last_conn = 0; line >> name; if( ( i = imc_getinfo( name ) ) ) { logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl; continue; } i = imc_new_info(); i->name = name; i->server = true; if( fileversion < 3 ) line >> i->host >> i->port >> i->clientpw >> i->serverpw >> last_conn; else if( fileversion == 3 ) line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> last_conn; else line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn; if( last_conn < 0 ) last_conn = imc_now; i->last_connected = last_conn; ++rtcount; } else if( key == "Server-Type2" || key == "Router-Type2" ) { istringstream line( value ); string name; last_conn = 0; line >> name; if( ( i = imc_getinfo( name ) ) ) { logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl; continue; } i = imc_new_info(); i->name = name; i->server = true; if( fileversion < 3 ) line >> i->clientpw >> i->serverpw >> last_conn; else if( fileversion == 3 ) line >> i->clientpw >> i->serverpw >> i->md5 >> last_conn; else line >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn; if( last_conn < 0 ) last_conn = imc_now; i->last_connected = last_conn; ++rtcount; } else if( key == "Mud-Type1" ) { istringstream line( value ); string name; last_conn = 0; line >> name; if( ( i = imc_getinfo( name ) ) ) { logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl; continue; } i = imc_new_info(); i->name = name; i->server = false; if( fileversion < 3 ) line >> i->host >> i->port >> i->clientpw >> i->serverpw >> last_conn; else if( fileversion == 3 ) line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> last_conn; else line >> i->host >> i->port >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn; if( last_conn < 0 ) last_conn = imc_now; i->last_connected = last_conn; ++clcount; } else if( key == "Mud-Type2" ) { istringstream line( value ); string name; last_conn = 0; line >> name; if( ( i = imc_getinfo( name ) ) ) { logfile << "Warning: duplicate '" << key << "' lines in configuration file - discarding new one." << endl; continue; } i = imc_new_info(); i->name = name; i->server = false; if( fileversion < 3 ) line >> i->clientpw >> i->serverpw >> last_conn; else if( fileversion == 3 ) line >> i->clientpw >> i->serverpw >> i->md5 >> last_conn; else line >> i->clientpw >> i->serverpw >> i->md5 >> i->sha256 >> last_conn; if( last_conn < 0 ) last_conn = imc_now; i->last_connected = last_conn; ++clcount; } else logfile << "Bad line in configuration file: " << key << endl; } while( !stream.eof() ); stream.close(); siteinfo->connects = infolist.size(); logfile << "Configured " << rtcount << " server" << ( rtcount == 1 ? "" : "s" ) << " and " << clcount << " client" << ( clcount == 1 ? "" : "s" ) << endl; return true; } void imc_shutdown_network( void ) { imc_event *ev, *ev_next; logfile << "Closing listening port." << endl; close( control ); imc_save_laston_table(); list<imc_laston*>::iterator lst; for( lst = lastonlist.begin(); lst != lastonlist.end(); ) { imc_laston *l = (*lst); ++lst; deleteptr( l ); } lastonlist.clear(); list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ) { connection *c = (*conn); ++conn; do_close( c ); imc_extract_connect( c ); } connlist.clear(); list<imc_reminfo*>::iterator rin; for( rin = reminfolist.begin(); rin != reminfolist.end(); ) { imc_reminfo *r = (*rin); ++rin; imc_delete_reminfo( r ); } reminfolist.clear(); for( ev = imc_event_list; ev; ev = ev_next ) { ev_next = ev->next; deleteptr( ev ); } for( ev = imc_event_free; ev; ev = ev_next ) { ev_next = ev->next; deleteptr( ev ); } imc_event_list = imc_event_free = NULL; } /* close down imc */ void imc_shutdown( void ) { logfile << "Shutting down " << IMC_VERSIONID << endl; list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ) { imc_info *i = *inf; ++inf; imc_delete_info( i ); } infolist.clear(); imc_shutdown_network(); deleteptr( siteinfo ); logfile << IMC_VERSIONID << " shutdown completed." << endl; logstream.close(); exit(0); } void imc_hotboot( void ) { ofstream stream; char buf[100], buf2[100]; stream.open( HOTBOOT_FILE ); if( !stream.is_open() ) { logfile << "Could not write to hotboot file. Hotboot aborted." << endl; return; } logfile << "Saving connection states...." << endl; /* For each connection, save its state */ list<connection*>::iterator conn; for( conn = connlist.begin(); conn != connlist.end(); ++conn ) { connection *c = *conn; if( c->state != CONN_COMPLETE ) /* drop those logging on */ do_close( c ); /* throw'em out */ else stream << c->desc << " " << c->version << " " << c->info->name << " " << c->host << endl; } stream << "-1" << endl; stream.close(); imc_save_reminfo_table(); imc_save_laston_table(); logfile << "Executing hotboot...." << endl; /* exec - descriptors are inherited */ snprintf( buf, 100, "%hu", siteinfo->port ); snprintf( buf2, 100, "%d", control ); logstream.close(); execl( EXE_FILE, "server", buf, "hotboot", buf2, (char *)NULL ); /* Failed - sucessful exec will not return */ perror( "imc_hotboot: execl" ); imc_shutdown(); } void imc_hotboot_recover( void ) { connection *c = NULL; imc_info *i; ifstream stream; imc_load_reminfo_table(); stream.open( HOTBOOT_FILE ); if( !stream.is_open() ) /* there are some descriptors open which will hang forever then ? */ { logfile << "Hotboot file not found. Exitting." << endl; exit( 1 ); } unlink( HOTBOOT_FILE ); /* In case something crashes - doesn't prevent reading */ do { string line, desc, version, name, host; char buf[MSS]; stream.getline( buf, MSS ); line = buf; if( line.empty() ) continue; istringstream lstream( line ); lstream >> desc >> version >> name >> host; strip_lspace( desc ); strip_lspace( version ); strip_lspace( name ); strip_lspace( host ); if( desc == "-1" ) break; /* Write something, and check if it goes error-free if( !write_to_descriptor_old( desc, "\r\nThe ether swirls in chaos.\r\n", 0 ) ) { close( desc ); continue; } */ c = imc_new_connect(); if( desc == "0" ) { logfile << __FUNCTION__ << ": ALERT! Assigning socket 0! BAD BAD BAD! Name: " << name << " Host: " << host << endl; imc_extract_connect( c ); continue; } c->state = CONN_COMPLETE; c->desc = atoi( desc.c_str() ); c->version = atoi( version.c_str() ); c->host = host; if( !( i = imc_getinfo( name ) ) ) { logfile << "Unable to find imc_info listing for " << name << endl; do_close( c ); imc_extract_connect( c ); continue; } c->info = i; i->login_time = imc_now; i->last_connected = imc_now; i->conn = c; } while( !stream.eof() ); stream.close(); logfile << "Hotboot recovery complete." << endl; } /* start up listening port */ bool imc_startup_port( void ) { int i; struct sockaddr_in sa; control = -1; logfile << "Binding port " << siteinfo->port << " for incoming connections." << endl; control = socket( AF_INET, SOCK_STREAM, 0 ); if( control < 0 ) { logfile << __FUNCTION__ << ": socket call failure" << endl; return false; } i = 1; if( setsockopt( control, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i) ) < 0 ) { logfile << __FUNCTION__ << ": SO_REUSEADDR unable to be set" << endl; close( control ); return false; } if( ( i = fcntl( control, F_GETFL, 0 ) ) < 0 ) { logfile << __FUNCTION__ << ": fcntl(F_GETFL) call failure" << endl; close( control ); return false; } if( fcntl( control, F_SETFL, i | O_NONBLOCK ) < 0 ) { logfile << __FUNCTION__ << ": fcntl(F_SETFL) call failure" << endl; close( control ); return false; } sa.sin_family = AF_INET; sa.sin_port = htons( siteinfo->port ); sa.sin_addr.s_addr = siteinfo->bind; /* already in network order */ if( bind( control, (struct sockaddr *)&sa, sizeof(sa) ) < 0 ) { logfile << __FUNCTION__ << ": bind call failure" << endl; close( control ); return false; } if( listen( control, 5 ) < 0 ) { logfile << __FUNCTION__ << ": listen call failure" << endl; close( control ); return false; } return true; } /* start up IMC */ bool imc_startup_network( bool hotboot ) { if( hotboot ) imc_hotboot_recover(); else if( !imc_startup_port() ) return false; imc_stats.start = imc_now; imc_stats.rx_pkts = 0; imc_stats.tx_pkts = 0; imc_stats.rx_bytes = 0; imc_stats.tx_bytes = 0; imc_stats.rx_cmpbytes = 0; imc_stats.tx_cmpbytes = 0; imc_stats.rx_bytessaved = 0; imc_stats.tx_bytessaved = 0; imc_stats.sequence_drops = 0; /* Scan for reboot file */ imc_add_event( 15, ev_bootcheck, NULL ); /* Allow updating mud connections list without reboot */ if( siteinfo->autoupdate ) imc_add_event( 15, ev_mudconnections, NULL ); /* Run the initial statdump for the web script -- Xorith */ if( siteinfo->webstats ) imc_add_event( 1, ev_statdump, NULL ); /* Begin the regular purge cycle for mud info listings - every 15 minutes */ if( siteinfo->prune ) imc_add_event( 900, ev_purgeinfo, NULL ); /* Setup autoconnection check event */ if( !hotboot ) { list<imc_info*>::iterator inf; for( inf = infolist.begin(); inf != infolist.end(); ++inf ) { imc_info *i = (*inf); if( i->port > 0 && i->conn == NULL ) imc_connect_to( i->name ); } } imc_add_event( 900, ev_autoconnects, NULL ); /* Send out a keepalive */ imc_add_event( 2, ev_keepalive, NULL ); /* Make sure any channel changes get sent out if the server reboots */ imc_add_event( 4, ev_iced_refresh, NULL ); return true; } void imc_startup( bool hotboot ) { imc_now = time( NULL ); /* start our clock */ imc_boot = imc_now; logfile << IMC_VERSIONID << " initializing" << endl; imc_sequencenumber = imc_now; if( !imc_readconfig() || !imc_startup_network( hotboot ) ) { logfile << "Unable to start server. Giving up. Sorry." << endl; exit( 1 ); } /* Lets register all the default packet handlers */ imc_register_default_packets(); imc_load_laston_table(); imc_readbans(); } void open_new_log( void ) { ostringstream buf; int logindex; for( logindex = 1000; ; ++logindex ) { buf.str(""); buf << "../logs/" << logindex << ".log"; if( exists_file( buf.str() ) ) continue; else break; } logstream.open( buf.str().c_str() ); if( !logstream.is_open() ) { fprintf( stderr, "Unable to open logfile %s.", buf.str().c_str() ); exit( 1 ); } logfile.setStream( logstream ); } static void graceful_exit( int sig ) { ofstream stream; logfile << "Shutdown called by kill signal." << endl; stream.open( "shutdown" ); if( !stream.is_open() ) { logfile << __FUNCTION__ << ": Unable to drop shutdown file! Server will reboot instead." << endl; imc_shutdown(); } stream << "SHUTDOWN" << endl; stream.close(); imc_shutdown(); } int main( int argc, char **argv ) { struct timeval last_time; bool hotboot = false; gettimeofday( &last_time, NULL ); signal( SIGPIPE, SIG_IGN ); signal( SIGTERM, graceful_exit ); if( argc > 1 ) { if( argv[2] && argv[2][0] ) { hotboot = true; control = atoi( argv[3] ); } else hotboot = false; } open_new_log(); imc_startup( hotboot ); iced_loadchannels(); if( hotboot ) { imc_request_keepalive(); } server_loop( last_time ); /* Never reaches this point - server_loop is recursive */ exit(0); }