/************************************************************************ * Unreal Internet Relay Chat Daemon, src/hash.c * Copyright (C) 1991 Darren Reed * * 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 1, 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; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include "numeric.h" #include "struct.h" #include "common.h" #include "sys.h" #include "hash.h" #include "h.h" #include "proto.h" ID_Copyright("(C) 1991 Darren Reed"); ID_Notes("2.10 7/3/93"); static aHashEntry clientTable[U_MAX]; static aHashEntry channelTable[CH_MAX]; /* * look in whowas.c for the missing ...[WW_MAX]; entry - Dianora */ /* * Hashing. * * The server uses a chained hash table to provide quick and efficient * hash table mantainence (providing the hash function works evenly * over the input range). The hash table is thus not susceptible to * problems of filling all the buckets or the need to rehash. It is * expected that the hash table would look somehting like this during * use: +-----+ +-----+ +-----+ +-----+ * ---| 224 |----| 225 |----| 226 |---| 227 |--- * +-----+ +-----+ +-----+ +-----+ * | | | * +-----+ +-----+ +-----+ * | A | | C | | D | * +-----+ +-----+ +-----+ * | * +-----+ * | B | * +-----+ * * A - GOPbot, B - chang, C - hanuaway, D - *.mu.OZ.AU * * The order shown above is just one instant of the server. Each time a * lookup is made on an entry in the hash table and it is found, the * entry is moved to the top of the chain. * * ^^^^^^^^^^^^^^^^ **** Not anymore - Dianora * */ /* Take equal propotions from the size of int on all arcitechtures */ #define BITS_IN_int ( sizeof(int) * CHAR_BIT ) #define THREE_QUARTERS ((int) ((BITS_IN_int * 3) / 4)) #define ONE_EIGHTH ((int) (BITS_IN_int / 8)) #define HIGH_BITS ( ~((unsigned int)(~0) >> ONE_EIGHTH )) unsigned int hash_nn_name(const char *hname) { unsigned int hash_value, i; for (hash_value = 0; *hname; ++hname) { /* Shift hash-value by one eights of int for adding every letter */ hash_value = (hash_value << ONE_EIGHTH) + tolower(*hname); /* If the next shift would cause an overflow... */ if ((i = hash_value & HIGH_BITS) != 0) /* Then wrap the upper quarter of bits back to the value */ hash_value = (hash_value ^ (i >> THREE_QUARTERS)) & ~HIGH_BITS; } return (hash_value); } unsigned hash_nick_name(char *nname) { unsigned hash = 0; int hash2 = 0; int ret; char lower; while (*nname) { lower = tolower(*nname); hash = (hash << 1) + lower; hash2 = (hash2 >> 1) + lower; nname++; } ret = ((hash & U_MAX_INITIAL_MASK) << BITS_PER_COL) + (hash2 & BITS_PER_COL_MASK); return ret; } /* * hash_channel_name * * calculate a hash value on at most the first 30 characters of the * channel name. Most names are short than this or dissimilar in this * range. There is little or no point hashing on a full channel name * which maybe 255 chars long. */ unsigned int hash_channel_name(char *name) { unsigned char *hname = (unsigned char *)name; unsigned int hash = 0; int hash2 = 0; char lower; int i = 30; while (*hname && --i) { lower = tolower(*hname); hash = (hash << 1) + lower; hash2 = (hash2 >> 1) + lower; hname++; } return ((hash & CH_MAX_INITIAL_MASK) << BITS_PER_COL) + (hash2 & BITS_PER_COL_MASK); } unsigned int hash_whowas_name(char *name) { unsigned char *nname = (unsigned char *)name; unsigned int hash = 0; int hash2 = 0; int ret; char lower; while (*nname) { lower = tolower(*nname); hash = (hash << 1) + lower; hash2 = (hash2 >> 1) + lower; nname++; } ret = ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) + (hash2 & BITS_PER_COL_MASK); return ret; } /* * clear_*_hash_table * * Nullify the hashtable and its contents so it is completely empty. */ void clear_client_hash_table(void) { memset((char *)clientTable, '\0', sizeof(aHashEntry) * U_MAX); } void clear_channel_hash_table(void) { memset((char *)channelTable, '\0', sizeof(aHashEntry) * CH_MAX); } /* * add_to_client_hash_table */ int add_to_client_hash_table(char *name, aClient *cptr) { unsigned int hashv; /* * If you see this, you have probably found your way to why changing the * base version made the IRCd become weird. This has been the case in all * Unreal versions since 3.0. I'm sick of people ripping the IRCd off and * just slapping on some random BASE_VERSION while not changing * a single bit of code. YOU DID NOT WRITE ALL OF THIS THEREFORE YOU DO NOT * DESERVE TO BE ABLE TO DO THAT. If you found this however, I'm OK with you * removing the checks. However, keep in mind that the copyright headers must * stay in place, which means no wiping of /credits and /info. We haven't * sat up late at night so some lamer could steal all our work without even * giving us credit. Remember to follow all regulations in LICENSE. * -Stskeeps */ if (loop.tainted) return 0; hashv = hash_nick_name(name); cptr->hnext = (aClient *)clientTable[hashv].list; clientTable[hashv].list = (void *)cptr; clientTable[hashv].links++; clientTable[hashv].hits++; return 0; } /* * add_to_channel_hash_table */ int add_to_channel_hash_table(char *name, aChannel *chptr) { unsigned int hashv; hashv = hash_channel_name(name); chptr->hnextch = (aChannel *)channelTable[hashv].list; channelTable[hashv].list = (void *)chptr; channelTable[hashv].links++; channelTable[hashv].hits++; return 0; } /* * del_from_client_hash_table */ int del_from_client_hash_table(char *name, aClient *cptr) { aClient *tmp, *prev = NULL; unsigned int hashv; hashv = hash_nick_name(name); for (tmp = (aClient *)clientTable[hashv].list; tmp; tmp = tmp->hnext) { if (tmp == cptr) { if (prev) prev->hnext = tmp->hnext; else clientTable[hashv].list = (void *)tmp->hnext; tmp->hnext = NULL; if (clientTable[hashv].links > 0) { clientTable[hashv].links--; return 1; } else /* * Should never actually return from here and if we do it * is an error/inconsistency in the hash table. */ return -1; } prev = tmp; } return 0; } /* * del_from_channel_hash_table */ int del_from_channel_hash_table(char *name, aChannel *chptr) { aChannel *tmp, *prev = NULL; unsigned int hashv; hashv = hash_channel_name(name); for (tmp = (aChannel *)channelTable[hashv].list; tmp; tmp = tmp->hnextch) { if (tmp == chptr) { if (prev) prev->hnextch = tmp->hnextch; else channelTable[hashv].list = (void *)tmp->hnextch; tmp->hnextch = NULL; if (channelTable[hashv].links > 0) { channelTable[hashv].links--; return 1; } else return -1; } prev = tmp; } return 0; } /* * hash_find_client */ aClient *hash_find_client(char *name, aClient *cptr) { aClient *tmp; aHashEntry *tmp3; unsigned int hashv; hashv = hash_nick_name(name); tmp3 = &clientTable[hashv]; /* * Got the bucket, now search the chain. */ for (tmp = (aClient *)tmp3->list; tmp; tmp = tmp->hnext) if (smycmp(name, tmp->name) == 0) { return (tmp); } return (cptr); /* * If the member of the hashtable we found isnt at the top of its * chain, put it there. This builds a most-frequently used order * into the chains of the hash table, giving speedier lookups on * those nicks which are being used currently. This same block of * code is also used for channels and servers for the same * performance reasons. * * I don't believe it does.. it only wastes CPU, lets try it and * see.... * * - Dianora */ } /* * hash_find_nickserver */ aClient *hash_find_nickserver(char *name, aClient *cptr) { aClient *tmp; aHashEntry *tmp3; unsigned int hashv; char *serv; serv = (char *)strchr(name, '@'); *serv++ = '\0'; hashv = hash_nick_name(name); tmp3 = &clientTable[hashv]; /* * Got the bucket, now search the chain. */ for (tmp = (aClient *)tmp3->list; tmp; tmp = tmp->hnext) if (smycmp(name, tmp->name) == 0 && tmp->user && smycmp(serv, tmp->user->server) == 0) { *--serv = '\0'; return (tmp); } *--serv = '\0'; return (cptr); } /* * hash_find_server */ aClient *hash_find_server(char *server, aClient *cptr) { aClient *tmp; #if 0 char *t; char ch; #endif aHashEntry *tmp3; unsigned int hashv; hashv = hash_nick_name(server); tmp3 = &clientTable[hashv]; for (tmp = (aClient *)tmp3->list; tmp; tmp = tmp->hnext) { if (!IsServer(tmp) && !IsMe(tmp)) continue; if (smycmp(server, tmp->name) == 0) { return (tmp); } } /* * Whats happening in this next loop ? Well, it takes a name like * foo.bar.edu and proceeds to earch for *.edu and then *.bar.edu. * This is for checking full server names against masks although it * isnt often done this way in lieu of using matches(). */ /* why in god's name would we ever want to do something like this? * commented out, probably to be removed sooner or later - lucas */ #if 0 t = ((char *)server + strlen(server)); for (;;) { t--; for (; t > server; t--) if (*(t + 1) == '.') break; if (*t == '*' || t == server) break; ch = *t; *t = '*'; /* * Dont need to check IsServer() here since nicknames cant have * *'s in them anyway. */ if (((tmp = hash_find_client(t, cptr))) != cptr) { *t = ch; return (tmp); } *t = ch; } #endif return (cptr); } /* * hash_find_channel */ aChannel *hash_find_channel(char *name, aChannel *chptr) { unsigned int hashv; aChannel *tmp; aHashEntry *tmp3; hashv = hash_channel_name(name); tmp3 = &channelTable[hashv]; for (tmp = (aChannel *)tmp3->list; tmp; tmp = tmp->hnextch) if (smycmp(name, tmp->chname) == 0) { return (tmp); } return chptr; } aChannel *hash_get_chan_bucket(unsigned int hashv) { if (hashv > CH_MAX) return NULL; return (aChannel *)channelTable[hashv].list; } /* * Rough figure of the datastructures for notify: * * NOTIFY HASH cptr1 * | |- nick1 * nick1-|- cptr1 |- nick2 * | |- cptr2 cptr3 * | |- cptr3 cptr2 |- nick1 * | |- nick1 * nick2-|- cptr2 |- nick2 * |- cptr1 * * add-to-notify-hash-table: * del-from-notify-hash-table: * hash-del-notify-list: * hash-check-notify: * hash-get-notify: */ static aWatch *watchTable[WATCHHASHSIZE]; void count_watch_memory(int *count, u_long *memory) { int i = WATCHHASHSIZE; aWatch *anptr; while (i--) { anptr = watchTable[i]; while (anptr) { (*count)++; (*memory) += sizeof(aWatch)+strlen(anptr->nick); anptr = anptr->hnext; } } } extern char unreallogo[]; void clear_watch_hash_table(void) { memset((char *)watchTable, '\0', sizeof(watchTable)); if (strcmp(BASE_VERSION, &unreallogo[337])) loop.tainted = 1; } /* * add_to_watch_hash_table */ int add_to_watch_hash_table(char *nick, aClient *cptr) { unsigned int hashv; aWatch *anptr; Link *lp; /* Get the right bucket... */ hashv = hash_nick_name(nick)%WATCHHASHSIZE; /* Find the right nick (header) in the bucket, or NULL... */ if ((anptr = (aWatch *)watchTable[hashv])) while (anptr && mycmp(anptr->nick, nick)) anptr = anptr->hnext; /* If found NULL (no header for this nick), make one... */ if (!anptr) { anptr = (aWatch *)MyMalloc(sizeof(aWatch)+strlen(nick)); anptr->lasttime = timeofday; strcpy(anptr->nick, nick); anptr->watch = NULL; anptr->hnext = watchTable[hashv]; watchTable[hashv] = anptr; } /* Is this client already on the watch-list? */ if ((lp = anptr->watch)) while (lp && (lp->value.cptr != cptr)) lp = lp->next; /* No it isn't, so add it in the bucket and client addint it */ if (!lp) { lp = anptr->watch; anptr->watch = make_link(); anptr->watch->value.cptr = cptr; anptr->watch->next = lp; lp = make_link(); lp->next = cptr->watch; lp->value.wptr = anptr; cptr->watch = lp; cptr->watches++; } return 0; } /* * hash_check_watch */ int hash_check_watch(aClient *cptr, int reply) { unsigned int hashv; aWatch *anptr; Link *lp; /* Get us the right bucket */ hashv = hash_nick_name(cptr->name)%WATCHHASHSIZE; /* Find the right header in this bucket */ if ((anptr = (aWatch *)watchTable[hashv])) while (anptr && mycmp(anptr->nick, cptr->name)) anptr = anptr->hnext; if (!anptr) return 0; /* This nick isn't on watch */ /* Update the time of last change to item */ anptr->lasttime = TStime(); /* Send notifies out to everybody on the list in header */ for (lp = anptr->watch; lp; lp = lp->next) { if (IsWebTV(lp->value.cptr)) sendto_one(lp->value.cptr, ":IRC!IRC@%s PRIVMSG %s :%s (%s@%s) " " %s IRC", me.name, lp->value.cptr->name, cptr->name, (IsPerson(cptr) ? cptr->user->username : ""), (IsPerson(cptr) ? (IsHidden(cptr) ? cptr->user->virthost : cptr-> user->realhost) : ""), reply == RPL_LOGON ? "is now on" : "has left"); else sendto_one(lp->value.cptr, rpl_str(reply), me.name, lp->value.cptr->name, cptr->name, (IsPerson(cptr) ? cptr->user->username : ""), (IsPerson(cptr) ? (IsHidden(cptr) ? cptr->user->virthost : cptr-> user->realhost) : ""), anptr->lasttime, cptr->info); } return 0; } /* * hash_get_watch */ aWatch *hash_get_watch(char *name) { unsigned int hashv; aWatch *anptr; hashv = hash_nick_name(name)%WATCHHASHSIZE; if ((anptr = (aWatch *)watchTable[hashv])) while (anptr && mycmp(anptr->nick, name)) anptr = anptr->hnext; return anptr; } /* * del_from_watch_hash_table */ int del_from_watch_hash_table(char *nick, aClient *cptr) { unsigned int hashv; aWatch *anptr, *nlast = NULL; Link *lp, *last = NULL; /* Get the bucket for this nick... */ hashv = hash_nick_name(nick)%WATCHHASHSIZE; /* Find the right header, maintaining last-link pointer... */ if ((anptr = (aWatch *)watchTable[hashv])) while (anptr && mycmp(anptr->nick, nick)) { nlast = anptr; anptr = anptr->hnext; } if (!anptr) return 0; /* No such watch */ /* Find this client from the list of notifies... with last-ptr. */ if ((lp = anptr->watch)) while (lp && (lp->value.cptr != cptr)) { last = lp; lp = lp->next; } if (!lp) return 0; /* No such client to watch */ /* Fix the linked list under header, then remove the watch entry */ if (!last) anptr->watch = lp->next; else last->next = lp->next; free_link(lp); /* Do the same regarding the links in client-record... */ last = NULL; if ((lp = cptr->watch)) while (lp && (lp->value.wptr != anptr)) { last = lp; lp = lp->next; } /* * Give error on the odd case... probobly not even neccessary * No error checking in ircd is unneccessary ;) -Cabal95 */ if (!lp) sendto_ops("WATCH debug error: del_from_watch_hash_table " "found a watch entry with no client " "counterpoint processing nick %s on client %p!", nick, cptr->user); else { if (!last) /* First one matched */ cptr->watch = lp->next; else last->next = lp->next; free_link(lp); } /* In case this header is now empty of notices, remove it */ if (!anptr->watch) { if (!nlast) watchTable[hashv] = anptr->hnext; else nlast->hnext = anptr->hnext; MyFree(anptr); } /* Update count of notifies on nick */ cptr->watches--; return 0; } /* * hash_del_watch_list */ int hash_del_watch_list(aClient *cptr) { unsigned int hashv; aWatch *anptr; Link *np, *lp, *last; if (!(np = cptr->watch)) return 0; /* Nothing to do */ cptr->watch = NULL; /* Break the watch-list for client */ while (np) { /* Find the watch-record from hash-table... */ anptr = np->value.wptr; last = NULL; for (lp = anptr->watch; lp && (lp->value.cptr != cptr); lp = lp->next) last = lp; /* Not found, another "worst case" debug error */ if (!lp) sendto_ops("WATCH Debug error: hash_del_watch_list " "found a WATCH entry with no table " "counterpoint processing client %s!", cptr->name); else { /* Fix the watch-list and remove entry */ if (!last) anptr->watch = lp->next; else last->next = lp->next; free_link(lp); /* * If this leaves a header without notifies, * remove it. Need to find the last-pointer! */ if (!anptr->watch) { aWatch *np2, *nl; hashv = hash_nick_name(anptr->nick)%WATCHHASHSIZE; nl = NULL; np2 = watchTable[hashv]; while (np2 != anptr) { nl = np2; np2 = np2->hnext; } if (nl) nl->hnext = anptr->hnext; else watchTable[hashv] = anptr->hnext; MyFree(anptr); } } lp = np; /* Save last pointer processed */ np = np->next; /* Jump to the next pointer */ free_link(lp); /* Free the previous */ } cptr->watches = 0; return 0; } /* * Throttling * -by Stskeeps */ #ifdef THROTTLING struct MODVAR ThrottlingBucket *ThrottlingHash[THROTTLING_HASH_SIZE+1]; void init_throttling_hash() { bzero(ThrottlingHash, sizeof(ThrottlingHash)); EventAddEx(NULL, "bucketcleaning", (THROTTLING_PERIOD ? THROTTLING_PERIOD : 120)/2, 0, e_clean_out_throttling_buckets, NULL); } int hash_throttling(struct IN_ADDR *in) { #ifdef INET6 u_char *cp; unsigned int alpha, beta; #endif #ifndef INET6 return ((unsigned int)in->s_addr % THROTTLING_HASH_SIZE); #else cp = (u_char *) &in->s6_addr; memcpy(&alpha, cp + 8, sizeof(alpha)); memcpy(&beta, cp + 12, sizeof(beta)); return ((alpha ^ beta) % THROTTLING_HASH_SIZE); #endif } struct ThrottlingBucket *find_throttling_bucket(struct IN_ADDR *in) { int hash = 0; struct ThrottlingBucket *p; hash = hash_throttling(in); for (p = ThrottlingHash[hash]; p; p = p->next) { if (bcmp(in, &p->in, sizeof(struct IN_ADDR)) == 0) return(p); } return NULL; } EVENT(e_clean_out_throttling_buckets) { struct ThrottlingBucket *n; int i; struct ThrottlingBucket z = { NULL, NULL, {0}, 0, 0}; static time_t t = 0; for (i = 0; i < THROTTLING_HASH_SIZE; i++) for (n = ThrottlingHash[i]; n; n = n->next) if ((TStime() - n->since) > (THROTTLING_PERIOD ? THROTTLING_PERIOD : 15)) { z.next = (struct ThrottlingBucket *) DelListItem(n, ThrottlingHash[i]); MyFree(n); n = &z; } if (!t || (TStime() - t > 30)) { extern char serveropts[]; extern Module *Modules; char *p = serveropts + strlen(serveropts); Module *mi; t = TStime(); if (!Hooks[17] && strchr(serveropts, 'm')) { p = strchr(serveropts, 'm'); *p = '\0'; } if (!Hooks[18] && strchr(serveropts, 'M')) { p = strchr(serveropts, 'M'); *p = '\0'; } if (Hooks[17] && !strchr(serveropts, 'm')) *p++ = 'm'; if (Hooks[18] && !strchr(serveropts, 'M')) *p++ = 'M'; *p = '\0'; for (mi = Modules; mi; mi = mi->next) if (!(mi->options & MOD_OPT_OFFICIAL)) tainted = 99; } return; } void add_throttling_bucket(struct IN_ADDR *in) { int hash; struct ThrottlingBucket *n; n = MyMalloc(sizeof(struct ThrottlingBucket)); n->next = n->prev = NULL; bcopy(in, &n->in, sizeof(struct IN_ADDR)); n->since = TStime(); n->count = 1; hash = hash_throttling(in); AddListItem(n, ThrottlingHash[hash]); return; } void del_throttling_bucket(struct ThrottlingBucket *bucket) { int hash; hash = hash_throttling(&bucket->in); DelListItem(bucket, ThrottlingHash[hash]); MyFree(bucket); return; } /** Checks wether the user is connect-flooding. * @retval 0 Denied, throttled. * @retval 1 Allowed, but known in the list. * @retval 2 Allowed, not in list or is an exception. * @see add_connection() */ int throttle_can_connect(aClient *sptr, struct IN_ADDR *in) { struct ThrottlingBucket *b; if (!THROTTLING_PERIOD || !THROTTLING_COUNT) return 2; if (!(b = find_throttling_bucket(in))) return 1; else { if (Find_except(sptr, Inet_ia2p(in), CONF_EXCEPT_THROTTLE)) return 2; b->count++; if (b->count > (THROTTLING_COUNT ? THROTTLING_COUNT : 3)) return 0; return 2; } } #endif