2 * IRC - Internet Relay Chat, ircd/whowas.c
3 * Copyright (C) 1990 Markku Savela
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 1, or (at your option)
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 * --- avalon --- 6th April 1992
20 * rewritten to scrap linked lists and use a table of structures which
21 * is referenced like a circular loop. Should be faster and more efficient.
23 * --- comstud --- 25th March 1997
24 * Everything rewritten from scratch. Avalon's code was bad. My version
25 * is faster and more efficient. No more hangs on /squits and you can
26 * safely raise NICKNAMEHISTORYLENGTH to a higher value without hurting
29 * --- comstud --- 5th August 1997
30 * Fixed for Undernet..
32 * --- Run --- 27th August 1997
33 * Speeded up the code, added comments.
40 #include "ircd_alloc.h"
41 #include "ircd_chattr.h"
42 #include "ircd_features.h"
43 #include "ircd_string.h"
61 struct Whowas *ww_list; /* list of whowas structures */
62 struct Whowas *ww_tail; /* tail of list for getting structures */
63 unsigned int ww_alloc; /* alloc count */
64 } wwList = { 0, 0, 0 };
66 struct Whowas* whowashash[WW_MAX];
69 * Since the introduction of numeric nicks (at least for upstream messages,
70 * like MODE +o <nick>, KICK #chan <nick>, KILL <nick> etc), there is no
71 * real important reason for a nick history anymore.
72 * Nevertheless, there are two reason why we might want to keep it:
73 * 1) The /WHOWAS command, which is often usefull to catch harrashing
74 * users or abusers in general.
75 * 2) Clients still use the normal nicks in the client-server protocol,
76 * and it might be considered a nice feature that here we still have
78 * Note however that BOTH reasons make it redundant to keep a whowas history
79 * for users that split off.
81 * The rewrite of comstud was many to safe cpu during net.breaks and therefore
82 * a bit redundant imho (Run).
84 * But - it was written anyway. So lets look at the structure of the
87 * We still have a static table of 'struct Whowas' structures in which we add
88 * new nicks (plus info) as in a rotating buffer. We keep a global pointer
89 * `whowas_next' that points to the next entry to be overwritten - or to
90 * the oldest entry in the table (which is the same).
92 * Each entry keeps pointers for two doubly linked lists (thus four pointers):
93 * A list of the entries that have the same hash value ('hashv list'), and
94 * a list of the entries that have the same online pointer (`online list').
95 * Note that the last list (pointers) is only updated as long as online points
96 * to the corresponding client: As soon as the client signs off, this list
97 * is not anymore maintained (and hopefully not used anymore either ;).
99 * So now we have two ways of accessing this database:
100 * 1) Given a <nick> we can calculate a hashv and then whowashash[hashv] will
101 * point to the start of the 'hash list': all entries with the same hashv.
102 * We'll have to search this list to find the entry with the correct <nick>.
103 * Once we found the correct whowas entry, we have a pointer to the
104 * corresponding client - if still online - for nich chasing purposes.
105 * Note that the same nick can occur multiple times in the whowas history,
106 * each of these having the same hash value of course. While a /WHOWAS on
107 * just a nick will return all entries, nick chasing will only find the
108 * first in the list. Because new entries are added at the start of the
109 * 'hash list' we will always find the youngest entry, which is what we want.
110 * 2) Given an online client we have a pointer to the first whowas entry
111 * of the linked list of whowas entries that all belong to this client.
112 * We ONLY need this to reset all `online' pointers when this client
117 * Note that following:
119 * a) We *only* (need to) change the 'hash list' and the 'online' list
121 * b) There we always ADD an entry to the BEGINNING of the 'hash list'
122 * and the 'online list': *new* entries are at the start of the lists.
123 * The oldest entries are at the end of the lists.
124 * c) We always REMOVE the oldest entry we have (whowas_next), this means
125 * that this is always an entry that is at the *end* of the 'hash list'
126 * and 'online list' that it is a part of: the next pointer will
128 * d) The previous pointer is *only* used to update the next pointer of the
129 * previous entry, therefore we could better use a pointer to this
130 * next pointer: That is faster - saves us a 'if' test (it will never be
131 * NULL because the last added entry will point to the pointer that
132 * points to the start of the list) and we won't need special code to
133 * update the list start pointers.
135 * I incorporated these considerations into the code below.
142 * Clean up a whowas structure
144 static struct Whowas *
145 whowas_clean(struct Whowas *ww)
150 Debug((DEBUG_LIST, "Cleaning whowas structure for %s", ww->name));
152 if (ww->online) { /* unlink from client */
153 if (ww->cnext) /* shouldn't happen, but I'm not confident of that */
154 ww->cnext->cprevnextp = ww->cprevnextp;
155 *ww->cprevnextp = ww->cnext;
158 if (ww->hnext) /* now unlink from hash table */
159 ww->hnext->hprevnextp = ww->hprevnextp;
160 *ww->hprevnextp = ww->hnext;
162 if (ww->wnext) /* unlink from whowas linked list... */
163 ww->wnext->wprev = ww->wprev;
165 ww->wprev->wnext = ww->wnext;
167 if (wwList.ww_tail == ww) /* update tail pointer appropriately */
168 wwList.ww_tail = ww->wprev;
174 MyFree(ww->username);
176 MyFree(ww->hostname);
178 MyFree(ww->servername);
180 MyFree(ww->realname);
189 * Free a struct Whowas...
192 whowas_free(struct Whowas *ww)
197 Debug((DEBUG_LIST, "Destroying whowas structure for %s", ww->name));
199 MyFree(whowas_clean(ww));
206 * Initializes a given whowas structure
208 static struct Whowas *
209 whowas_init(struct Whowas *ww)
235 * Returns a whowas structure to use
237 static struct Whowas *
240 if (wwList.ww_alloc >= feature_int(FEAT_NICKNAMEHISTORYLENGTH))
241 return whowas_init(whowas_clean(wwList.ww_tail));
243 wwList.ww_alloc++; /* going to allocate a new one... */
244 return whowas_init((struct Whowas *) MyMalloc(sizeof(struct Whowas)));
254 Debug((DEBUG_LIST, "whowas_realloc() called with alloc count %d, "
255 "history length %d, tail pointer %p", wwList.ww_alloc,
256 feature_int(FEAT_NICKNAMEHISTORYLENGTH), wwList.ww_tail));
258 while (wwList.ww_alloc > feature_int(FEAT_NICKNAMEHISTORYLENGTH)) {
259 if (!wwList.ww_tail) { /* list is empty... */
260 Debug((DEBUG_LIST, "whowas list emptied with alloc count %d",
265 whowas_free(wwList.ww_tail); /* free oldest element of whowas list */
272 * Add a client (cptr) that just changed nick (still_on == true), or
273 * just signed off (still_on == false) to the `whowas' table.
275 * If the entry used was already in use, then this entry is
278 void add_history(struct Client *cptr, int still_on)
282 if (!(ww = whowas_alloc()))
283 return; /* couldn't get a structure */
285 ww->hashv = hash_whowas_name(cli_name(cptr)); /* initialize struct */
286 ww->logoff = CurrentTime;
287 DupString(ww->name, cli_name(cptr));
288 DupString(ww->username, cli_user(cptr)->username);
289 DupString(ww->hostname, cli_user(cptr)->host);
290 DupString(ww->servername, cli_name(cli_user(cptr)->server));
291 DupString(ww->realname, cli_info(cptr));
292 if (cli_user(cptr)->away)
293 DupString(ww->away, cli_user(cptr)->away);
295 if (still_on) { /* user changed nicknames... */
297 if ((ww->cnext = cli_whowas(cptr)))
298 ww->cnext->cprevnextp = &ww->cnext;
299 ww->cprevnextp = &(cli_whowas(cptr));
300 cli_whowas(cptr) = ww;
301 } else /* user quit */
304 /* link new whowas structure to list */
305 ww->wnext = wwList.ww_list;
307 wwList.ww_list->wprev = ww;
310 if (!wwList.ww_tail) /* update the tail pointer... */
313 /* Now link it into the hash table */
314 if ((ww->hnext = whowashash[ww->hashv]))
315 ww->hnext->hprevnextp = &ww->hnext;
316 ww->hprevnextp = &whowashash[ww->hashv];
317 whowashash[ww->hashv] = ww;
323 * Client `cptr' signed off: Set all `online' pointers
324 * corresponding to this client to NULL.
326 void off_history(const struct Client *cptr)
330 for (temp = cli_whowas(cptr); temp; temp = temp->cnext)
337 * Return a pointer to a client that had nick `nick' not more then
338 * `timelimit' seconds ago, if still on line. Otherwise return NULL.
340 * This function is used for "nick chasing"; since the use of numeric
341 * nicks for "upstream" messages in ircu2.10, this is only used for
342 * looking up non-existing nicks in client->server messages.
344 struct Client *get_history(const char *nick, time_t timelimit)
346 struct Whowas *temp = whowashash[hash_whowas_name(nick)];
347 timelimit = CurrentTime - timelimit;
349 for (; temp; temp = temp->hnext)
350 if (0 == ircd_strcmp(nick, temp->name) && temp->logoff > timelimit)
356 void count_whowas_memory(int *wwu, size_t *wwum, int *wwa, size_t *wwam)
368 for (tmp = wwList.ww_list; tmp; tmp = tmp->wnext) {
370 um += (strlen(tmp->name) + 1);
371 um += (strlen(tmp->username) + 1);
372 um += (strlen(tmp->hostname) + 1);
373 um += (strlen(tmp->servername) + 1);
376 am += (strlen(tmp->away) + 1);
386 void initwhowas(void)
390 for (i = 0; i < WW_MAX; i++)
394 unsigned int hash_whowas_name(const char *name)
396 unsigned int hash = 0;
397 unsigned int hash2 = 0;
402 lower = ToLower(*name);
403 hash = (hash << 1) + lower;
404 hash2 = (hash2 >> 1) + lower;
408 return ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) +
409 (hash2 & BITS_PER_COL_MASK);