/* * IMC2 version 0.10 - an inter-mud communications protocol * Copyright (C) 1996 & 1997 Oliver Jowett <oliver@randomly.org> * * IMC2 Gold versions 1.00 though 2.00 were developed by MudWorld. * Copyright (C) 1999 - 2002 Haslage Net Electronics (Anthony R. Haslage) * * IMC2 MUD-Net version 3.10 was developed by Alsherok and Crimson Oracles * Copyright (C) 2002 Roger Libiez ( Samson ) * Additional code Copyright (C) 2002 Orion Elder * Registered with the United States Copyright Office * TX 5-555-584 * * IMC2 Hermes R01-R04 were developed by Rogel * Copyright (C) 2003-2004 by Rogel * * IMC2 Cyclone R01 was developed by Rogel * Copyright (C) 2004 by Rogel * * 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. * */ #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <string.h> #include <unistd.h> #include <sys/time.h> #include <time.h> #include <ctype.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <fcntl.h> #include <errno.h> #include <sys/file.h> #include <signal.h> #include "imc.h" #ifndef __FreeBSD__ char *strcasestr( const char *haystack, const char *needle ); #endif time_t imc_now; /* current time */ time_t imc_boot; /* current time */ imc_event *imc_event_list, *imc_event_free; static int event_freecount; /* sequence memory */ _imc_memory imc_memory[IMC_MEMORY]; unsigned long imc_sequencenumber; /* sequence# for outgoing packets */ imc_statistics imc_stats; CONNECTION *first_connection; CONNECTION *last_connection; NETWORK *first_network; NETWORK *last_network; REMINFO *first_reminfo; REMINFO *last_reminfo; CHANNEL *first_channel; CHANNEL *last_channel; static void graceful_exit( int sig ) { Log( "%s", "Bridge killed from shell." ); imc_shutdown(); } int main( void ) { struct timeval last_time; gettimeofday( &last_time, NULL ); signal( SIGPIPE, SIG_IGN ); signal( SIGTERM, graceful_exit ); imc_startup( ); bridge_loop(last_time); /* Never reaches this point - bridge_loop is recursive */ exit(0); } void bridge_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( "main: select: stall" ); exit( 1 ); } } gettimeofday( &last_time, NULL ); bridge_loop(last_time); return; } // License for strlcpy located at ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcpy.c #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 */ // License for strlcat located at ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcat.c #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 */ /* * Error logging */ void Log( const char *format, ... ) { char buf[LSS]; struct timeval proper_time; time_t current_time; char *strtime; va_list ap; va_start( ap, format ); vsnprintf( buf, LSS, format, ap ); va_end( ap ); gettimeofday( &proper_time, NULL ); current_time = proper_time.tv_sec; strtime = ctime( ¤t_time ); strtime[strlen(strtime)-1] = '\0'; fprintf( stderr, "%s :: %s\n", strtime, buf ); return; } /* escape2: escape " -> \", \ -> \\, CR -> \r, LF -> \n */ static const char *escape2(const char *data) { static char buf[IMC_DATA_LENGTH]; unsigned int i = 0, j = 0; buf[0] = '\0'; if ( !data || data[0] == '\0' ) return buf; for ( i = 0, j = 0; i < IMC_DATA_LENGTH && data[j] != '\0'; i++, j++ ) { if ( data[j] == '\n' || data[j] == '\r' || data[j] == '\\' || data[j] == '"' ) { if ( i + 1 == IMC_DATA_LENGTH ) break; buf[i++] = '\\'; if ( data[j] == '\n' ) buf[i] = 'n'; else if ( data[j] == '\r' ) buf[i] = 'r'; else if ( data[j] == '\\' ) buf[i] = '\\'; else buf[i] = '\"'; } else buf[i] = data[j]; } buf[i] = '\0'; return buf; } /* printkeys: print key-value pairs, escaping values */ static const char *printkeys( PACKET *data ) { static char buf[IMC_DATA_LENGTH]; char temp[IMC_DATA_LENGTH]; unsigned char i; buf[0] = '\0'; if ( !data ) return buf; for( i = 0; i < IMC_MAX_KEYS; i++ ) { if( !data->key[i] ) continue; if( !strchr( data->value[i], ' ' ) ) snprintf( temp, IMC_DATA_LENGTH, "%s=%s ", data->key[i], escape2( data->value[i] ) ); else snprintf( temp, IMC_DATA_LENGTH, "%s=\"%s\" ", data->key[i], escape2( data->value[i] ) ); strlcat( buf, temp, IMC_DATA_LENGTH ); } return buf; } /* add "key=value" to "p" */ void imc_addkey( PACKET *p, const char *key, const char *value ) { unsigned char i; for( i = 0; i < IMC_MAX_KEYS; i++ ) { if( p->key[i] && STR_CEQL( key, p->key[i] ) ) { DISPOSE( p->key[i] ); DISPOSE( p->value[i] ); break; } } if( !value || value[0] == '\0' ) return; for( i = 0; i < IMC_MAX_KEYS; i++ ) { if( !p->key[i] ) { p->key[i] = strdup( key ); p->value[i] = strdup( value ); return; } } } /* add "key=value" for an integer value */ void imc_addkeyi( PACKET *p, const char *key, int value ) { char temp[20]; snprintf( temp, 20, "%d", value ); imc_addkey( p, key, temp ); } /* parsekeys: extract keys from string */ static void parsekeys( const char *string, PACKET *data ) { const char *p1; char *p2; char k[IMC_DATA_LENGTH], v[IMC_DATA_LENGTH]; bool quote; p1 = string; while (*p1) { while( *p1 && isspace(*p1) ) p1++; p2 = k; while( *p1 && *p1 != '=' && p2-k < IMC_DATA_LENGTH-1 ) *p2++=*p1++; *p2=0; if( !k[0] || !*p1 ) /* no more keys? */ break; p1++; /* skip the '=' */ if( *p1 == '"' ) { p1++; quote = TRUE; } else quote = FALSE; p2 = v; while( *p1 && (!quote || *p1 != '"') && (quote || *p1 != ' ') && p2-v < IMC_DATA_LENGTH+1 ) { if( *p1 == '\\' ) { switch( *(++p1) ) { case '\\': *p2++='\\'; break; case 'n': *p2++='\n'; break; case 'r': *p2++='\r'; break; case '"': *p2++='"'; break; default: *p2++=*p1; break; } if( *p1 ) p1++; } else *p2++ = *p1++; } *p2 = 0; if( !v[0] ) continue; imc_addkey( data, k, v ); if( quote && *p1 ) p1++; } } /* clear all keys in "p" */ void imc_initdata( PACKET *p ) { unsigned char i; if ( !p ) return; for( i = 0; i < IMC_MAX_KEYS; i++ ) { p->key[i] = NULL; p->value[i] = NULL; } } /* free all the keys in "p" */ void imc_freedata( PACKET *p ) { unsigned char i; if ( !p ) return; for( i = 0; i < IMC_MAX_KEYS; i++ ) { if( p->key[i] ) DISPOSE( p->key[i] ); if( p->value[i] ) DISPOSE( p->value[i] ); } } /* imc_getarg: extract a single argument (with given max length) from * argument to arg; if arg==NULL, just skip an arg, don't copy it out */ const char *imc_getarg( const char *argument, char *arg, unsigned int length) { unsigned int len = 0; if ( !argument || argument[0] == '\0' ) { if ( arg ) arg[0] = '\0'; return argument; } while (*argument && isspace(*argument)) argument++; if (arg) while (*argument && !isspace(*argument) && len < length-1) *arg++=*argument++, len++; else while (*argument && !isspace(*argument)) argument++; while (*argument && !isspace(*argument)) argument++; while (*argument && isspace(*argument)) argument++; if (arg) *arg = '\0'; return argument; } char *generate2( PACKET *p, AFFILIATE *a ) { static char temp[IMC_PACKET_LENGTH]; char newpath[IMC_PATH_LENGTH]; if( !p->type[0] || !p->i.from[0] || !p->i.to[0] ) { Log( "generate2: bad packet - type: %s from: %s to: %s path: %s data: %s", p->type, p->i.from, p->i.to, p->i.path, printkeys(p) ); return NULL; /* catch bad packets here */ } if( !p->i.path[0] ) strlcpy( newpath, a->name, IMC_PATH_LENGTH ); else snprintf( newpath, IMC_PATH_LENGTH, "%s!%s", p->i.path, a->name ); snprintf( temp, IMC_PACKET_LENGTH, "%s %lu %s %s %s %s", p->i.from, p->i.sequence, newpath, p->type, p->i.to, printkeys( p ) ); return temp; } PACKET *interpret2( const char *argument ) { char seq[20]; static PACKET out; imc_initdata( &out ); argument = imc_getarg( argument, out.i.from, IMC_NAME_LENGTH ); argument = imc_getarg( argument, seq, 20 ); argument = imc_getarg( argument, out.i.path, IMC_PATH_LENGTH ); argument = imc_getarg( argument, out.type, IMC_TYPE_LENGTH ); argument = imc_getarg( argument, out.i.to, IMC_NAME_LENGTH ); if( !out.i.from[0] || !seq[0] || !out.i.path[0] || !out.type[0] || !out.i.to[0] ) { Log( "interpret2: bad packet - type: %s from: %s to: %s path: %s seq: %s", out.type, out.from, out.i.to, out.i.path, seq ); return NULL; } parsekeys( argument, &out ); out.i.sequence = strtoul( seq, NULL, 10 ); return &out; } _imc_vinfo imc_vinfo[] = { { 0, NULL, NULL }, { 1, NULL, NULL }, { 2, generate2, interpret2 } }; /* * Key/value manipulation */ /* get the value of "key" from "p"; if it isn't present, return "def" */ const char *imc_getkey( const PACKET *p, const char *key, const char *def ) { unsigned char i; for( i = 0; i < IMC_MAX_KEYS; i++ ) if( p->key[i] && STR_CEQL(p->key[i], key)) return p->value[i]; return def; } /* identical to imc_getkey, except get the integer value of the key */ int imc_getkeyi( const PACKET *p, const char *key, int def ) { unsigned char i; for( i = 0; i < IMC_MAX_KEYS; i++ ) if( p->key[i] && STR_CEQL(p->key[i], key)) return atoi( p->value[i] ); return def; } /* * String manipulation functions, mostly exported */ /* return 'mud' from 'player@mud' */ const char *imc_mudof(const char *fullname) { static char buf[IMC_MNAME_LENGTH]; char *where; buf[0] = '\0'; if ( !fullname || fullname[0] == '\0' ) return buf; snprintf( buf, IMC_MNAME_LENGTH, "%s", (where = strchr( fullname, '@' )) != NULL ? where + 1 : fullname ); return buf; } /* return 'player' from 'player@mud' */ const char *imc_nameof(const char *fullname) { static char buf[IMC_PNAME_LENGTH]; unsigned char i = 0, j = 0; buf[0] = '\0'; if ( !fullname || fullname[0] == '\0' ) return buf; for ( i = 0, j = 0; i < IMC_PNAME_LENGTH && fullname[j] != '\0' && fullname[j] != '@'; i++, j++ ) buf[i] = fullname[j]; buf[i] = '\0'; return buf; } /* Convert a string from internetwork format to local format */ char *imc_itol( const char *name ) { static char buf[IMC_NAME_LENGTH]; unsigned char i = 0, j = 0; buf[0] = '\0'; if ( !name || name[0] == '\0' ) return buf; if ( STR_CEQL( imc_mudof( name ), "*" ) || !strchr( name, ':' )) { strlcpy( buf, name, IMC_NAME_LENGTH ); return buf; } for ( i = 0; i < IMC_NAME_LENGTH && name && name[i] != '\0' && name[i] != '@'; i++ ) { if ( name[i] == ':' ) j = i; buf[i] = name[i]; } buf[j] = '@'; buf[i] = '\0'; return buf; } /* convert a string from local format to internetwork format */ char *imc_ltoi( const char *name, AFFILIATE *a ) { char newstring[IMC_NAME_LENGTH]; static char buf[IMC_NAME_LENGTH]; unsigned char i = 0; buf[0] = '\0'; if ( !a || !name || name[0] == '\0' ) return buf; for( i = 0; name && name[i] != '\0' && i < IMC_NAME_LENGTH; i++ ) { if ( name[i] == '@' ) newstring[i] = ':'; else newstring[i] = name[i]; } newstring[i] = '\0'; snprintf( buf, IMC_NAME_LENGTH, "%s@%s", newstring, a->name ); return buf; } /* return 'e' from 'a!b!c!d!e' */ char *imc_lastinpath( const char *path ) { const char *where; static char buf[IMC_NAME_LENGTH]; where = path + strlen(path)-1; while( *where != '!' && where >= path ) where--; strlcpy( buf, where+1, IMC_NAME_LENGTH ); return buf; } /* return 'b' from 'a!b!c!d!e' */ char * imc_hubinpath( char *path, AFFILIATE *a ) { const char *separator, *where; static char buf[IMC_NAME_LENGTH]; unsigned int i = 1; buf[0] = '\0'; if ( !path || path[0] == '\0') return buf; if ( (separator = strchr( path, '!' ) ) == NULL ) return a->name; for ( where = separator + 1; *where != '!' && *where != '\0'; where++ ) i++; strlcpy( buf, separator + 1, i ); return buf; } /* return 'a' from 'a!b!c!d!e' */ char *imc_firstinpath( const char *path ) { static char buf[IMC_NAME_LENGTH]; char *p; buf[0] = '\0'; if ( !path || path[0] == '\0' ) return buf; for( p = buf; *path && *path != '!'; *p++=*path++ ) ; *p = 0; return buf; } /* get some IMC stats, return a string describing them */ char *imc_getstats( void ) { static char buf[IMC_DATA_LENGTH]; unsigned int evcount = 0; imc_event *ev = imc_event_list; while( ev ) { evcount++; ev = ev->next; } snprintf( buf, IMC_DATA_LENGTH, "~WGeneral Bridge Statistics\n\r" "~cReceived packets : ~C%ld\n\r" "~cReceived bytes : ~C%ld\n\r" "~cTransmitted packets: ~C%ld\n\r" "~cTransmitted bytes : ~C%ld\n\r" "~cMaximum packet size: ~C%d\n\r" "~cPending events : ~C%d\n\r" "~cSequence drops : ~C%d\n\r" "~cLast Bridge Boot : ~C%s\n\r", imc_stats.rx_pkts, imc_stats.rx_bytes, imc_stats.tx_pkts, imc_stats.tx_bytes, imc_stats.max_pkt, evcount, imc_stats.sequence_drops, ctime( &imc_boot ) ); return buf; } /* * imc_reminfo handling */ /* find an info entry for "name" */ REMINFO *imc_find_reminfo( const char *name, NETWORK *network ) { REMINFO *p; if ( !name || name[0] == '\0' ) return NULL; for( p = first_reminfo; p; p = p->next ) { if( STR_CEQL( name, p->name ) && network == p->network ) return p; } return NULL; } /* create a new info entry, insert into list */ REMINFO *imc_new_reminfo( char *mud, NETWORK *network ) { REMINFO *p, *mud_prev; if ( !mud || mud[0] == '\0' || !network ) return NULL; CREATE( p, REMINFO, 1 ); p->name = strdup( mud ); p->netname = NULL; p->web = NULL; p->version = NULL; p->path = NULL; p->top_sequence = 0; p->network = network; for( mud_prev = first_reminfo; mud_prev; mud_prev = mud_prev->next ) if( strcasecmp( mud_prev->name, mud ) >= 0 ) break; if( !mud_prev ) LINK( p, first_reminfo, last_reminfo, next, prev ); else INSERT( p, mud_prev, first_reminfo, next, prev ); return p; } /* delete the info entry "p" */ void imc_delete_reminfo( REMINFO *p ) { if( !first_reminfo || !p ) return; UNLINK( p, first_reminfo, last_reminfo, next, prev ); p->network = NULL; if ( p->name ) DISPOSE( p->name ); if ( p->netname ) DISPOSE( p->netname ); if ( p->web ) DISPOSE( p->web ); if ( p->version ) DISPOSE( p->version ); if ( p->path ) DISPOSE( p->path ); imc_cancel_event( NULL, p ); DISPOSE( p ); } /* get info struct for given mud */ NETWORK *imc_getnetwork( const char *mud ) { NETWORK *p; for( p = first_network; p; p = p->next ) if( STR_CEQL( mud, p->netname ) ) return p; return NULL; } /* get name of a connection */ const char *imc_getconnectname( const CONNECTION *c ) { static char buf[IMC_NAME_LENGTH]; if( c->affiliate && c->affiliate->network && c->affiliate->network->netname && c->affiliate->name ) snprintf( buf, IMC_NAME_LENGTH, "%s(as %s)", c->affiliate->network->netname, c->affiliate->name ); else snprintf( buf, IMC_NAME_LENGTH, "%s", c->ip ); return buf; } /* set up for a reconnect in 20 seconds */ void imc_setup_reconnect( AFFILIATE *a ) { int t; t = imc_next_event( ev_reconnect, a ); if( t >= 0 && t < 20) return; if ( t > 0 ) imc_cancel_event( ev_reconnect, a ); imc_add_event( 20, ev_reconnect, a ); return; } /* set up a new imc_connect struct, and link it into imc_connect_list */ CONNECTION *imc_new_connect( void ) { CONNECTION *c; CREATE( c, CONNECTION, 1 ); c->state = CONN_NONE; c->desc = -1; c->insize = IMC_MINBUF; CREATE( c->inbuf, char, c->insize ); c->outsize = IMC_MINBUF; CREATE( c->outbuf, char, c->outsize ); c->inbuf[0] = c->outbuf[0] = '\0'; c->affiliate = NULL; c->ip = NULL; c->newoutput = 0; LINK( c, first_connection, last_connection, next, prev ); 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 ) { Log( "imc_extract_connect: non-closed connection" ); return; } DISPOSE( c->inbuf ); DISPOSE( c->outbuf ); if ( c->ip ) DISPOSE( c->ip ); UNLINK( c, first_connection, last_connection, next, prev ); imc_cancel_event( NULL, c ); DISPOSE( c ); } /* update our routing table based on a packet received with path "path" */ static void updateroutes( char *path, AFFILIATE *a ) { REMINFO *p; char *sender; char *temp; if ( !path || path[0] == '\0' || !a || !a->name || a->name[0] == '\0' ) return; /* loop through each item in the path, and update routes to there */ temp = path; while( temp && temp[0] ) { sender = imc_firstinpath( temp ); if( sender && sender[0] != '\0' && !STR_CEQL( sender, a->name ) ) { /* not from us */ /* check if its in the list already */ p = imc_find_reminfo( sender, a->network ); if( !p ) /* not in list yet, create a new entry */ { p = imc_new_reminfo( sender, a->network ); p->netname = strdup("unknown"); p->web = strdup("unknown"); p->version = strdup("unknown"); } if (p->path) DISPOSE(p->path); p->path = strdup( temp ); } /* get the next item in the path */ temp = strchr( temp, '!' ); if( temp ) temp++; /* skip to just after the next '!' */ } } /* return 1 if 'name' is a part of 'path' (internal) */ bool inpath(const char *path, const char *name) { char buf[IMC_MNAME_LENGTH+3]; if ( !path || path[0] == '\0' || !name || name[0] == '\0' ) return FALSE; if (STR_CEQL(path, name)) return TRUE; snprintf(buf, IMC_MNAME_LENGTH+3, "%s!", name); if (STRN_CEQL(path, buf, strlen(buf))) return TRUE; snprintf(buf, IMC_MNAME_LENGTH+3, "!%s", name); if (strlen(buf) < strlen(path) && STR_CEQL(path + strlen(path) - strlen(buf), buf)) return TRUE; snprintf(buf, IMC_MNAME_LENGTH+3, "!%s!", name); if (strcasestr(path, buf)) return TRUE; return FALSE; } /* * Core functions (all internal) */ /* notify everyone of the closure - shogar */ void imc_close_notify( AFFILIATE *a ) { REMINFO *r = NULL, *r_next = NULL; if ( !a || !a->equiv ) return; for ( r = first_reminfo; r; r = r->next ) { PACKET out; if ( r->network != a->network ) continue; imc_initdata( &out ); strlcpy( out.type, "close-notify", IMC_TYPE_LENGTH ); strlcpy( out.to, "*@*", IMC_NAME_LENGTH ); imc_addkey( &out, "versionid", IMC_VERSIONID ); imc_addkey( &out, "host", r->name ); imc_send( &out, a->equiv ); imc_freedata( &out ); } for ( r = first_reminfo; r; r = r_next ) { r_next = r->next; if ( r->network == a->network ) imc_delete_reminfo( r ); } /* log and announce if closed via event - shogar 2/26/2000 */ Log( "Bridge has closed the connection to %s(as %s).", a->network->netname, a->name ); } /* close given connection */ void do_close( CONNECTION *c ) { if( c->state == CONN_NONE ) return; c->state = CONN_NONE; if ( c->desc ) { close( c->desc ); c->desc = 0; } if ( c->affiliate ) { c->affiliate->connection = NULL; if ( !c->affiliate->full_bridge ) { REMINFO *r = NULL, *r_next = NULL; Log( "Bridge has closed the connection to %s.", imc_getconnectname( c ) ); for ( r = first_reminfo; r; r = r_next ) { r_next = r->next; if ( r->network == c->affiliate->network ) imc_delete_reminfo( r ); } } else imc_add_event( 60, ev_close_notify, c->affiliate ); /* Handle reconnects */ imc_setup_reconnect( c->affiliate ); c->affiliate = NULL; } else Log( "Bridge has closed the connection to %s.", imc_getconnectname( c ) ); c->inbuf[0] = '\0'; c->outbuf[0] = '\0'; } /* read waiting data from descriptor. * read to a temp buffer to avoid repeated allocations */ static void do_read( CONNECTION *c ) { unsigned int size; int r; char temp[IMC_MAXBUF]; char *newbuf; unsigned int newsize; r = read( c->desc, temp, IMC_MAXBUF-1 ); if( !r || ( r < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) ) { if( r < 0 ) /* read error */ Log( "%s: do_read: %s", imc_getconnectname( c ), strerror(errno) ); else /* socket was closed */ Log( "%s: do_read: connection closed from other side", imc_getconnectname( c ) ); do_close( c ); return; } if( r < 0 ) /* EAGAIN error */ return; temp[r] = '\0'; size = strlen( c->inbuf ) + r + 1; 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; } strlcat( c->inbuf, temp, c->insize ); imc_stats.rx_bytes += r; } /* write to descriptor */ static void do_write( CONNECTION *c ) { unsigned int size = 0; int w = 0; if( c->state == CONN_SERVERCONNECT ) { /* Wait for server password */ c->state = CONN_WAITSERVERPWD; return; } size = strlen( c->outbuf ); if( size == 0 ) /* nothing to write */ return; w = write( c->desc, c->outbuf, size ); if( !w || ( w < 0 && errno != EAGAIN && errno != EWOULDBLOCK ) ) { if( w < 0 ) /* write error */ Log( "%s: do_write: %s.", imc_getconnectname( c ), strerror(errno) ); else /* socket was closed */ Log( "%s: do_write: connection closed from other side", imc_getconnectname( c ) ); do_close( c ); return; } if( w < 0 ) /* EAGAIN */ return; strlcpy( c->outbuf, c->outbuf + w, c->outsize ); imc_stats.tx_bytes += w; } /* put a line onto descriptors output buffer */ static void do_send( CONNECTION *c, const char *line ) { unsigned int len; char *newbuf; unsigned int newsize = c->outsize; if( c->state == CONN_NONE ) return; if( !c->outbuf[0] ) c->newoutput = 1; len = strlen( c->outbuf ) + strlen( line ) + 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->outsize ); strlcat( c->outbuf, "\n\r", c->outsize ); } /* 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 *getline(char *buffer, size_t len) { unsigned 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; } static int memory_head; /* next entry in memory table to use, wrapping */ /* checkrepeat: check for repeats in the memory table */ bool checkrepeat(const char *mud, unsigned long seq, AFFILIATE *a ) { unsigned int i; for (i=0; i<IMC_MEMORY; i++) if (imc_memory[i].from && seq == imc_memory[i].sequence && STR_CEQL(mud, imc_memory[i].from) && a == imc_memory[i].affiliate ) return TRUE; /* not a repeat, so log it */ if (imc_memory[memory_head].from) DISPOSE(imc_memory[memory_head].from); imc_memory[memory_head].from = strdup(mud); imc_memory[memory_head].sequence = seq; imc_memory[memory_head].affiliate = a; memory_head++; if (memory_head==IMC_MEMORY) memory_head=0; return FALSE; } /* send a packet to a mud using the right version */ static void do_send_packet( CONNECTION *c, PACKET *p, AFFILIATE *a ) { const char *output; unsigned char v; v = c->version; if( v > IMC_VERSION ) v = IMC_VERSION; output = ( *imc_vinfo[v].generate )(p, a); if( output ) { imc_stats.tx_pkts++; if( strlen( output ) > imc_stats.max_pkt ) imc_stats.max_pkt = strlen( output ); do_send( c, output ); } } /* checks if a player and/or mud is on a network's blacklist */ bool is_blacklisted( char *name, NETWORK *network ) { char arg[SSS]; const char *p; if ( !name || name[0] == '\0' || !network ) return TRUE; if ( !network->blacklist || network->blacklist[0] == '\0' ) return FALSE; p = imc_getarg( network->blacklist, arg, SSS ); while ( arg && arg[0] ) { if ( strcasestr( name, arg ) ) return TRUE; p = imc_getarg( p, arg, SSS ); } return FALSE; } /* Checks if a packet is allowed to be forwarded to another network - default is reject */ bool can_forward( PACKET *p, AFFILIATE *a, bool sendlocal ) { if ( STR_CEQL( p->type, "keepalive-request" ) ) { if ( sendlocal ) return TRUE; } else if ( STR_CEQL( p->type, "is-alive" ) || STR_CEQL( p->type, "close-notify" ) ) { if ( a->full_bridge || sendlocal ) return TRUE; } else if ( STR_CEQL( p->type, "ice-msg-r" ) || STR_CEQL( p->type, "ice-msg-b" ) ) { char channel_name[SSS]; strlcpy( channel_name, find_channel_equivalent( imc_getkey( p, "channel", "" ), a->network, a->equiv->network ), SSS ); /* if channel is bridged, convert sender and channel keys */ if ( channel_name && channel_name[0] != '\0' ) { char sender[IMC_NAME_LENGTH]; if ( !a->full_bridge ) { strlcpy( sender, imc_ltoi( imc_getkey( p, "sender", "" ), a->equiv ), IMC_NAME_LENGTH ); imc_addkey( p, "sender", sender ); } imc_addkey( p, "channel", channel_name ); imc_addkeyi( p, "echo", 0 ); return TRUE; } } else if ( STR_CEQL( p->type, "ping" ) ) { if ( a->full_bridge ) return TRUE; } else if ( STR_CEQL( p->type, "tell" ) || STR_CEQL( p->type, "beep" ) || STR_CEQL( p->type, "who-reply" ) || STR_CEQL( p->type, "who" ) || STR_CEQL( p->type, "ping-reply" ) || STR_CEQL( p->type, "whois" ) || STR_CEQL( p->type, "whois-reply" )) return TRUE; return FALSE; } /* forward a packet - main routing function, all packets pass through here */ static void forward( PACKET *p, AFFILIATE *a, bool readlocal ) { REMINFO *route = NULL; AFFILIATE *direct = NULL; const char *to; bool isbroadcast = FALSE, sendlocal = FALSE; /* check for duplication, and register the packet in the sequence memory */ if( p->i.sequence && checkrepeat( imc_mudof( p->i.from ), p->i.sequence, a ) ) return; /* find the locally sent packets */ if ( a->full_bridge ) sendlocal = STR_CEQL( imc_mudof( p->i.from ), a->name ); else sendlocal = STR_CEQL( p->i.from, "*" ); /* check for packets we've already forwarded */ if( inpath( p->i.path, a->name ) || ( !a->full_bridge && STR_CEQL( imc_mudof( p->i.from ), a->name ) ) || ( isbroadcast && strchr( p->i.from, ':' ) ) ) return; /* update our routing info */ updateroutes( p->i.path, a ); /* check for really old packets */ if( (route = imc_find_reminfo( imc_mudof( p->i.from ), a->network )) != NULL ) { if( ( p->i.sequence+IMC_PACKET_LIFETIME ) < route->top_sequence ) { imc_stats.sequence_drops++; return; } if( p->i.sequence > route->top_sequence ) route->top_sequence = p->i.sequence; } /* if we received an is-alive, let it go through a full bridge */ if ( readlocal && STR_CEQL( p->type, "is-alive" ) ) strlcpy( p->i.to, "*@*", IMC_NAME_LENGTH ); to = imc_mudof( p->i.to ); isbroadcast = STR_EQL( to, "*" ); /* broadcasts are, well, broadcasts */ /* Make sure they are not blacklisted */ if ( a->equiv && is_blacklisted( p->i.from, a->equiv->network ) && !STR_CEQL( p->type, "is-alive" ) && !STR_CEQL( p->type, "close-notify" ) && !STR_CEQL( p->type, "who-reply" ) && !STR_CEQL( p->type, "whois-reply" ) && !STR_CEQL( p->type, "ping-reply" ) ) { if ( !isbroadcast ) { char newstring[SSS]; /* Let's be polite and tell them if they are blacklisted */ snprintf( newstring, SSS, "You have been blacklisted from the %s network.", a->equiv->network->netname ); imc_send_tell( p->i.from, newstring, a ); Log( "%s was denied access to %s(packet type: %s to: %s).", p->i.from, a->equiv->network->netname, p->type, p->i.to ); return; } return; } /* forward to our bridge if it's for us */ if( readlocal ) { strlcpy( p->to, imc_nameof( p->i.to ), IMC_NAME_LENGTH ); /* strip the name from the 'to' */ strlcpy( p->from, p->i.from, IMC_NAME_LENGTH ); imc_recv( p, a ); /* if its only to us (ie. not broadcast) don't forward it */ if ( !isbroadcast ) return; } /* check if we should just drop it (policy rules) */ if( !can_forward(p, a, sendlocal) ) return; /* if it was not made by us, have it go to other side of the bridge */ if ( !sendlocal ) a = a->equiv; /* convert 'to' fields that we have a route for to a hop along the route */ if( !isbroadcast && ( route = imc_find_reminfo( to, a->network ) ) != NULL && route->path != NULL && !inpath( p->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( !STR_CEQL( to, imc_lastinpath(route->path) ) && a != NULL && a->connection ) direct = a; to = imc_lastinpath(route->path); } /* check for a direct connection */ if( ( !a || !a->connection ) && ( !direct || !direct->connection ) ) { Log( "No path available for packet sent from %s(%s) to %s(%s) of type %s.", imc_mudof(p->i.from), a->network->netname, p->i.to, a->name, p->type ); return; } /* bridge should be only thing seen if just a relay */ if ( !a->full_bridge ) { strlcpy( p->from, imc_ltoi( p->i.from, a ), IMC_NAME_LENGTH ); strlcpy( p->i.from, p->from, IMC_NAME_LENGTH ); p->i.sequence = imc_sequencenumber++; if ( !imc_sequencenumber ) imc_sequencenumber++; p->i.path[0] = 0; } /* but only if they haven't seen it (sanity check) */ if( a && a->connection && !inpath( p->i.path, a->name ) ) do_send_packet( a->connection, p, a ); /* send on direct connection, if we have one */ if( direct && direct != a && direct->connection && !inpath( p->i.path, direct->name ) ) do_send_packet( direct->connection, p, a ); } /* handle a password response from a server */ static void serverpassword( CONNECTION *c, const char *argument ) { const char *p; char type[3], name[IMC_MNAME_LENGTH], pw[IMC_PW_LENGTH], version[20]; p = imc_getarg( argument, type, 3 ); /* has to be PW */ p = imc_getarg( p, name, IMC_MNAME_LENGTH ); p = imc_getarg( p, pw, IMC_PW_LENGTH ); p = imc_getarg( p, version, 20 ); if( !STR_CEQL( type, "PW" ) ) { Log( "%s: non-PW password packet on connect: %s", imc_getconnectname( c ), argument ); do_close( c ); return; } if( !c->affiliate || !STR_EQL( c->affiliate->network->serverpw, pw ) ) { Log( "%s: password failure for %s", imc_getconnectname( c ), name ); do_close( c ); return; } if( c->affiliate->connection ) /* kill old connections */ do_close( c->affiliate->connection ); c->affiliate->connection = c; c->state = CONN_COMPLETE; /* check for a version string (assume version 2 if not present) */ if( sscanf( version, "version=%hu", &c->version ) != 1 ) c->version = 2; /* check for generator/interpreter */ if( !imc_vinfo[c->version].generate || !imc_vinfo[c->version].interpret ) { Log( "%s: unsupported version %d on connect", imc_getconnectname(c), c->version ); do_close( c ); return; } Log( "%s: connected (version %d)", imc_getconnectname(c), c->version ); imc_cancel_event( ev_login_timeout, c ); imc_cancel_event( ev_reconnect, c->affiliate ); imc_cancel_event( ev_close_notify, c->affiliate ); imc_request_keepalive( c->affiliate ); imc_send_keepalive( NULL, "*", c->affiliate ); } /* connect to networks and clear stats */ bool imc_startup_network( void ) { NETWORK *network = NULL; imc_stats.rx_pkts = 0; imc_stats.tx_pkts = 0; imc_stats.rx_bytes = 0; imc_stats.tx_bytes = 0; imc_stats.sequence_drops = 0; Log( "%s", "Bridge will attempt to connect to the loaded networks." ); /* do autoconnects */ for( network = first_network; network; network = network->next ) { AFFILIATE *af = NULL; for ( af = network->first_affiliate; af; af = af->next ) imc_connect_to( af ); } return TRUE; } /* begin bridge processes */ void imc_startup( void ) { imc_now=time(NULL); /* start our clock */ imc_boot=imc_now; Log("%s initializing.", IMC_VERSIONID); imc_sequencenumber=imc_now; if (!imc_readconfig() || !imc_startup_network()) { Log( "Unable to complete startup procedures - aborting." ); exit(1); } } /* print stats and clear memory */ void imc_shutdown_network( void ) { imc_event *ev, *ev_next; CONNECTION *c, *c_next; REMINFO *p, *pnext; Log( "Received %ld bytes(%ld packets) total.", imc_stats.rx_bytes, imc_stats.rx_pkts ); Log( "Received %d age-dropped packets.", imc_stats.sequence_drops ); Log( "Sent %ld bytes(%ld packets) total.", imc_stats.tx_bytes, imc_stats.tx_pkts ); Log( "The largest packet sent or received was %d bytes.", imc_stats.max_pkt ); for( c = first_connection; c; c = c_next ) { c_next = c->next; do_close( c ); imc_extract_connect( c ); } for( p = first_reminfo; p; p = pnext ) { pnext = p->next; imc_delete_reminfo( p ); } for( ev = imc_event_list; ev; ev = ev_next ) { ev_next = ev->next; ev->next = NULL; DISPOSE( ev ); } for( ev = imc_event_free; ev; ev = ev_next ) { ev_next = ev->next; ev->next = NULL; DISPOSE( ev ); } imc_event_list = imc_event_free = NULL; } /* delete a bridge channel struct */ void imc_delete_channel( CHANNEL *c ) { if ( !c ) return; UNLINK( c, first_channel, last_channel, next, prev ); if ( c->local_channels ) DISPOSE( c->local_channels ); DISPOSE( c ); return; } /* delete a network struct */ void imc_delete_network( NETWORK *i ) { CONNECTION *c; AFFILIATE *d, *d_next; if ( !i ) return; for( c = first_connection; c; c = c->next ) if( c->affiliate && c->affiliate->network == i ) do_close( c ); UNLINK( i, first_network, last_network, next, prev ); if( i->netname ) DISPOSE( i->netname ); if( i->host ) DISPOSE( i->host ); if( i->clientpw ) DISPOSE( i->clientpw ); if( i->serverpw ) DISPOSE( i->serverpw ); if( i->blacklist ) DISPOSE( i->blacklist ); for ( d = i->first_affiliate; d; d = d_next ) { d_next = d->next; UNLINK( d, i->first_affiliate, i->last_affiliate, next, prev ); if ( d->name ) DISPOSE( d->name ); d->equiv = NULL; d->network = NULL; imc_cancel_event( NULL, d ); DISPOSE( d ); } imc_cancel_event( NULL, i ); DISPOSE( i ); return; } /* announce shutdown and start clearing memory */ void imc_shutdown( void ) { NETWORK *network, *network_next; CHANNEL *channel, *channel_next; Log( "Shutting down %s.", IMC_VERSIONID ); for( network = first_network; network; network = network_next ) { network_next = network->next; imc_delete_network( network ); } for( channel = first_channel; channel; channel = channel_next ) { channel_next = channel->next; imc_delete_channel( channel ); } imc_shutdown_network(); exit(0); } /* interpret an incoming packet using the right version */ static PACKET *do_interpret_packet( CONNECTION *c, const char *line ) { unsigned char v; PACKET *p; if( !line[0] ) return NULL; v = c->version; if( v > IMC_VERSION ) v = IMC_VERSION; p = ( *imc_vinfo[v].interpret )( line ); return p; } /* set up descriptors to be checked with select */ int imc_fill_fdsets( fd_set *sread, fd_set *swrite, fd_set *exc ) { CONNECTION *c; int maxfd = 0; /* set up fd_sets for select */ for( c = first_connection; c; c = c->next ) { if( maxfd < c->desc ) maxfd = c->desc; if ( c->state == CONN_NONE ) continue; if ( c->state == CONN_SERVERCONNECT ) { FD_SET( c->desc, swrite ); continue; } FD_SET( c->desc, sread ); if( c->outbuf[0] ) FD_SET( c->desc, swrite ); } return maxfd; } /* shell around imc_idle_select */ void imc_idle(unsigned int s) { fd_set sread, swrite, exc; int maxfd; struct timeval timeout; int i; FD_ZERO(&sread); FD_ZERO(&swrite); FD_ZERO(&exc); maxfd=imc_fill_fdsets(&sread, &swrite, &exc); timeout.tv_sec = s; timeout.tv_usec = 0; if (maxfd) while ((i=select(maxfd+1, &sread, &swrite, &exc, &timeout)) < 0 && errno == EINTR) /* loop, ignoring signals */ ; else while ((i=select(0, NULL, NULL, NULL, &timeout)) < 0 && errno == EINTR) ; if ( i < 0 ) { Log( "%s", "imc_idle: select call failed" ); imc_shutdown(); return; } imc_idle_select(&sread, &swrite, &exc, time(NULL)); } /* * Events */ /* time out a login */ void ev_login_timeout( void *data ) { CONNECTION *c = (CONNECTION *)data; Log( "%s: login timeout", imc_getconnectname( c ) ); do_close( c ); } /* make it an event, dont announce a simple reboot - shogar - 2/1/2000 */ void ev_close_notify( void *data ) { AFFILIATE *a = (AFFILIATE *)data; imc_close_notify(a); } /* try a reconnect to the given imc_info */ void ev_reconnect( void *data ) { AFFILIATE *affiliate = (AFFILIATE *)data; if( !affiliate->connection ) imc_connect_to( affiliate ); } 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 */ { DISPOSE( p ); } else { p->next=imc_event_free; imc_event_free=p; event_freecount++; } } void imc_add_event(int when, void (*callback)(void *), 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 CREATE( p, imc_event, 1 ); 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; return; } void imc_cancel_event(void (*callback)(void *), 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)(void *), 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)(void *), 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; } unsigned 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)(void *); 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 Log("imc_run_events: NULL callback"); } imc_now=newtime; } /* low-level idle function: read/write buffers as needed, etc */ void imc_idle_select( fd_set *sread, fd_set *swrite, fd_set *exc, time_t now ) { const char *command; PACKET *p; CONNECTION *c, *c_next; if( imc_sequencenumber < (unsigned long)imc_now ) imc_sequencenumber = (unsigned long)imc_now; imc_run_events(now); /* handle results of the select */ for( c = first_connection; c; c = c_next ) { c_next = c->next; if( c->state != CONN_NONE && FD_ISSET( c->desc, exc ) ) do_close( c ); if( c->state != CONN_NONE && FD_ISSET( c->desc, sread ) ) do_read( c ); while( c->state != CONN_NONE && ( command = getline( c->inbuf, c->insize ) ) != NULL ) { if( strlen( command ) > imc_stats.max_pkt ) imc_stats.max_pkt = strlen( command ); switch( c->state ) { case CONN_WAITSERVERPWD: serverpassword( c, command ); break; case CONN_COMPLETE: p = do_interpret_packet( c, command ); if( p ) { char mud[IMC_NAME_LENGTH]; char *po = NULL; imc_stats.rx_pkts++; mud[0] = '\0'; /* if a full bridge, don't do anything special */ if ( !c->affiliate->full_bridge ) { /* convert a finger request to a regular who or a proper internetwork finger */ /* also allows for player searches */ if ( STR_CEQL( p->type, "who" ) && sscanf( imc_getkey( p, "type", "unknown" ), "finger %60s", mud ) == 1 ) { if ( (po = strchr( mud, ':' )) == NULL ) { imc_addkey( p, "type", "who" ); snprintf( p->i.to, IMC_NAME_LENGTH, "*@%s", mud ); } else { char newstring[SSS]; *po = '@'; /* Send whois if it is formatted right */ if ( STR_CEQL( imc_mudof(mud), "*" ) ) { strlcpy( p->type, "whois", IMC_TYPE_LENGTH ); imc_addkey( p, "type", "" ); snprintf( p->i.to, IMC_NAME_LENGTH, "%s@*", imc_nameof(mud) ); } else { strlcpy( p->i.to, mud, IMC_NAME_LENGTH ); snprintf( newstring, SSS, "finger %s", imc_nameof(p->i.to) ); imc_addkey( p, "type", newstring ); } } } /* if we have an entry with the same name as hub, make sure things * don't break */ else if ( strchr( p->i.to, ':' ) ) strlcpy( mud, "boo", IMC_NAME_LENGTH ); /* convert to from internetwork format to local format */ strlcpy( p->to, imc_itol( p->i.to ), IMC_NAME_LENGTH ); strlcpy( p->i.to, p->to, IMC_NAME_LENGTH ); } /* decide if it was directly for the bridge or not */ if ( (!mud || mud[0] == '\0') && (STR_CEQL( imc_mudof(p->i.to), c->affiliate->name ) || STR_CEQL( imc_mudof( p->i.to ), "*" ) ) ) forward(p, c->affiliate, TRUE ); else forward(p, c->affiliate, FALSE ); imc_freedata( p ); } break; } } } for( c = first_connection; c; c = c_next ) { c_next = c->next; if( c->state != CONN_NONE && ( FD_ISSET( c->desc, swrite ) || c->newoutput ) ) { do_write( c ); c->newoutput = c->outbuf[0]; } } for( c = first_connection; c; c = c_next ) { c_next = c->next; if( c->state == CONN_NONE ) imc_extract_connect( c ); } } /* connect to given mud */ bool imc_connect_to( AFFILIATE *i ) { CONNECTION *c; int desc; struct sockaddr_in sa; char namestring[SSS]; char buf[IMC_DATA_LENGTH]; int r; if ( !i ) { Log( "%s", "imc_connect_to: No affiliate information provided" ); return 0; } snprintf( namestring, SSS, "%s(as %s)", i->network->netname, i->name ); if( i->connection ) { Log( "imc_connect_to: %s - already connected", namestring ); return 0; } Log( "imc_connect_to: connecting to %s", namestring ); if ( !i->network || !i->network->host || i->network->host[0] == '\0' ) { Log( "imc_connect_to: host field null to %s", namestring ); return 0; } /* warning: this blocks. It would be better to farm the query out to * another process, but that is difficult to do without lots of changes * to the core mud code. You may want to change this code if you have an * existing resolver process running. */ if( !inet_aton( i->network->host, &sa.sin_addr ) ) { struct hostent *hostinfo; if( ( hostinfo = gethostbyname( i->network->host ) ) == NULL ) { Log( "imc_connect_to: hostname could not be resolved" ); return 0; } memcpy( &sa.sin_addr, hostinfo->h_addr, (size_t) hostinfo->h_length ); } sa.sin_port = htons( (unsigned short) i->network->port ); sa.sin_family = AF_INET; memset(&(sa.sin_zero), '\0', 8); // zero the rest of the struct desc = socket( AF_INET, SOCK_STREAM, 0 ); if( desc < 0 ) { Log( "imc_connect_to: socket call failure" ); return 0; } r = fcntl( desc, F_GETFL, 0 ); if( r < 0 || fcntl( desc, F_SETFL, O_NONBLOCK | r ) < 0 ) { Log( "imc_connect_to: fcntl call failure" ); close( desc ); return 0; } if( connect( desc, ( struct sockaddr * )&sa, sizeof( sa ) ) < 0 ) { if( errno != EINPROGRESS ) { Log( "imc_connect_to: connection could not be established" ); close( desc ); return 0; } } c = imc_new_connect(); c->desc = desc; c->state = CONN_SERVERCONNECT; c->affiliate = i; c->ip = strdup( inet_ntoa( sa.sin_addr ) ); imc_add_event( IMC_LOGIN_TIMEOUT, ev_login_timeout, c ); snprintf( buf, IMC_DATA_LENGTH, "PW %s %s version=%d", i->name, i->network->clientpw, IMC_VERSION ); do_send( c, buf ); return 1; } void imc_send(PACKET *p, AFFILIATE *a) { /* initialize packet fields that the caller shouldn't/doesn't set */ p->i.path[0] = 0; p->i.sequence = imc_sequencenumber++; if (!imc_sequencenumber) imc_sequencenumber++; strlcpy(p->i.to, p->to, IMC_NAME_LENGTH); strlcpy(p->from, "*", IMC_NAME_LENGTH ); /* full bridge packets don't get converted to internetwork format */ if ( a->full_bridge ) snprintf( p->i.from, IMC_NAME_LENGTH, "%s@%s", p->from, a->name ); else strlcpy(p->i.from, p->from, IMC_NAME_LENGTH ); forward( p, a, FALSE ); } /* make a new network struct */ NETWORK *imc_new_network( void ) { NETWORK *i; CREATE( i, NETWORK, 1 ); i->netname = NULL; i->host = NULL; i->port = 0; i->clientpw = NULL; i->serverpw = NULL; i->blacklist = NULL; i->first_affiliate = NULL; i->last_affiliate = NULL; LINK( i, first_network, last_network, next, prev ); return i; } /* make a new bridge channel struct */ CHANNEL *imc_new_channel( char *local_channels ) { CHANNEL *c = NULL; if ( !local_channels || local_channels[0] == '\0' ) return NULL; CREATE( c, CHANNEL, 1 ); c->local_channels = strdup( local_channels ); LINK( c, first_channel, last_channel, next, prev ); return c; } /* find an equivalent bridged channel for the one that was sent */ char *find_channel_equivalent( const char *from, NETWORK *from_network, NETWORK *to_network ) { CHANNEL *c = NULL; const char *p = NULL; static char channame[SSS]; char newstring[SSS]; channame[0] = '\0'; if ( !from || from[0] == '\0' || !from_network || !to_network ) return channame; snprintf( newstring, SSS, "%s:%s", from_network->netname, from ); for ( c = first_channel; c; c = c->next ) { if ( strcasestr( c->local_channels, newstring ) ) break; } if ( !c ) return channame; p = imc_getarg( c->local_channels, newstring, SSS ); while ( newstring && newstring[0] != '\0' ) { char *d; if ( (d = strcasestr( newstring, to_network->netname )) != NULL && *d == newstring[0] ) break; p = imc_getarg( p, newstring, SSS ); } if ( !newstring || newstring[0] == '\0' || (p = strchr( newstring, ':' ) ) == NULL ) return channame; strlcpy( channame, p + 1, SSS ); return channame; } /* read in data for a bridge */ void read_bridge( char *affiliates, NETWORK *n, bool fullbridge ) { char arg[SSS]; const char *p; if ( !affiliates || affiliates[0] == '\0' || !n ) return; p = imc_getarg( affiliates, arg, SSS ); while ( arg && arg[0] ) { if ( !STR_CEQL( arg, n->netname ) ) { AFFILIATE *a; CREATE( a, AFFILIATE, 1 ); a->name = strdup(arg); a->network = n; a->equiv = NULL; a->connection = NULL; a->full_bridge = fullbridge; LINK( a, n->first_affiliate, n->last_affiliate, next, prev ); } p = imc_getarg( p, arg, SSS ); } return; } /* link up both sides of a bridge */ void complete_bridge( AFFILIATE *a ) { NETWORK *n = NULL; AFFILIATE *f = NULL; if ( !a ) return; for ( n = first_network; n; n = n->next ) if ( STR_CEQL( n->netname, a->name ) ) break; if ( !n ) return; for ( f = n->first_affiliate; f; f = f->next ) if ( STR_CEQL( a->network->netname, f->name ) ) break; if ( !f ) return; a->equiv = f; if ( f->equiv == NULL ) f->equiv = a; } /* read config file */ bool imc_readconfig( void ) { FILE *cf; NETWORK *n = NULL; AFFILIATE *a = NULL; unsigned char count = 0; char c = '\0'; if ( !(cf = fopen( IMC_CONFIG_FILE, "r" )) ) { Log( "Configuration file could not be opened for reading."); return FALSE; } while ( (c = fgetc( cf ) ) != EOF ) { if ( ferror( cf ) ) { Log( "%s", "An error occured on an attempted read of the configuration file." ); SFCLOSE( cf ); return FALSE; } ungetc( c, cf ); if ( c == '#' || c == '\n' || c == '\r' ) { while ( c != '\n' && c != '\r' && c != EOF ) c = fgetc(cf); while ( c == '\n' || c == '\r' ) c = fgetc(cf); ungetc( c, cf ); } else if ( c == 'B' ) { char local_channels[SSS]; if ( fscanf( cf, "Bridged-Channel %255[^\n]\n", local_channels ) != 1 ) { Log( "Bad bridged-channel entry encountered in configuration file." ); continue; } imc_new_channel( local_channels ); } else if ( c == 'F' ) { char affiliates[LSS]; char arg[SSS]; const char *p; if ( fscanf( cf, "Full-Bridge %255[^\n]\n", affiliates ) != 1 ) { Log( "Bad full-bridge entry encountered in the configuration file." ); continue; } p = imc_getarg( affiliates, arg, SSS ); while ( arg && arg[0] ) { for ( n = first_network; n; n = n->next ) { if ( STR_CEQL( n->netname, arg ) ) { read_bridge( affiliates, n, TRUE ); break; } } p = imc_getarg( p, arg, SSS ); } } else if ( c == 'N' ) { char netname[LSS], host[LSS], clientpwd[LSS], serverpwd[LSS], blacklist[LSS]; unsigned short port = 0; if ( fscanf( cf, "Network %11[^\n]\n" "Connection %255[^:]:%hu:%19[^:]:%19[^\n]\n" "Blacklist %255[^\n]\n\n", netname, host, &port, clientpwd, serverpwd, blacklist ) != 6 ) { Log( "Bad network entry encountered in the configuration file." ); continue; } n = imc_new_network(); n->netname = strdup( netname ); n->host = strdup(host); n->port = port; n->clientpw = strdup( clientpwd ); n->serverpw = strdup( serverpwd ); if ( STR_CEQL( blacklist, "none" ) ) n->blacklist = strdup( "" ); else n->blacklist = strdup( blacklist ); count++; } else if ( c == 'R' ) { char affiliates[LSS]; char arg[SSS]; const char *p; if ( fscanf( cf, "Relay-Bridge %255[^\n]\n", affiliates ) != 1 ) { Log( "Bad relay-bridge entry encountered in the configuration file." ); continue; } p = imc_getarg( affiliates, arg, SSS ); while ( arg && arg[0] ) { for ( n = first_network; n; n = n->next ) { if ( STR_CEQL( n->netname, arg ) ) { read_bridge( affiliates, n, FALSE ); break; } } p = imc_getarg( p, arg, SSS ); } } else { Log( "Invalid line encountered in the configuration file." ); while ( c != '\n' && c != '\r' && c != EOF ) c = fgetc(cf); while ( c == '\n' || c == '\r' ) c = fgetc(cf); ungetc( c, cf ); } } SFCLOSE(cf); for ( n = first_network; n; n = n->next ) for ( a = n->first_affiliate; a; a = a->next ) if ( a->equiv == NULL ) complete_bridge( a ); if ( count < 2 ) { Log( "Bridge needs at least two loaded networks to function properly - aborting" ); return FALSE; } return TRUE; } /* display a listing of network connections for a single network */ const char *imc_list( AFFILIATE *a ) { static char buf[IMC_DATA_LENGTH]; REMINFO *p; unsigned int count = 1; snprintf( buf, IMC_DATA_LENGTH, "~WNetwork Connection Listings for %s:\n\r" "~g%-15.15s ~c%-35.35s ~G%-10.10s ~g%-10.10s" "\n\r\n\r~g%-15.15s ~c%-35.35s ~G%-10.10s ~g%-10.10s", a->name, "Name", "IMC2 Version", "Network", "Hub", a->equiv->name, IMC_VERSIONID, a->equiv->name, "Bridge" ); for( p = first_reminfo; p; p = p->next ) { if ( p->network != a->equiv->network ) continue; snprintf( buf + strlen(buf), IMC_DATA_LENGTH - strlen(buf), "\n\r~g%-15.15s ~c%-35.35s ~G%-10.10s ~g%-10.10s", p->name, p->version, STR_CEQL( p->netname, "unknown" ) ? a->name : p->netname, imc_hubinpath(p->path, a)); count++; } snprintf( buf + strlen( buf ), IMC_DATA_LENGTH - strlen(buf), "\n\r~W%d active connections found.\n\r", count ); return buf; } /* send a keepalive request to everyone - shogar */ void imc_request_keepalive( AFFILIATE *a ) { PACKET out; imc_initdata( &out ); strlcpy( out.type, "keepalive-request", IMC_TYPE_LENGTH ); strlcpy( out.to, "*@*", IMC_NAME_LENGTH ); imc_addkey( &out, "versionid", IMC_VERSIONID ); imc_send( &out, a ); imc_freedata( &out ); } /* called when a keepalive has been received */ void imc_recv_keepalive( const char *from, const char *version, const char *networkname, const char *web, AFFILIATE *a) { REMINFO *p; if( STR_CEQL( from, a->name ) ) return; /* this should never fail, imc.c should create an entry if one doesn't exist (in the path update code) */ p = imc_find_reminfo( from, a->network ); if( !p ) /* boggle */ return; if( !STR_CEQL( version, p->version ) ) /* remote version has changed? */ { DISPOSE( p->version ); /* if so, update it */ p->version = strdup( version ); } if( !STR_CEQL( networkname, p->netname ) ) { DISPOSE( p->netname ); p->netname = strdup( networkname ); } if( !STR_CEQL( web, p->web ) ) { DISPOSE( p->web ); p->web = strdup( web ); } } /* called when a ping request is received */ void imc_recv_ping(const char *from, const char *path, AFFILIATE *a) { /* ping 'em back */ imc_send_pingreply(from, path, a); } /* called when a bridge information requesting packet is received */ void imc_recv_who( PACKET *p, const char *pname, const char *type, AFFILIATE *a ) { char arg[IMC_DATA_LENGTH]; type = imc_getarg( type, arg, IMC_DATA_LENGTH ); if( STR_CEQL( arg, "list" ) || STR_CEQL( arg, "who" ) ) imc_send_whoreply( pname, imc_list(a), a ); else if( STR_CEQL( arg, "info" ) ) imc_send_whoreply( pname, imc_getstats( ), a ); else if( STR_CEQL( arg, "help" ) || STR_CEQL( arg, "services" ) ) imc_send_whoreply( pname, "~WAvailable imcminfo types:\n\r" "~chelp ~W- ~Cthis list\n\r" "~cinfo ~W- ~Cbridge information\n\r" "~cwho ~W- ~Clist known muds on IMC\n\r" "~cfinger <mud> ~W- ~Cshow a mud's who listing\n\r" "~cfinger <player:mud> ~W- ~Cshow a player's information\n\r" "~cfinger <player:*> ~W- ~Csearch for a player\n\r", a ); else imc_send_whoreply( pname, "Sorry, no information of that type is available.", a ); } /* handle a packet destined for us, or a broadcast */ void imc_recv( PACKET *p, AFFILIATE *a ) { /* who: receive a who request */ if( STR_CEQL( p->type, "who" ) ) { imc_recv_who( p, p->from, imc_getkey( p, "type", "who" ), a ); } /* is-alive: receive a keepalive (broadcast) */ else if( STR_CEQL( p->type, "is-alive" ) ) { Log( "%s: Received is-alive from %s.", imc_getconnectname( a->connection ), imc_mudof( p->from ) ); imc_recv_keepalive( imc_mudof( p->from ), imc_getkey( p, "versionid", "unknown" ), imc_getkey( p, "networkname", "unknown"), imc_getkey( p, "url", "unknown"), a ); } /* ping: receive a ping request */ else if( STR_CEQL( p->type, "ping" ) ) { imc_recv_ping( imc_mudof( p->from ), p->i.path, a ); } /* handle keepalive requests - shogar */ else if( STR_CEQL( p->type, "keepalive-request" ) ) { struct imc_keepalive *k = NULL; Log( "%s: Keepalive request received from %s.", imc_getconnectname( a->connection ), imc_mudof(p->from) ); /* send cached network listing if full bridge, otherwise just send bridge's is-alive */ if ( a->full_bridge ) { CREATE( k, struct imc_keepalive, 1 ); strlcpy( k->destination, imc_mudof(p->from), IMC_MNAME_LENGTH ); k->count = 0; k->remote = first_reminfo; k->affiliate = a; imc_add_event( 1, ev_send_isalive, k ); } else { char newstring[IMC_NAME_LENGTH]; strlcpy( newstring, imc_mudof( p->from ), IMC_NAME_LENGTH ); imc_send_keepalive( NULL, newstring, a ); } } /* expire closed hubs - shogar */ else if( STR_CEQL( p->type, "close-notify" ) ) { REMINFO *r = NULL; Log( "%s: Close-notify packet for %s received from %s.", imc_getconnectname( a->connection ), imc_getkey( p, "host", "unknown" ), p->from ); if ((r = imc_find_reminfo(imc_getkey(p, "host", ""), a->network )) != NULL ) { REMINFO *o, *o_next; /* if it's a full bridge, inform the other side of the expire */ if ( a->full_bridge ) { PACKET out; imc_initdata( &out ); strlcpy( out.type, "close-notify", IMC_TYPE_LENGTH ); strlcpy( out.to, "*@*", IMC_NAME_LENGTH ); imc_addkey( &out, "versionid", IMC_VERSIONID ); imc_addkey( &out, "host", r->name ); imc_send( &out, a->equiv ); imc_freedata( &out ); } /* if there is no path to them, take them off the list */ for ( o = first_reminfo; o; o = o_next ) { o_next = o->next; if ( STR_CEQL( r->name, imc_hubinpath( o->path, a ) ) ) imc_delete_reminfo( o ); } imc_delete_reminfo( r ); } } } /* Commands called by the interface layer */ /* send a tell to a remote player */ void imc_send_tell( const char *to, const char *argument, AFFILIATE *a ) { PACKET out; if( STR_EQL( imc_mudof(to), "*" ) ) return; /* don't let them do this */ imc_initdata(&out); strcpy( out.to, to ); strcpy( out.type, "tell" ); imc_addkey( &out, "text", argument ); imc_addkeyi( &out, "isreply", 1 ); imc_addkeyi( &out, "level", -1 ); imc_send( &out, a ); imc_freedata( &out ); } /* respond to a who request with the given data */ void imc_send_whoreply( const char *to, const char *data, AFFILIATE *a ) { PACKET out; if( STR_EQL( imc_mudof(to), "*" ) ) return; /* don't let them do this */ imc_initdata( &out ); strlcpy( out.to, to, IMC_NAME_LENGTH ); strlcpy( out.type, "who-reply", IMC_TYPE_LENGTH ); imc_addkey( &out, "text", data ); imc_send( &out, a ); imc_freedata( &out ); } /* send a cached supply of is-alive packets for a network */ void ev_send_isalive( void *data ) { REMINFO *r = NULL; struct imc_keepalive *k = (struct imc_keepalive *) data; unsigned char count = 0; imc_cancel_event( ev_send_isalive, k); if ( !k ) return; if ( k->count == 0 ) { imc_send_keepalive( NULL, k->destination, k->affiliate ); count++; } for ( r = k->remote; r; r = r->next ) { if ( STR_CEQL( k->destination, r->name ) || inpath(r->path, k->destination) || r->network == k->affiliate->network ) continue; imc_send_keepalive( r, k->destination, k->affiliate ); count++; /* Don't want to send more than 15 is-alive packets at a time */ if ( count >= 15 && r->next != NULL ) { k->count += count; k->remote = r->next; imc_add_event( 3, ev_send_isalive, k ); return; } } k->destination[0] = '\0'; k->count = 0; k->remote = NULL; k->affiliate = NULL; DISPOSE( k ); return; } /* send a keepalive to a requestor */ void imc_send_keepalive( REMINFO *r, char *destination, AFFILIATE *a ) { PACKET out; if ( !destination || destination[0] == '\0' ) return; imc_initdata( &out ); strlcpy( out.from, "*", IMC_NAME_LENGTH ); strlcpy( out.type, "is-alive", IMC_TYPE_LENGTH ); snprintf( out.to, IMC_NAME_LENGTH, "*@%s", destination ); strlcpy( out.i.to, out.to, IMC_NAME_LENGTH ); /* send an is-alive for the bridge */ if ( !r ) { if ( a->full_bridge ) snprintf( out.i.from, IMC_NAME_LENGTH, "%s@%s", out.from, a->name ); else strlcpy( out.i.from, out.from, IMC_NAME_LENGTH ); imc_addkey( &out, "versionid", IMC_VERSIONID ); imc_addkey( &out, "networkname", a->name ); out.i.sequence = imc_sequencenumber++; if ( !imc_sequencenumber ) imc_sequencenumber++; out.i.path[0] = 0; } /* send an is-alive for a network connection */ else { unsigned int used_sequence[IMC_MEMORY]; unsigned int counter = 0, x = 0, new_sequence = r->top_sequence; snprintf( out.from, IMC_NAME_LENGTH, "*@%s", r->name ); strlcpy( out.i.from, out.from, IMC_NAME_LENGTH ); if ( r->version && r->version[0] != '\0' && !STR_CEQL( r->version, "unknown" ) ) imc_addkey( &out, "versionid", r->version ); if ( r->netname && r->netname[0] != '\0' && !STR_CEQL( r->netname, "unknown" ) ) imc_addkey( &out, "networkname", r->netname ); if ( r->web && r->web[0] != '\0' && !STR_CEQL( r->web, "unknown" ) ) imc_addkey( &out, "url", r->web ); for( counter = 0; counter < IMC_MEMORY; counter++ ) if ( imc_memory[counter].from && STR_CEQL( r->name, imc_memory[counter].from ) ) used_sequence[x++] = imc_memory[counter].sequence; counter = 0; while ( counter < x ) { new_sequence--; for ( counter = 0; counter < x; counter++ ) if ( new_sequence == used_sequence[counter] ) break; } out.i.sequence = new_sequence; strlcpy( out.i.path, r->path, IMC_PATH_LENGTH ); } forward( &out, a, FALSE ); imc_freedata( &out ); } /* send a pingreply with the originating path */ void imc_send_pingreply( const char *to, const char *path, AFFILIATE *a ) { PACKET out; imc_initdata( &out ); strlcpy( out.type, "ping-reply", IMC_TYPE_LENGTH ); snprintf( out.to, IMC_NAME_LENGTH, "*@%s", to ); imc_addkey( &out, "path", path ); imc_send( &out, a ); imc_freedata( &out ); }