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