]> jfr.im git - irc/quakenet/snircd.git/blame - ircd/whowas.c
Should be unsigned long for A
[irc/quakenet/snircd.git] / ircd / whowas.c
CommitLineData
189935b1 1/*
2 * IRC - Internet Relay Chat, ircd/whowas.c
3 * Copyright (C) 1990 Markku Savela
4 *
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)
8 * any later version.
9 *
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.
14 *
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.
18 *
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.
22 *
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
27 * performance.
28 *
29 * --- comstud --- 5th August 1997
30 * Fixed for Undernet..
31 *
32 * --- Run --- 27th August 1997
33 * Speeded up the code, added comments.
34 */
35#include "config.h"
36
37#include "whowas.h"
38#include "client.h"
39#include "ircd.h"
40#include "ircd_alloc.h"
41#include "ircd_chattr.h"
42#include "ircd_features.h"
43#include "ircd_log.h"
44#include "ircd_string.h"
45#include "list.h"
46#include "numeric.h"
47#include "s_debug.h"
48#include "s_misc.h"
49#include "s_user.h"
50#include "send.h"
51#include "struct.h"
52#include "sys.h"
53#include "msg.h"
54
55/* #include <assert.h> -- Now using assert in ircd_log.h */
56#include <stdlib.h>
57#include <string.h>
58
59
60/** Keeps track of whowas least-recently-used list. */
61static struct {
62 struct Whowas *ww_list; /**< list of whowas structures */
63 struct Whowas *ww_tail; /**< tail of list for getting structures */
64 unsigned int ww_alloc; /**< alloc count */
65} wwList = { 0, 0, 0 };
66
67/** Hash table of Whowas entries by nickname. */
68struct Whowas* whowashash[WW_MAX];
69
70/** @file
71 * @brief Manipulation functions for the whowas list.
9f8856e9 72 * @version $Id: whowas.c,v 1.17.2.1 2006/03/25 03:46:56 entrope Exp $
189935b1 73 *
74 * Since the introduction of numeric nicks (at least for upstream messages,
75 * like MODE +o &lt;nick>, KICK #chan &lt;nick>, KILL &lt;nick> etc), there is no
76 * real important reason for a nick history anymore.
77 * Nevertheless, there are two reason why we might want to keep it:
78 * @li The /WHOWAS command, which is often useful to catch harassing
79 * users or abusers in general.
80 * @li Clients still use the normal nicks in the client-server protocol,
81 * and it might be considered a nice feature that here we still have
82 * nick chasing.
83 *
84 * Note however that BOTH reasons make it redundant to keep a whowas history
85 * for users that split off.
86 *
87 * The rewrite of comstud was many to safe cpu during net.breaks and therefore
88 * a bit redundant imho (Run).
89 *
90 * But - it was written anyway. So lets look at the structure of the
91 * whowas history now:
92 *
93 * We still have a static table of 'struct Whowas' structures in which we add
94 * new nicks (plus info) as in a rotating buffer. We keep a global pointer
95 * `whowas_next' that points to the next entry to be overwritten - or to
96 * the oldest entry in the table (which is the same).
97 *
98 * Each entry keeps pointers for two doubly linked lists (thus four pointers):
99 * A list of the entries that have the same hash value ('hashv list'), and
100 * a list of the entries that have the same online pointer (`online list').
101 * Note that the last list (pointers) is only updated as long as online points
102 * to the corresponding client: As soon as the client signs off, this list
103 * is not anymore maintained (and hopefully not used anymore either ;).
104 *
105 * So now we have two ways of accessing this database:
106 * @li Given a &lt;nick> we can calculate a hashv and then whowashash[hashv] will
107 * point to the start of the 'hash list': all entries with the same hashv.
108 * We'll have to search this list to find the entry with the correct &lt;nick>.
109 * Once we found the correct whowas entry, we have a pointer to the
110 * corresponding client - if still online - for nick chasing purposes.
111 * Note that the same nick can occur multiple times in the whowas history,
112 * each of these having the same hash value of course. While a /WHOWAS on
113 * just a nick will return all entries, nick chasing will only find the
114 * first in the list. Because new entries are added at the start of the
115 * 'hash list' we will always find the youngest entry, which is what we want.
116 * @li Given an online client we have a pointer to the first whowas entry
117 * of the linked list of whowas entries that all belong to this client.
118 * We ONLY need this to reset all `online' pointers when this client
119 * signs off.
120 *
121 * 27/8/97:
122 *
123 * Note that following:
124 *
125 * @li We *only* (need to) change the 'hash list' and the 'online' list
126 * in add_history().
127 * @li There we always ADD an entry to the BEGINNING of the 'hash list'
128 * and the 'online list': *new* entries are at the start of the lists.
129 * The oldest entries are at the end of the lists.
130 * @li We always REMOVE the oldest entry we have (whowas_next), this means
131 * that this is always an entry that is at the *end* of the 'hash list'
132 * and 'online list' that it is a part of: the next pointer will
133 * always be NULL.
134 * @li The previous pointer is *only* used to update the next pointer of the
135 * previous entry, therefore we could better use a pointer to this
136 * next pointer: That is faster - saves us a 'if' test (it will never be
137 * NULL because the last added entry will point to the pointer that
138 * points to the start of the list) and we won't need special code to
139 * update the list start pointers.
140 *
141 * I incorporated these considerations into the code below.
142 *
143 * --Run
144 */
145
146/** Unlink a Whowas structure and free everything inside it.
147 * @param[in,out] ww The whowas record to free.
148 * @return The pointer \a ww.
149 */
150static struct Whowas *
151whowas_clean(struct Whowas *ww)
152{
153 if (!ww)
154 return 0;
155
156 Debug((DEBUG_LIST, "Cleaning whowas structure for %s", ww->name));
157
158 if (ww->online) { /* unlink from client */
159 if (ww->cnext) /* shouldn't happen, but I'm not confident of that */
160 ww->cnext->cprevnextp = ww->cprevnextp;
161 *ww->cprevnextp = ww->cnext;
162 }
163
164 if (ww->hnext) /* now unlink from hash table */
165 ww->hnext->hprevnextp = ww->hprevnextp;
166 *ww->hprevnextp = ww->hnext;
167
168 if (ww->wnext) /* unlink from whowas linked list... */
169 ww->wnext->wprev = ww->wprev;
170 if (ww->wprev)
171 ww->wprev->wnext = ww->wnext;
172
173 if (wwList.ww_tail == ww) /* update tail pointer appropriately */
174 wwList.ww_tail = ww->wprev;
175
176 /* Free old info */
177 if (ww->name)
178 MyFree(ww->name);
179 if (ww->username)
180 MyFree(ww->username);
181 if (ww->hostname)
182 MyFree(ww->hostname);
183 if (ww->realhost)
184 MyFree(ww->realhost);
185 if (ww->servername)
186 MyFree(ww->servername);
187 if (ww->realname)
188 MyFree(ww->realname);
189 if (ww->away)
190 MyFree(ww->away);
191
192 return ww;
193}
194
195/** Clean and free a whowas record.
196 * @param[in] ww Whowas record to free.
197 */
198static void
199whowas_free(struct Whowas *ww)
200{
201 if (!ww)
202 return;
203
204 Debug((DEBUG_LIST, "Destroying whowas structure for %s", ww->name));
205
206 whowas_clean(ww);
207 MyFree(ww);
208
209 wwList.ww_alloc--;
210}
211
189935b1 212/** Return a fresh Whowas record.
213 * If the total number of records is smaller than determined by
214 * FEAT_NICKNAMEHISTORYLENGTH, allocate a new one. Otherwise,
215 * reuse the oldest record in use.
216 * @return A pointer to a clean Whowas.
217 */
218static struct Whowas *
219whowas_alloc(void)
220{
9f8856e9 221 struct Whowas *ww;
222
223 if (wwList.ww_alloc >= feature_int(FEAT_NICKNAMEHISTORYLENGTH)) {
224 /* reclaim the oldest whowas entry */
225 ww = whowas_clean(wwList.ww_tail);
226 } else {
227 /* allocate a new one */
228 wwList.ww_alloc++;
229 ww = (struct Whowas *) MyMalloc(sizeof(struct Whowas));
230 }
189935b1 231
9f8856e9 232 assert(ww != NULL);
233 memset(ww, 0, sizeof(*ww));
234 return ww;
189935b1 235}
236
237/** If necessary, trim the whowas list.
238 * This function trims the whowas list until it contains no more than
239 * FEAT_NICKNAMEHISTORYLENGTH records.
240 */
241void
242whowas_realloc(void)
243{
244 Debug((DEBUG_LIST, "whowas_realloc() called with alloc count %d, "
245 "history length %d, tail pointer %p", wwList.ww_alloc,
246 feature_int(FEAT_NICKNAMEHISTORYLENGTH), wwList.ww_tail));
247
248 while (wwList.ww_alloc > feature_int(FEAT_NICKNAMEHISTORYLENGTH)) {
249 if (!wwList.ww_tail) { /* list is empty... */
250 Debug((DEBUG_LIST, "whowas list emptied with alloc count %d",
251 wwList.ww_alloc));
252 return;
253 }
254
255 whowas_free(wwList.ww_tail); /* free oldest element of whowas list */
256 }
257}
258
259/** Add a client to the whowas list.
260 * @param[in] cptr Client to add.
261 * @param[in] still_on If non-zero, link the record to the client's personal history.
262 */
263void add_history(struct Client *cptr, int still_on)
264{
265 struct Whowas *ww;
266
267 if (!(ww = whowas_alloc()))
268 return; /* couldn't get a structure */
269
270 ww->hashv = hash_whowas_name(cli_name(cptr)); /* initialize struct */
271 ww->logoff = CurrentTime;
272 DupString(ww->name, cli_name(cptr));
273 DupString(ww->username, cli_user(cptr)->username);
274 DupString(ww->hostname, cli_user(cptr)->host);
275 if (HasHiddenHost(cptr))
276 DupString(ww->realhost, cli_user(cptr)->realhost);
277 DupString(ww->servername, cli_name(cli_user(cptr)->server));
278 DupString(ww->realname, cli_info(cptr));
279 if (cli_user(cptr)->away)
280 DupString(ww->away, cli_user(cptr)->away);
281
282 if (still_on) { /* user changed nicknames... */
283 ww->online = cptr;
284 if ((ww->cnext = cli_whowas(cptr)))
285 ww->cnext->cprevnextp = &ww->cnext;
286 ww->cprevnextp = &(cli_whowas(cptr));
287 cli_whowas(cptr) = ww;
288 } else /* user quit */
289 ww->online = 0;
290
291 /* link new whowas structure to list */
292 ww->wnext = wwList.ww_list;
293 if (wwList.ww_list)
294 wwList.ww_list->wprev = ww;
295 wwList.ww_list = ww;
296
297 if (!wwList.ww_tail) /* update the tail pointer... */
298 wwList.ww_tail = ww;
299
300 /* Now link it into the hash table */
301 if ((ww->hnext = whowashash[ww->hashv]))
302 ww->hnext->hprevnextp = &ww->hnext;
303 ww->hprevnextp = &whowashash[ww->hashv];
304 whowashash[ww->hashv] = ww;
305}
306
307/** Clear all Whowas::online pointers that point to a client.
308 * @param[in] cptr Client who is going offline.
309 */
310void off_history(const struct Client *cptr)
311{
312 struct Whowas *temp;
313
314 for (temp = cli_whowas(cptr); temp; temp = temp->cnext)
315 temp->online = NULL;
316}
317
318/** Find a client who has recently used a particular nickname.
319 * @param[in] nick Nickname to find.
320 * @param[in] timelimit Maximum age for entry.
321 * @return User's online client, or NULL if none is found.
322 */
323struct Client *get_history(const char *nick, time_t timelimit)
324{
325 struct Whowas *temp = whowashash[hash_whowas_name(nick)];
326 timelimit = CurrentTime - timelimit;
327
328 for (; temp; temp = temp->hnext)
329 if (0 == ircd_strcmp(nick, temp->name) && temp->logoff > timelimit)
330 return temp->online;
331
332 return NULL;
333}
334
335/** Count memory used by whowas list.
336 * @param[out] wwu Number of entries in whowas list.
337 * @param[out] wwum Total number of bytes used by nickname, username,
338 * hostname and servername fields.
339 * @param[out] wwa Number of away strings in whowas list.
340 * @param[out] wwam Total number of bytes used by away strings.
341 */
342void count_whowas_memory(int *wwu, size_t *wwum, int *wwa, size_t *wwam)
343{
344 struct Whowas *tmp;
345 int u = 0;
346 int a = 0;
347 size_t um = 0;
348 size_t am = 0;
349 assert(0 != wwu);
350 assert(0 != wwum);
351 assert(0 != wwa);
352 assert(0 != wwam);
353
354 for (tmp = wwList.ww_list; tmp; tmp = tmp->wnext) {
355 u++;
356 um += (strlen(tmp->name) + 1);
357 um += (strlen(tmp->username) + 1);
358 um += (strlen(tmp->hostname) + 1);
359 um += (strlen(tmp->servername) + 1);
360 if (tmp->away) {
361 a++;
362 am += (strlen(tmp->away) + 1);
363 }
364 }
365 *wwu = u;
366 *wwum = um;
367 *wwa = a;
368 *wwam = am;
369}
370
371/** Initialize whowas table. */
372void initwhowas(void)
373{
374 int i;
375
376 for (i = 0; i < WW_MAX; i++)
377 whowashash[i] = 0;
378}
379
380/** Calculate a hash value for a string.
381 * @param[in] name Nickname to calculate hash over.
382 * @return Calculated hash value.
383 */
384unsigned int hash_whowas_name(const char *name)
385{
386 unsigned int hash = 0;
387 unsigned int hash2 = 0;
388 unsigned char lower;
389
390 do
391 {
392 lower = ToLower(*name);
393 hash = (hash << 1) + lower;
394 hash2 = (hash2 >> 1) + lower;
395 }
396 while (*++name);
397
398 return ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) +
399 (hash2 & BITS_PER_COL_MASK);
400}
401