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_string.h"
58 static struct Whowas whowas[NICKNAMEHISTORYLENGTH];
59 static struct Whowas* whowas_next = whowas;
60 struct Whowas* whowashash[WW_MAX];
63 * Since the introduction of numeric nicks (at least for upstream messages,
64 * like MODE +o <nick>, KICK #chan <nick>, KILL <nick> etc), there is no
65 * real important reason for a nick history anymore.
66 * Nevertheless, there are two reason why we might want to keep it:
67 * 1) The /WHOWAS command, which is often usefull to catch harrashing
68 * users or abusers in general.
69 * 2) Clients still use the normal nicks in the client-server protocol,
70 * and it might be considered a nice feature that here we still have
72 * Note however that BOTH reasons make it redundant to keep a whowas history
73 * for users that split off.
75 * The rewrite of comstud was many to safe cpu during net.breaks and therefore
76 * a bit redundant imho (Run).
78 * But - it was written anyway. So lets look at the structure of the
81 * We still have a static table of 'struct Whowas' structures in which we add
82 * new nicks (plus info) as in a rotating buffer. We keep a global pointer
83 * `whowas_next' that points to the next entry to be overwritten - or to
84 * the oldest entry in the table (which is the same).
86 * Each entry keeps pointers for two doubly linked lists (thus four pointers):
87 * A list of the entries that have the same hash value ('hashv list'), and
88 * a list of the entries that have the same online pointer (`online list').
89 * Note that the last list (pointers) is only updated as long as online points
90 * to the corresponding client: As soon as the client signs off, this list
91 * is not anymore maintained (and hopefully not used anymore either ;).
93 * So now we have two ways of accessing this database:
94 * 1) Given a <nick> we can calculate a hashv and then whowashash[hashv] will
95 * point to the start of the 'hash list': all entries with the same hashv.
96 * We'll have to search this list to find the entry with the correct <nick>.
97 * Once we found the correct whowas entry, we have a pointer to the
98 * corresponding client - if still online - for nich chasing purposes.
99 * Note that the same nick can occur multiple times in the whowas history,
100 * each of these having the same hash value of course. While a /WHOWAS on
101 * just a nick will return all entries, nick chasing will only find the
102 * first in the list. Because new entries are added at the start of the
103 * 'hash list' we will always find the youngest entry, which is what we want.
104 * 2) Given an online client we have a pointer to the first whowas entry
105 * of the linked list of whowas entries that all belong to this client.
106 * We ONLY need this to reset all `online' pointers when this client
111 * Note that following:
113 * a) We *only* (need to) change the 'hash list' and the 'online' list
115 * b) There we always ADD an entry to the BEGINNING of the 'hash list'
116 * and the 'online list': *new* entries are at the start of the lists.
117 * The oldest entries are at the end of the lists.
118 * c) We always REMOVE the oldest entry we have (whowas_next), this means
119 * that this is always an entry that is at the *end* of the 'hash list'
120 * and 'online list' that it is a part of: the next pointer will
122 * d) The previous pointer is *only* used to update the next pointer of the
123 * previous entry, therefore we could better use a pointer to this
124 * next pointer: That is faster - saves us a 'if' test (it will never be
125 * NULL because the last added entry will point to the pointer that
126 * points to the start of the list) and we won't need special code to
127 * update the list start pointers.
129 * I incorporated these considerations into the code below.
135 struct Whowas *newww;
136 struct Whowas *oldww;
139 #define WHOWAS_UNUSED ((unsigned int)-1)
144 * Add a client (cptr) that just changed nick (still_on == true), or
145 * just signed off (still_on == false) to the `whowas' table.
147 * If the entry used was already in use, then this entry is
150 void add_history(struct Client *cptr, int still_on)
153 ww.newww = whowas_next;
155 /* If this entry has already been used, remove it from the lists */
156 if (ww.newww->hashv != WHOWAS_UNUSED)
158 if (ww.oldww->online) /* No need to update cnext/cprev when offline! */
160 /* Remove ww.oldww from the linked list with the same `online' pointers */
161 *ww.oldww->cprevnextp = ww.oldww->cnext;
163 assert(0 == ww.oldww->cnext);
166 /* Remove ww.oldww from the linked list with the same `hashv' */
167 *ww.oldww->hprevnextp = ww.oldww->hnext;
169 assert(0 == ww.oldww->hnext);
172 MyFree(ww.oldww->name);
173 if (ww.oldww->username)
174 MyFree(ww.oldww->username);
175 if (ww.oldww->hostname)
176 MyFree(ww.oldww->hostname);
177 if (ww.oldww->servername)
178 MyFree(ww.oldww->servername);
179 if (ww.oldww->realname)
180 MyFree(ww.oldww->realname);
182 MyFree(ww.oldww->away);
185 /* Initialize aWhoWas struct `newww' */
186 ww.newww->hashv = hash_whowas_name(cli_name(cptr));
187 ww.newww->logoff = CurrentTime;
188 DupString(ww.newww->name, cli_name(cptr));
189 DupString(ww.newww->username, cli_user(cptr)->username);
190 DupString(ww.newww->hostname, cli_user(cptr)->host);
191 /* Should be changed to server numeric */
192 DupString(ww.newww->servername, cli_name(cli_user(cptr)->server));
193 DupString(ww.newww->realname, cli_info(cptr));
194 if (cli_user(cptr)->away)
195 DupString(ww.newww->away, cli_user(cptr)->away);
197 ww.newww->away = NULL;
199 /* Update/initialize online/cnext/cprev: */
200 if (still_on) /* User just changed nicknames */
202 ww.newww->online = cptr;
203 /* Add struct Whowas struct `newww' to start of 'online list': */
204 if ((ww.newww->cnext = cli_whowas(cptr)))
205 ww.newww->cnext->cprevnextp = &ww.newww->cnext;
206 ww.newww->cprevnextp = &(cli_whowas(cptr));
207 cli_whowas(cptr) = ww.newww;
209 else /* User quitting */
210 ww.newww->online = NULL;
212 /* Add struct Whowas struct `newww' to start of 'hashv list': */
213 if ((ww.newww->hnext = whowashash[ww.newww->hashv]))
214 ww.newww->hnext->hprevnextp = &ww.newww->hnext;
215 ww.newww->hprevnextp = &whowashash[ww.newww->hashv];
216 whowashash[ww.newww->hashv] = ww.newww;
218 /* Advance `whowas_next' to next entry in the `whowas' table: */
219 if (++whowas_next == &whowas[NICKNAMEHISTORYLENGTH])
220 whowas_next = whowas;
226 * Client `cptr' signed off: Set all `online' pointers
227 * corresponding to this client to NULL.
229 void off_history(const struct Client *cptr)
233 for (temp = cli_whowas(cptr); temp; temp = temp->cnext)
240 * Return a pointer to a client that had nick `nick' not more then
241 * `timelimit' seconds ago, if still on line. Otherwise return NULL.
243 * This function is used for "nick chasing"; since the use of numeric
244 * nicks for "upstream" messages in ircu2.10, this is only used for
245 * looking up non-existing nicks in client->server messages.
247 struct Client *get_history(const char *nick, time_t timelimit)
249 struct Whowas *temp = whowashash[hash_whowas_name(nick)];
250 timelimit = CurrentTime - timelimit;
252 for (; temp; temp = temp->hnext)
253 if (0 == ircd_strcmp(nick, temp->name) && temp->logoff > timelimit)
259 void count_whowas_memory(int *wwu, size_t *wwum, int *wwa, size_t *wwam)
272 for (i = 0, tmp = whowas; i < NICKNAMEHISTORYLENGTH; i++, tmp++) {
273 if (tmp->hashv != WHOWAS_UNUSED) {
275 um += (strlen(tmp->name) + 1);
276 um += (strlen(tmp->username) + 1);
277 um += (strlen(tmp->hostname) + 1);
278 um += (strlen(tmp->servername) + 1);
281 am += (strlen(tmp->away) + 1);
292 void initwhowas(void)
296 for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
297 whowas[i].hashv = WHOWAS_UNUSED;
300 unsigned int hash_whowas_name(const char *name)
302 unsigned int hash = 0;
303 unsigned int hash2 = 0;
308 lower = ToLower(*name);
309 hash = (hash << 1) + lower;
310 hash2 = (hash2 >> 1) + lower;
314 return ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) +
315 (hash2 & BITS_PER_COL_MASK);