/* * IMC2 - an inter-mud communications protocol * * imc.c: the core protocol code * * Copyright (C) 1996,1997 Oliver Jowett <oliver@jowett.manawatu.planet.co.nz> * * 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 <ctype.h> #include <unistd.h> #include <sys/time.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> /* if freeBSD , use flock instead of lockf - shogar */ #ifndef F_TLOCK #define F_TLOCK LOCK_EX #define F_ULOCK LOCK_UN #define lockf(a,b,c) flock(a,b) #endif #include "imc.h" #include "icec.h" /* * Local declarations + some global stuff from imc.h */ /* decls of vars from imc.h */ imc_connect *imc_connect_list; imc_info *imc_info_list; imc_reminfo *imc_reminfo_list; imc_statistics imc_stats; /* imc_active now has more states: * * 0: nothing done yet * 1: configuration loaded, but IMC not active. imc_name is not set. * 2: configuration loaded, but IMC not active. imc_name is valid. * 3: imc_name and configuration loaded, network active, port disabled. * 4: everything active. * */ int imc_active; time_t imc_now; /* current time */ time_t imc_boot; /* current time */ int imc_lock; int imc_is_router; /* run as a router (ie. ping stuff) */ int imc_lock_file=-1; char global_hubname[IMC_MNAME_LENGTH] = "NULL"; int global_directnum=-1; extern int imc_hubswitch; /* control socket for accepting connections */ static int control; /* sequence memory */ _imc_memory imc_memory[IMC_MEMORY]; unsigned long imc_sequencenumber; /* sequence# for outgoing packets */ char *imc_name; /* our IMC name */ unsigned short imc_port; /* our port; 0=disabled */ unsigned long imc_bind; /* IP to bind to */ /* imc flag/state tables */ /* flags for connections */ const imc_flag_type imc_connection_flags[] = { { "noauto", IMC_NOAUTO }, { "client", IMC_CLIENT }, { "reconnect", IMC_RECONNECT }, { "broadcast", IMC_BROADCAST }, { "deny", IMC_DENY }, { "quiet", IMC_QUIET }, { "hub", IMC_HUB }, /* SPAM fix - shogar && currently used hub (unless main hub.. confused yet?) ;) - Scion */ { "main_hub", IMC_MAIN_HUB }, /* main hub, same as hub, but first to try - Scion */ { "old_hub", IMC_OLD_HUB }, /* backup hub just in case - Scion */ { "dead_hub", IMC_DEAD_HUB }, /* defunct hub, 3 connection attempts - Scion */ { "new", IMC_NEW_HUB }, /* mark new hubs so dont save - shogar */ { "noswitch", IMC_NOSWITCH }, /* might be reasons to never switch to them - shogar */ { NULL, 0 }, }; /* flags for rignore entries */ const imc_flag_type imc_ignore_types[] = { { "ignore", IMC_IGNORE }, { "notrust", IMC_NOTRUST }, { "trust", IMC_TRUST }, /* for old config files */ { "1", IMC_IGNORE }, { "2", IMC_NOTRUST }, { NULL, 0 } }; /* states that state in imc_connect can take */ const imc_flag_type imc_state_names[] = { { "closed", IMC_CLOSED }, { "connecting", IMC_CONNECTING }, { "wait1", IMC_WAIT1 }, { "wait2", IMC_WAIT2 }, { "connected", IMC_CONNECTED }, { NULL, 0 } }; /* states that imc_active can take */ const imc_flag_type imc_active_names[] = { { "inactive - not initialized", IA_NONE }, { "inactive - config loaded, local name not set", IA_CONFIG1 }, { "inactive - config loaded, local name set", IA_CONFIG2 }, { "active - not accepting connections", IA_UP }, { "active - accepting connections", IA_LISTENING }, { NULL, 0 } }; /* set up a new imc_connect struct, and link it into imc_connect_list */ imc_connect *imc_new_connect(void) { imc_connect *c; c=imc_malloc(sizeof(*c)); c->state = IMC_CLOSED; c->desc = -1; c->insize = IMC_MINBUF; c->inbuf = imc_malloc(c->insize); c->outsize = IMC_MINBUF; c->outbuf = imc_malloc(c->outsize); c->inbuf[0] = c->outbuf[0] = 0; c->info = NULL; c->spamcounter1=0; c->spamcounter2=0; c->spamtime1=0; c->spamtime2=0; c->newoutput=0; c->next=imc_connect_list; imc_connect_list=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==IMC_CLOSED */ void imc_extract_connect(imc_connect *c) { imc_connect *c_find; if (c->state!=IMC_CLOSED) { imc_logerror("imc_extract_connect: non-closed connection"); return; } imc_free(c->inbuf, c->insize); imc_free(c->outbuf, c->outsize); if (c==imc_connect_list) imc_connect_list=c->next; else { for (c_find=imc_connect_list; c_find && c_find->next!=c; c_find=c_find->next) ; if (!c_find) imc_logerror("imc_extract_connect: not in imc_connect_list"); else c_find->next=c->next; } imc_cancel_event(NULL, c); imc_free(c, sizeof(*c)); } /* update our routing table based on a packet received with path "path" */ static void updateroutes(const char *path) { imc_reminfo *p; const char *sender, *last; const char *temp; /* loop through each item in the path, and update routes to there */ last = imc_lastinpath(path); temp = path; while (temp && temp[0]) { sender=imc_firstinpath(temp); if (strcasecmp(sender, imc_name)) { /* not from us */ /* check if its in the list already */ p = imc_find_reminfo(sender, 1); if (!p) /* not in list yet, create a new entry */ { p=imc_new_reminfo(); p->name = imc_strdup(sender); p->ping = 0; p->alive = imc_now; p->route = imc_strdup(last); p->version = imc_strdup("unknown"); p->type = IMC_REMINFO_NORMAL; } else { /* already in list, update the entry */ if (strcasecmp(last, p->route)) { imc_strfree(p->route); p->route=imc_strdup(last); } p->alive=imc_now; p->type = IMC_REMINFO_NORMAL; } } /* 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) */ static int inpath(const char *path, const char *name) { char buf[IMC_MNAME_LENGTH+3]; char tempn[IMC_MNAME_LENGTH], tempp[IMC_PATH_LENGTH]; imc_sncpy(tempn, name, IMC_MNAME_LENGTH); imc_sncpy(tempp, path, IMC_PATH_LENGTH); imc_slower(tempn); imc_slower(tempp); if (!strcmp(tempp, tempn)) return 1; sprintf(buf, "%s!", tempn); if (!strncmp(tempp, buf, strlen(buf))) return 1; sprintf(buf, "!%s", tempn); if (strlen(buf) < strlen(tempp) && !strcmp(tempp + strlen(tempp) - strlen(buf), buf)) return 1; sprintf(buf, "!%s!", tempn); if (strstr(tempp, buf)) return 1; return 0; } /* * Core functions (all internal) */ /* accept a connection on the control port */ static void do_accept(void) { int d; imc_connect *c; struct sockaddr_in sa; int size = sizeof(sa); int r; d=accept(control, (struct sockaddr *) &sa, &size); if (d<0) { imc_lerror("accept"); return; } r=fcntl(d, F_GETFL, 0); if (r<0 || fcntl(d, F_SETFL, O_NONBLOCK | r)<0) { imc_lerror("do_accept: fcntl"); close(d); return; } c=imc_new_connect(); c->state = IMC_WAIT1; c->desc = d; imc_add_event(IMC_LOGIN_TIMEOUT, ev_login_timeout, c, 1); imc_logstring("connection from %s:%d on descriptor %d", inet_ntoa(sa.sin_addr), ntohs(sa.sin_port), d); } /* notify everyone of the closure - shogar */ void imc_close_notify(const char *host) { imc_packet out; char shorthost[80]; if (imc_active<IA_UP) return; strcpy(shorthost,host); if(strchr(shorthost,'[')) *(strchr(shorthost,'['))=0; imc_initdata(&out.data); strcpy(out.type, "close-notify"); strcpy(out.from, "*"); strcpy(out.to, "*@*"); imc_addkey(&out.data, "versionid", IMC_VERSIONID); if (imc_siteinfo.flags[0]) imc_addkey(&out.data, "flags", imc_siteinfo.flags); imc_addkey(&out.data, "host", shorthost); imc_send(&out); imc_freedata(&out.data); } /* close given connection */ static void do_close(imc_connect *c) { const char *name; imc_reminfo *r; if (c->info) /* if our switched hub, get a new one right away - shogar */ { if (!imc_is_router && (c->info->flags & IMC_NEW_HUB) && c->info == imc_info_list) { imc_cancel_event(ev_imc_pollforhub,NULL); imc_cancel_event(ev_imc_optimize,NULL); imc_add_event(10, ev_imc_pollforhub, NULL, 1); imc_add_event(70, ev_imc_optimize, NULL, 1); } } if (c->state==IMC_CLOSED) return; name=imc_getconnectname(c); if(name && c->state == IMC_CONNECTED) /* dont send if never connected */ { imc_close_notify(name); close(c->desc); /* dont close if closed */ } if (c->state == IMC_CONNECTED) c->info->connection=NULL; /* handle reconnects */ if (c->info) if ((c->info->flags & IMC_RECONNECT) && !(c->info->flags & IMC_DENY) && !(c->info->flags & IMC_CLIENT)) { imc_setup_reconnect(c->info); } c->state=IMC_CLOSED; /* only log after we've set the state, in case imc_logstring * sends packets itself (problems with eg. output buffer overflow). */ if (!c->info || !(c->info->flags & IMC_QUIET)) { name=imc_getconnectname(c); if(name) imc_logstring("%s: closing link", name); } if (c->info) { r=imc_find_reminfo(c->info->name, 1); if (r) imc_delete_reminfo(r); } if(c->desc) close(c->desc); c->desc=0; c->inbuf[0]=0; c->outbuf[0]=0; } /* time out a login */ void ev_login_timeout(void *data) { imc_connect *c=(imc_connect *)data; if (!c->info || !(c->info->flags & IMC_QUIET)) imc_logstring("%s: login timeout", imc_getconnectname(c)); do_close(c); } /* read waiting data from descriptor. * read to a temp buffer to avoid repeated allocations */ static void do_read(imc_connect *c) { int size; int r; char temp[IMC_MAXBUF]; char *newbuf; int newsize; r=read(c->desc, temp, IMC_MAXBUF-1); if (!r || (r<0 && errno != EAGAIN && errno != EWOULDBLOCK)) { if (!c->info || !(c->info->flags & IMC_QUIET)) { if (r<0) /* read error */ { imc_lerror("%s: read", imc_getconnectname(c)); } else /* socket was closed */ { imc_logerror("%s: read: EOF", 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) { #ifdef SHOW_OVERFLOW /* not an error anymore, expected and handled - shogar */ if (size>IMC_MAXBUF) { imc_logerror("%s: input buffer overflow", imc_getconnectname(c)); imc_logerror("%d: was allocated", c->insize); // do_close(c); // imc_free(c->inbuf,c->insize); // c->insize=IMC_MINBUF; // c->inbuf= imc_malloc(c->insize); // size = r + 1; // return; } #endif newsize=c->insize; while(newsize<size) newsize*=2; newbuf=imc_malloc(newsize); strcpy(newbuf, c->inbuf); imc_free(c->inbuf, c->insize); c->inbuf=newbuf; c->insize=newsize; } if (size>c->insize/2) { imc_cancel_event(ev_shrink_input, c); imc_add_event(IMC_SHRINKTIME, ev_shrink_input, c, 0); } if (size<c->insize/2 && size >= IMC_MINBUF) { newsize=c->insize; newsize/=2; newbuf=imc_malloc(newsize); strcpy(newbuf, c->inbuf); imc_free(c->inbuf, c->insize); c->inbuf=newbuf; c->insize=newsize; } strcat(c->inbuf, temp); imc_stats.rx_bytes += r; } /* write to descriptor */ static void do_write(imc_connect *c) { int size, w; if (c->state==IMC_CONNECTING) { /* Wait for server password */ c->state=IMC_WAIT2; return; } size = strlen(c->outbuf); if (!size) /* nothing to write */ return; w=write(c->desc, c->outbuf, size); if (!w || (w<0 && errno != EAGAIN && errno != EWOULDBLOCK)) { if (!c->info || !(c->info->flags & IMC_QUIET)) { if (w<0) /* write error */ { imc_lerror("%s: write", imc_getconnectname(c)); } else /* socket was closed */ { imc_logerror("%s: write: EOF", imc_getconnectname(c)); } } do_close(c); return; } if (w<0) /* EAGAIN */ return; /* throw away data we wrote */ // memmove(c->outbuf, c->outbuf+w, size-w+1); strcpy(c->outbuf,c->outbuf+w); imc_stats.tx_bytes += w; } /* put a line onto descriptors output buffer */ static void do_send(imc_connect *c, const char *line) { int len; char *newbuf; int newsize=c->outsize; if (c->state==IMC_CLOSED) return; // imc_debug(c, 1, line); /* log outgoing traffic */ if (!c->outbuf[0]) c->newoutput=1; len=strlen(c->outbuf)+strlen(line)+3; if (len > c->outsize) { #ifdef SHOW_OVERFLOW /* not an error anymore, expected and handled - shogar */ if (len > IMC_MAXBUF) { if (!c->info || !(c->info->flags & IMC_QUIET)) imc_logerror("%s: output buffer overflow", imc_getconnectname(c)); imc_logerror("%d: was allocated", c->outsize); // imc_logerror("current buf: %s", c->outbuf); // do_close(c); // imc_free(c->outbuf,c->outsize); // c->outsize=IMC_MINBUF; // c->outbuf= imc_malloc(c->outsize); // len=strlen(line)+3; // return; } #endif while(newsize < len) newsize*=2; newbuf=imc_malloc(newsize); strcpy(newbuf, c->outbuf); imc_free(c->outbuf, c->outsize); c->outbuf=newbuf; c->outsize=newsize; } if (len<c->outsize/2 && len >= IMC_MINBUF) { newsize=c->outsize/2; newbuf=imc_malloc(newsize); strcpy(newbuf, c->outbuf); imc_free(c->outbuf, c->outsize); c->outbuf=newbuf; c->outsize=newsize; } strcat(c->outbuf, line); strcat(c->outbuf, "\n\r"); if (strlen(c->outbuf)>=c->outsize/2) { imc_cancel_event(ev_shrink_output, c); imc_add_event(IMC_SHRINKTIME, ev_shrink_output, c, 0); } } /* 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) { int i; char *buf=imc_getsbuf(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; imc_shrinksbuf(buf); 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 */ // memmove(buffer, buffer+i, strlen(buffer+i) + 1); strcpy(buffer,buffer+i); imc_shrinksbuf(buf); return buf; } static int memory_head; /* next entry in memory table to use, wrapping */ /* checkrepeat: check for repeats in the memory table */ static int checkrepeat(const char *mud, unsigned long seq) { int i; for (i=0; i<IMC_MEMORY; i++) if (imc_memory[i].from && !strcasecmp(mud, imc_memory[i].from) && seq == imc_memory[i].sequence) return 1; /* not a repeat, so log it */ if (imc_memory[memory_head].from) imc_strfree(imc_memory[memory_head].from); imc_memory[memory_head].from = imc_strdup(mud); imc_memory[memory_head].sequence = seq; memory_head++; if (memory_head==IMC_MEMORY) memory_head=0; return 0; } #ifdef IMC_NOTIFY static void do_notify(void) { /* tell the central server that we're using IMC. * * This isn't related to your IMC connections, it's just so I can keep track * of how many people are using IMC, with what versions, and where. * * This gets done once a day (and on startup), but only when packets are * actually being forwarded. This means that muds not connected * to anything won't notify. The notification is a single UDP packet to a * hardcoded IP, containing the version ID of IMC being used, and your IMC * name. * * If it bugs you, comment out the #define IMC_NOTIFY in imc.h and it won't * notify the central server. */ struct sockaddr_in sa; int s; char buf[100]; sprintf(buf, "name=%s\nversion=%s\nemail=%s\n", imc_name ? imc_name : "unset", IMC_VERSIONID, imc_siteinfo.email); s=socket(AF_INET, SOCK_DGRAM, 0); if (s<0) return; /* we won't do a lookup here.. if the IP changes, such is life. * 209.51.169.2 is toof.net */ sa.sin_family=AF_INET; sa.sin_addr.s_addr=inet_addr("209.51.169.2"); sa.sin_port=htons(9000); sendto(s, buf, 100, 0, (struct sockaddr *)&sa, sizeof(sa)); close(s); } #endif /* send a packet to a mud using the right version */ static void do_send_packet(imc_connect *c, const imc_packet *p) { const char *output; int v; #ifdef IMC_NOTIFY static time_t last_notify; if (last_notify + 3600*24 < imc_now) { last_notify = imc_now; do_notify(); } #endif v=c->version; if (v>IMC_VERSION) v=IMC_VERSION; output=(*imc_vinfo[v].generate)(p); if (output) { imc_stats.tx_pkts++; if (strlen(output) > imc_stats.max_pkt) imc_stats.max_pkt=strlen(output); do_send(c, output); } } static int can_forward(const imc_packet *p) { if (!strcasecmp(p->type, "chat") || !strcasecmp(p->type, "emote")) { int chan=imc_getkeyi(&p->data, "channel", 0); if (chan==0 || chan==1 || chan==3) return 0; } return 1; } /* forward a packet - main routing function, all packets pass through here */ static void forward(imc_packet *p) { imc_info *i; int broadcast, isbroadcast; const char *to; imc_reminfo *route; imc_info *direct; imc_connect *c; char recievedfrom[IMC_MAXBUF]; /* SPAM fix - shogar */ imc_connect *rf; /* SPAM fix - shogar */ /* check for duplication, and register the packet in the sequence memory */ if (p->i.sequence && checkrepeat(imc_mudof(p->i.from), p->i.sequence)) return; /* check for packets we've already forwarded */ if (inpath(p->i.path, imc_name)) return; /* check for really old packets */ route=imc_find_reminfo(imc_mudof(p->i.from), 1); if (route) { if ((p->i.sequence+IMC_PACKET_LIFETIME) < route->top_sequence) { imc_stats.sequence_drops++; #ifdef LOG_LATE_PACKETS /* kind of spammy, but late packets are natural when the path is broken between sender and reciever if(imc_is_router) /* spare the muds from seeing this - shogar */ imc_logstring("sequence drop: %s (seq=%ld, top=%ld)", p->i.path, p->i.sequence, route->top_sequence); #endif return; } if (p->i.sequence > route->top_sequence) route->top_sequence=p->i.sequence; } /* update our routing info */ updateroutes(p->i.path); /* forward to our mud if it's for us */ if (!strcmp(imc_mudof(p->i.to), "*") || !strcasecmp(imc_mudof(p->i.to), imc_name)) { strcpy(p->to, imc_nameof(p->i.to)); /* strip the name from the 'to' */ strcpy(p->from, p->i.from); imc_recv(p); } /* if its only to us (ie. not broadcast) don't forward it */ if (!strcasecmp(imc_mudof(p->to), imc_name)) return; /* check if we should just drop it (policy rules) */ if (!can_forward(p)) return; /* convert a specific destination to a broadcast in some cases */ to=imc_mudof(p->i.to); isbroadcast=!strcmp(to, "*"); /* broadcasts are, well, broadcasts */ broadcast=1; /* unless we know better, flood packets */ i=0; /* make gcc happy */ direct=NULL; /* no direct connection to send on */ /* convert 'to' fields that we have a route for to a hop along the route */ if (!isbroadcast && (route=imc_find_reminfo(to, 0)) != NULL && route->route != NULL && !inpath(p->i.path, route->route)) /* 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 (strcasecmp(to, route->route) && (i=imc_getinfo(to))!=NULL && i->connection) direct=i; to=route->route; } /* check for a direct connection */ if (!isbroadcast && (i=imc_getinfo(to)) != NULL && i->connection && !(i->flags & IMC_BROADCAST)) broadcast=0; if (broadcast) { /* need to forward a packet */ /* SPAM fix - hubcnt and who just gave us the packet- shogar */ int hubcnt,fromhub; hubcnt=0; fromhub=0; strcpy(recievedfrom,imc_lastinpath(p->i.path)); for (rf=imc_connect_list; rf; rf=rf->next) { if(rf->info && rf->info->name && !strcmp(recievedfrom,rf->info->name)) { if(rf->info->flags & IMC_HUB) fromhub=1; } } /* end SPAM fix */ for (c=imc_connect_list; c; c=c->next) if (c->state==IMC_CONNECTED) { /* don't forward to sites that have already received it, * or sites that don't need this packet */ if (inpath(p->i.path, c->info->name) || (p->i.stamp & c->info->noforward)!=0) continue; /* SPAM fix - shogar */ if(c->info->flags & IMC_HUB) { if(!imc_is_router) { if (fromhub) continue; if(hubcnt) { continue; } else { hubcnt=1; } } /* if for imc3 we need to do this - shogar */ /* if (imc_getkeyi(&p->data,"channel",0) == 2) continue; */ } /* end SPAM fix */ do_send_packet(c, p); } } else /* forwarding to a specific connection */ { /* but only if they haven't seen it (sanity check) */ if (i->connection && !inpath(p->i.path, i->name)) do_send_packet(i->connection, p); /* send on direct connection, if we have one */ if (direct && direct!=i && direct->connection && !inpath(p->i.path, direct->name)) do_send_packet(direct->connection, p); } } /* handle a password from a client */ static void clientpassword(imc_connect *c, const char *argument) { char arg1[3], name[IMC_MNAME_LENGTH], pw[IMC_PW_LENGTH], version[20]; imc_info *i; char response[IMC_PACKET_LENGTH]; argument=imc_getarg(argument, arg1, 4); /* packet type (has to be PW) */ argument=imc_getarg(argument, name, IMC_MNAME_LENGTH); /* remote mud name */ argument=imc_getarg(argument, pw, IMC_PW_LENGTH); /* password */ argument=imc_getarg(argument, version, 20); /* optional version=n string */ if (strcasecmp(arg1, "PW")) { imc_logstring("%s: non-PW password packet", imc_getconnectname(c)); do_close(c); return; } /* do we know them, and do they have the right password? */ i=imc_getinfo(name); if (!i || strcmp(i->clientpw, pw)) { if (!i || !(i->flags & IMC_QUIET)) imc_logstring("%s: password failure for %s", imc_getconnectname(c), name); do_close(c); return; } /* deny access if deny flag is set (good for eg. muds that start crashing * on rwho) */ if (i->flags & IMC_DENY) { if (!(i->flags & IMC_QUIET)) imc_logstring("%s: denying connection", name); do_close(c); return; } if (i->connection) /* kill old connections */ do_close(i->connection); /* register them */ i->connection = c; c->state = IMC_CONNECTED; c->info = i; c->spamcounter1 = 0; c->spamcounter2 = 0; /* check for a version string (assume version 0 if not present) */ if (sscanf(version, "version=%hu", &c->version)!=1) c->version=0; /* check for generator/interpreter */ if (!imc_vinfo[c->version].generate || !imc_vinfo[c->version].interpret) { if (!(i->flags & IMC_QUIET)) imc_logstring("%s: unsupported version %d", imc_getconnectname(c), c->version); do_close(c); return; } /* send our response */ sprintf(response, "PW %s %s version=%d", imc_name, i->serverpw, IMC_VERSION); do_send(c, response); if (!(i->flags & IMC_QUIET)) imc_logstring("%s: connected (version %d)", imc_getconnectname(c), c->version); c->info->timer_duration=IMC_MIN_RECONNECT_TIME; c->info->last_connected=imc_now; imc_cancel_event(ev_login_timeout, c); imc_cancel_event(ev_reconnect, c->info); } /* handle a password response from a server */ static void serverpassword(imc_connect *c, const char *argument) { char arg1[3], name[IMC_MNAME_LENGTH], pw[IMC_PW_LENGTH], version[20]; imc_info *i; argument=imc_getarg(argument, arg1, 4); /* has to be PW */ argument=imc_getarg(argument, name, IMC_MNAME_LENGTH); argument=imc_getarg(argument, pw, IMC_PW_LENGTH); argument=imc_getarg(argument, version, 20); if (strcasecmp(arg1, "PW")) { imc_logstring("%s: non-PW password packet", imc_getconnectname(c)); do_close(c); return; } i=imc_getinfo(name); if (!i || strcmp(i->serverpw, pw) || i != c->info) { if ((!i || !(i->flags & IMC_QUIET)) && !(c->info->flags & IMC_QUIET)) imc_logstring("%s: password failure for %s", imc_getconnectname(c), name); do_close(c); return; } if (i->connection) /* kill old connections */ do_close(i->connection); i->connection = c; c->state = IMC_CONNECTED; c->spamcounter1 = 0; c->spamcounter2 = 0; /* check for a version string (assume version 0 if not present) */ if (sscanf(version, "version=%hu", &c->version)!=1) c->version=0; /* check for generator/interpreter */ if (!imc_vinfo[c->version].generate || !imc_vinfo[c->version].interpret) { if (!(i->flags & IMC_QUIET)) imc_logstring("%s: unsupported version %d", imc_getconnectname(c), c->version); do_close(c); return; } if (!(i->flags & IMC_QUIET)) imc_logstring("%s: connected (version %d)", imc_getconnectname(c), c->version); c->info->timer_duration=IMC_MIN_RECONNECT_TIME; c->info->last_connected=imc_now; imc_cancel_event(ev_login_timeout, c); imc_cancel_event(ev_reconnect, c->info); } /* start up listening port */ void imc_startup_port(void) { int i; struct sockaddr_in sa; if (imc_active!=IA_UP) { imc_logerror("imc_startup_port: called with imc_active=%d", imc_active); return; } if (imc_port==0) { imc_logerror("imc_startup_port: called with imc_port=0"); return; } imc_logstring("binding port %d for incoming connections", imc_port); control = socket(AF_INET, SOCK_STREAM, 0); if (control<0) { imc_lerror("imc_startup_port: socket"); return; } i=1; if (setsockopt(control, SOL_SOCKET, SO_REUSEADDR, (void *)&i, sizeof(i))<0) { imc_lerror("imc_startup_port: SO_REUSEADDR"); close(control); return; } if ((i=fcntl(control, F_GETFL, 0))<0) { imc_lerror("imc_startup_port: fcntl(F_GETFL)"); close(control); return; } if (fcntl(control, F_SETFL, i | O_NONBLOCK)<0) { imc_lerror("imc_startup_port: fcntl(F_SETFL)"); close(control); return; } sa.sin_family = AF_INET; sa.sin_port = htons(imc_port); sa.sin_addr.s_addr = imc_bind; /* already in network order */ if (bind(control, (struct sockaddr *)&sa, sizeof(sa))<0) { imc_lerror("imc_startup_port: bind"); close(control); return; } if (listen(control, 1)<0) { imc_lerror("imc_startup_port: listen"); close(control); return; } imc_active=IA_LISTENING; } /* shut down listening port */ void imc_shutdown_port(void) { if (imc_active!=IA_LISTENING) { imc_logerror("imc_shutdown_port: called with imc_active=%d", imc_active); return; } imc_logstring("closing listen port"); close(control); imc_active=IA_UP; } #ifdef USEIOCTL /* this is an ugly hack to generate the send-queue size for an empty queue. * SO_SNDBUF is only supported in some places, and seems to cause problems * under SunOS */ /* connect to the local discard server, and look at the queue size for an * empty socket. */ static int getsndbuf(void) { struct sockaddr_in sa; int s, queue; if ((s=socket(AF_INET, SOCK_STREAM, 0))<0) return 0; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("127.0.0.1"); /* connect to localhost */ sa.sin_port = htons(9); /* 'discard' service */ if (connect(s, (struct sockaddr *)&sa, sizeof(sa))<0) { close(s); return 0; } if (ioctl(s, TIOCOUTQ, &queue)<0) { close(s); return 0; } close(s); return queue; } #endif static int lock_prefix(void) { char lockfile[1000]; sprintf(lockfile, "%slock", imc_prefix); imc_lock_file=open(lockfile, O_CREAT|O_EXCL|O_RDWR, 0644); if (imc_lock_file<0) imc_lock_file=open(lockfile, O_RDWR, 0644); if (imc_lock_file<0) { imc_lerror("lock_prefix: open %s", lockfile); return 0; } //if (lockf(imc_lock_file, F_TLOCK, 1)<0) if (imc_lock_file<0) { close(imc_lock_file); imc_lock_file=-1; return 0; } return 1; } static void unlock_prefix(void) { if (imc_lock_file<0) return; //lockf(imc_lock_file, F_ULOCK, 1); close(imc_lock_file); imc_lock_file=-1; } /* start up IMC */ void imc_startup_network(void) { imc_info *info; int toggle=0; /* This is to tell after we've done reconnects if we've connected to a hub or not -- Scion */ if (imc_active != IA_CONFIG2) { imc_logerror("imc_startup_network: called with imc_active==%d", imc_active); return; } if (!imc_siteinfo.name[0]) { imc_logerror("InfoName not set, not initializing"); return; } if (!imc_siteinfo.email[0]) { imc_logerror("InfoEmail not set, not initializing"); return; } imc_logstring("network initializing"); imc_active=IA_UP; control=-1; if (imc_port) imc_startup_port(); 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.sequence_drops = 0; imc_add_event(20, ev_keepalive, NULL, 1); /* fill my imclist please - shogar */ imc_add_event(30, ev_request_keepalive, NULL, 1); imc_mail_startup(); /* start up the mailer */ if (!lock_prefix()) { imc_logstring("another process is using the same config prefix, not autoconnecting."); return; } /* do autoconnects */ for (info=imc_info_list; info; info=info->next) if (!(info->flags & IMC_NOAUTO) && !(info->flags & IMC_CLIENT) && !(info->flags & IMC_DENY) /* && !(info->flags & IMC_OLD_HUB) not used anymore - shogar && !(info->flags & IMC_DEAD_HUB) */ && !(info->flags & IMC_HUB)) { if (imc_connect_to(info->name) && ((info->flags & IMC_MAIN_HUB) || (info->flags & IMC_HUB))) toggle=1; } /* Setup optimization to run, it won't if we're a hub -- Scion */ if(imc_hubswitch && !imc_is_router) { imc_add_event(360, ev_imc_pollforhub, NULL, 1); imc_add_event(420, ev_imc_optimize, NULL, 1); imc_logstring("Setting auto-optimize to run in 6 minutes."); } /* Are we connected to any hubs, btw? -- Scion */ if (!toggle) { /* No hubs connected! */ for (info=imc_info_list; info; info=info->next) if ((info->flags & IMC_HUB) && (!toggle)) if (imc_connect_to(info->name)) toggle=0; } /* We're out of ideas, notify the admins -- Scion */ /* if (!toggle) imc_logstring("No hubs connected! Check the configuration!"); */ } void imc_startup(const char *prefix) { if (imc_active!=IA_NONE) { imc_logstring("imc_startup: called with imc_active=%d", imc_active); return; } imc_now=time(NULL); /* start our clock */ imc_boot=imc_now; imc_logstring("%s initializing", IMC_VERSIONID); #ifdef USEIOCTL outqsize = getsndbuf(); imc_logstring("found TIOCOUTQ=%d", outqsize); #endif imc_prefix=imc_strdup(prefix); imc_sequencenumber=imc_now; strcpy(imc_lasterror, "no error"); imc_readconfig(); imc_readignores(); imc_active = imc_name ? IA_CONFIG2 : IA_CONFIG1; if (imc_active==IA_CONFIG2) imc_startup_network(); } void imc_shutdown_network(void) { imc_event *ev, *ev_next; imc_connect *c, *c_next; imc_reminfo *p, *pnext; if (imc_active < IA_UP) { imc_logerror("imc_shutdown_network: called with imc_active==%d", imc_active); return; } if (imc_lock) { imc_logerror("imc_shutdown_network: called from within imc_idle_select"); return; } imc_logstring("shutting down network"); if (imc_active == IA_LISTENING) imc_shutdown_port(); imc_logstring("rx %ld packets, %ld bytes (%ld/second)", imc_stats.rx_pkts, imc_stats.rx_bytes, (imc_now == imc_stats.start) ? 0 : imc_stats.rx_bytes / (imc_now - imc_stats.start)); imc_logstring("tx %ld packets, %ld bytes (%ld/second)", imc_stats.tx_pkts, imc_stats.tx_bytes, (imc_now == imc_stats.start) ? 0 : imc_stats.tx_bytes / (imc_now - imc_stats.start)); imc_logstring("largest packet %d bytes", imc_stats.max_pkt); imc_logstring("dropped %d packets by sequence number", imc_stats.sequence_drops); imc_mail_shutdown(); for (c=imc_connect_list; c; c=c_next) { c_next=c->next; do_close(c); imc_extract_connect(c); } imc_connect_list=NULL; if(!imc_is_router) icec_shutdown(); for (p=imc_reminfo_list; p; p=pnext) { pnext=p->next; imc_strfree(p->version); imc_strfree(p->name); if(p->path) imc_strfree(p->path); imc_free(p, sizeof(imc_reminfo)); } imc_reminfo_list=NULL; for (ev=imc_event_list; ev; ev=ev_next) { ev_next=ev->next; imc_free(ev, sizeof(imc_event)); } for (ev=imc_event_free; ev; ev=ev_next) { ev_next=ev->next; imc_free(ev, sizeof(imc_event)); } imc_event_list=imc_event_free=NULL; unlock_prefix(); imc_active=IA_CONFIG2; } /* close down imc */ void imc_shutdown(void) { imc_ignore_data *ign, *ign_next; imc_info *info, *info_next; if (imc_active==IA_NONE) { imc_logerror("imc_shutdown: called with imc_active==0"); return; } if (imc_active>=IA_UP) imc_shutdown_network(); for (ign=imc_ignore_list; ign; ign=ign_next) { ign_next=ign->next; imc_freeignore(ign); } imc_ignore_list=NULL; for (info=imc_info_list; info; info=info_next) { info_next=info->next; imc_delete_info(info); } imc_info_list=NULL; if (imc_active >= IA_UP) imc_shutdown_network(); imc_strfree(imc_prefix); imc_prefix=NULL; if (imc_active >= IA_CONFIG2) imc_strfree(imc_name); imc_name=NULL; imc_active=IA_NONE; } /* interpret an incoming packet using the right version */ static imc_packet *do_interpret_packet(imc_connect *c, const char *line) { int v; imc_packet *p; if (!line[0]) return NULL; v=c->version; if (v>IMC_VERSION) v=IMC_VERSION; p=(*imc_vinfo[v].interpret)(line); if (p) { if (c->info) { p->i.stamp=c->info->rcvstamp; } else { p->i.stamp=0; } } return p; } int imc_fill_fdsets(int maxfd, fd_set *read, fd_set *write, fd_set *exc) { imc_connect *c; if (imc_active<IA_UP) return maxfd; /* set up fd_sets for select */ if (imc_active>=IA_LISTENING) { if (maxfd < control) maxfd = control; FD_SET(control, read); } for (c=imc_connect_list; c; c=c->next) { if (maxfd < c->desc) maxfd = c->desc; switch (c->state) { case IMC_CONNECTING: /* connected/error when writable */ FD_SET(c->desc, write); break; case IMC_CONNECTED: case IMC_WAIT1: case IMC_WAIT2: FD_SET(c->desc, read); if (c->outbuf[0]) FD_SET(c->desc, write); break; } } return maxfd; } int imc_get_max_timeout(void) { imc_event *p; for (p=imc_event_list; p; p=p->next) if (p->timed) return p->when - imc_now; return 60; /* make sure we don't get too backlogged with events */ } /* shell around imc_idle_select */ void imc_idle(int s, int us) { fd_set read, write, exc; int maxfd; struct timeval timeout; int i; FD_ZERO(&read); FD_ZERO(&write); FD_ZERO(&exc); maxfd=imc_fill_fdsets(0, &read, &write, &exc); timeout.tv_sec = s; timeout.tv_usec = us; if (maxfd) while ((i=select(maxfd+1, &read, &write, &exc, &timeout)) < 0 && errno == EINTR) /* loop, ignoring signals */ ; else while ((i=select(0, NULL, NULL, NULL, &timeout)) < 0 && errno == EINTR) ; if (i<0) { imc_lerror("imc_idle: select"); imc_shutdown_network(); return; } imc_idle_select(&read, &write, &exc, time(NULL)); } /* low-level idle function: read/write buffers as needed, etc */ void imc_idle_select(fd_set *read, fd_set *write, fd_set *exc, time_t now) { const char *command; imc_packet *p; imc_connect *c, *c_next ; if (imc_active<IA_CONFIG1) return; if (imc_lock) { imc_logerror("imc_idle_select: recursive call"); return; } imc_lock=1; if (imc_sequencenumber < (unsigned long)imc_now) imc_sequencenumber=(unsigned long)imc_now; imc_run_events(now); if (imc_active<IA_UP) { imc_lock=0; return; } /* handle results of the select */ if (imc_active >= IA_LISTENING && FD_ISSET(control, read)) do_accept(); for (c=imc_connect_list; c; c=c_next) { c_next=c->next; if (c->state!=IMC_CLOSED && FD_ISSET(c->desc, exc)) do_close(c); if (c->state!=IMC_CLOSED && FD_ISSET(c->desc, read)) do_read(c); while (c->state!=IMC_CLOSED && // (c->spamtime1>=0 || c->spamcounter1<=IMC_SPAM1MAX) && // (c->spamtime2>=0 || c->spamcounter2<=IMC_SPAM2MAX) && (command = getline(c->inbuf)) != NULL) { if (strlen(command) > imc_stats.max_pkt) imc_stats.max_pkt=strlen(command); // imc_debug(c, 0, command); /* log incoming packets */ switch (c->state) { case IMC_CLOSED: break; case IMC_WAIT1: clientpassword(c, command); break; case IMC_WAIT2: serverpassword(c, command); break; case IMC_CONNECTED: p = do_interpret_packet(c, command); if (p) { #ifdef IMC_PARANOIA /* paranoia: 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. */ imc_stats.rx_pkts++; if (strcasecmp(c->info->name, imc_lastinpath(p->i.path))) imc_logerror("PARANOIA: packet from %s allegedly from %s", c->info->name, imc_lastinpath(p->i.path)); else if (strcasecmp(imc_mudof(p->i.from), imc_firstinpath(p->i.path))) imc_logerror("PARANOIA: packet from %s has firstinpath %s", p->i.from, imc_firstinpath(p->i.path)); else forward(p); /* only forward if its a valid packet! */ #else imc_stats.rx_pkts++; forward(p); #endif #ifdef SPAMPROT if (!strcasecmp(p->type, "chat") || !strcasecmp(p->type, "tell") || !strcasecmp(p->type, "emote") || 1) { if (!c->spamcounter1 && !c->spamtime1) imc_add_event(IMC_SPAM1INTERVAL, ev_spam1, c, 0); c->spamcounter1++; if (!c->spamcounter2 && !c->spamtime2) imc_add_event(IMC_SPAM2INTERVAL, ev_spam2, c, 0); c->spamcounter2++; } #endif imc_freedata(&p->data); } break; } } } for (c=imc_connect_list; c; c=c_next) { c_next=c->next; if (c->state!=IMC_CLOSED && (FD_ISSET(c->desc, write) || c->newoutput)) { // c->newoutput=0; do_write(c); c->newoutput=c->outbuf[0]; } } for (c=imc_connect_list; c; c=c_next) { c_next=c->next; if (c->state==IMC_CLOSED) imc_extract_connect(c); } imc_lock=0; } /* connect to given mud */ int imc_connect_to(const char *mud) { imc_info *i; imc_connect *c; int desc; struct sockaddr_in sa; char buf[IMC_DATA_LENGTH]; int r; if (imc_active == IA_NONE) { imc_qerror("IMC is not active"); return 0; } i=imc_getinfo(mud); if (!i) { imc_qerror("%s: unknown mud name", mud); return 0; } if (i->connection) { imc_qerror("%s: already connected", mud); return 0; } if (i->flags & IMC_CLIENT) { imc_qerror("%s: client-only flag is set", mud); return 0; } if (i->flags & IMC_DENY) { imc_qerror("%s: deny flag is set", mud); return 0; } if (!(i->flags & IMC_QUIET)) imc_logstring("connect to %s", mud); /* 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 ((sa.sin_addr.s_addr=inet_addr(i->host)) == -1UL) { struct hostent *hostinfo; if (NULL == (hostinfo=gethostbyname(i->host))) { imc_logerror("imc_connect: couldn't resolve hostname"); return 0; } sa.sin_addr.s_addr = *(unsigned long *) hostinfo->h_addr; } sa.sin_port = htons(i->port); sa.sin_family = AF_INET; desc=socket(AF_INET, SOCK_STREAM, 0); if (desc<0) { imc_lerror("socket"); return 0; } r=fcntl(desc, F_GETFL, 0); if (r<0 || fcntl(desc, F_SETFL, O_NONBLOCK | r)<0) { imc_lerror("imc_connect: fcntl"); close(desc); return 0; } if (connect(desc, (struct sockaddr *)&sa, sizeof(sa))<0) if (errno != EINPROGRESS) { imc_lerror("connect"); close(desc); return 0; } c=imc_new_connect(); c->desc = desc; c->state = IMC_CONNECTING; c->info = i; imc_add_event(IMC_LOGIN_TIMEOUT, ev_login_timeout, c, 1); sprintf(buf, "PW %s %s version=%d", imc_name, i->clientpw, IMC_VERSION); do_send(c, buf); return 1; } int imc_disconnect(const char *mud) { imc_connect *c; imc_info *i; int d; if (imc_active == IA_NONE) { imc_qerror("IMC is not active"); return 0; } if ((d=atoi(mud))!=0) { /* disconnect a specific descriptor */ for (c=imc_connect_list; c; c=c->next) if (c->desc==d) { imc_logstring("disconnect descriptor %s", imc_getconnectname(c)); do_close(c); return 1; } imc_qerror("%d: no matching descriptor", d); return 0; } i=imc_getinfo(mud); if (!i) { if (strcasecmp(mud, "unknown")) /* disconnect all unknown muds */ { imc_qerror("%s: unknown mud", mud); return 0; } } imc_logstring("disconnect %s", mud); for (c=imc_connect_list; c; c=c->next) if (c->info==i) do_close(c); return 1; } void imc_send(imc_packet *p) { if (imc_active < IA_UP) { imc_logerror("imc_send when not active!"); return; } /* initialize packet fields that the caller shouldn't/doesn't set */ p->i.stamp = 0; p->i.path[0] = 0; p->i.sequence = imc_sequencenumber++; if (!imc_sequencenumber) imc_sequencenumber++; imc_sncpy(p->i.to, p->to, IMC_NAME_LENGTH); imc_sncpy(p->i.from, p->from, IMC_NAME_LENGTH); strcat(p->i.from, "@"); imc_sncpy(p->i.from + strlen(p->i.from), imc_name, IMC_NAME_LENGTH - strlen(p->i.from)); forward(p); } imc_info *imc_new_info() { imc_info *i, *p; i=imc_malloc(sizeof(*i)); i->name = NULL; i->host = NULL; i->port = 0; i->connection = NULL; i->clientpw = NULL; i->serverpw = NULL; i->timer_duration = IMC_MIN_RECONNECT_TIME; i->rcvstamp = 0; i->noforward = 0; i->flags = 0; i->last_connected = 0; /* ugly hack, but Too Bad, I don't want another global floating around */ i->next=NULL; for (p=imc_info_list; p && p->next; p=p->next) ; if (!p) imc_info_list=i; else p->next=i; return i; } imc_info *imc_insert_info() { imc_info *i; i=imc_malloc(sizeof(*i)); i->name = NULL; i->host = NULL; i->port = 0; i->connection = NULL; i->clientpw = NULL; i->serverpw = NULL; i->timer_duration = IMC_MIN_RECONNECT_TIME; i->rcvstamp = 0; i->noforward = 0; i->flags = 0; i->last_connected = 0; /* ugly hack, but Too Bad, I don't want another global floating around */ i->next=imc_info_list; imc_info_list=i; return i; } void imc_delete_info(imc_info *i) { imc_connect *c; imc_info *last; for (c=imc_connect_list; c; c=c->next) if (c->info==i) do_close(c); if (i==imc_info_list) imc_info_list=i->next; else { for (last=imc_info_list; last && last->next!=i; last=last->next) ; if (!last) imc_logerror("imc_delete_info: not in list"); else last->next=i->next; } if (i->name) imc_strfree(i->name); if (i->host) imc_strfree(i->host); if (i->clientpw) imc_strfree(i->clientpw); if (i->serverpw) imc_strfree(i->serverpw); imc_cancel_event(NULL, i); imc_free(i, sizeof(*i)); } void imc_cancel_info(imc_info *i) { imc_info *last; if (i==imc_info_list) imc_info_list=i->next; else { for (last=imc_info_list; last && last->next!=i; last=last->next) ; if (!last) imc_logerror("imc_delete_info: not in list"); else last->next=i->next; } if (i->name) imc_strfree(i->name); if (i->host) imc_strfree(i->host); if (i->clientpw) imc_strfree(i->clientpw); if (i->serverpw) imc_strfree(i->serverpw); imc_free(i, sizeof(*i)); }