Author: Kev <klmitch@mit.edu>
[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 "whowas.h"
38 #include "client.h"
39 #include "ircd.h"
40 #include "ircd_alloc.h"
41 #include "ircd_chattr.h"
42 #include "ircd_string.h"
43 #include "list.h"
44 #include "numeric.h"
45 #include "s_misc.h"
46 #include "s_user.h"
47 #include "send.h"
48 #include "struct.h"
49 #include "support.h"
50 #include "sys.h"
51 #include "msg.h"
52
53 #include <assert.h>
54 #include <stdlib.h>
55 #include <string.h>
56
57
58 static struct Whowas  whowas[NICKNAMEHISTORYLENGTH];
59 static struct Whowas* whowas_next = whowas;
60 struct Whowas* whowashash[WW_MAX];
61
62 /*
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
71  *    nick chasing.
72  * Note however that BOTH reasons make it redundant to keep a whowas history
73  * for users that split off.
74  *
75  * The rewrite of comstud was many to safe cpu during net.breaks and therefore
76  * a bit redundant imho (Run).
77  *
78  * But - it was written anyway.  So lets look at the structure of the
79  * whowas history now:
80  *
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).
85  *
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 ;).
92  *
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
107  *    signs off.
108  *
109  * 27/8/79:
110  *
111  * Note that following:
112  *
113  * a) We *only* (need to) change the 'hash list' and the 'online' list
114  *    in add_history().
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
121  *    always be NULL.
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.
128  *
129  * I incorporated these considerations into the code below.
130  *
131  * --Run
132  */
133
134 typedef union {
135   struct Whowas *newww;
136   struct Whowas *oldww;
137 } Current;
138
139 #define WHOWAS_UNUSED ((unsigned int)-1)
140
141 /*
142  * add_history
143  *
144  * Add a client (cptr) that just changed nick (still_on == true), or
145  * just signed off (still_on == false) to the `whowas' table.
146  *
147  * If the entry used was already in use, then this entry is
148  * freed (lost).
149  */
150 void add_history(struct Client *cptr, int still_on)
151 {
152   Current ww;
153   ww.newww = whowas_next;
154
155   /* If this entry has already been used, remove it from the lists */
156   if (ww.newww->hashv != WHOWAS_UNUSED)
157   {
158     if (ww.oldww->online)       /* No need to update cnext/cprev when offline! */
159     {
160       /* Remove ww.oldww from the linked list with the same `online' pointers */
161       *ww.oldww->cprevnextp = ww.oldww->cnext;
162
163       assert(0 == ww.oldww->cnext);
164
165     }
166     /* Remove ww.oldww from the linked list with the same `hashv' */
167     *ww.oldww->hprevnextp = ww.oldww->hnext;
168
169     assert(0 == ww.oldww->hnext);
170
171     if (ww.oldww->name)
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);
181     if (ww.oldww->away)
182       MyFree(ww.oldww->away);
183   }
184
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);
196   else
197     ww.newww->away = NULL;
198
199   /* Update/initialize online/cnext/cprev: */
200   if (still_on)                 /* User just changed nicknames */
201   {
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;
208   }
209   else                          /* User quitting */
210     ww.newww->online = NULL;
211
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;
217
218   /* Advance `whowas_next' to next entry in the `whowas' table: */
219   if (++whowas_next == &whowas[NICKNAMEHISTORYLENGTH])
220     whowas_next = whowas;
221 }
222
223 /*
224  * off_history
225  *
226  * Client `cptr' signed off: Set all `online' pointers
227  * corresponding to this client to NULL.
228  */
229 void off_history(const struct Client *cptr)
230 {
231   struct Whowas *temp;
232
233   for (temp = cli_whowas(cptr); temp; temp = temp->cnext)
234     temp->online = NULL;
235 }
236
237 /*
238  * get_history
239  *
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.
242  *
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.
246  */
247 struct Client *get_history(const char *nick, time_t timelimit)
248 {
249   struct Whowas *temp = whowashash[hash_whowas_name(nick)];
250   timelimit = CurrentTime - timelimit;
251
252   for (; temp; temp = temp->hnext)
253     if (0 == ircd_strcmp(nick, temp->name) && temp->logoff > timelimit)
254       return temp->online;
255
256   return NULL;
257 }
258
259 void count_whowas_memory(int *wwu, size_t *wwum, int *wwa, size_t *wwam)
260 {
261   struct Whowas *tmp;
262   int i;
263   int u = 0;
264   int a = 0;
265   size_t um = 0;
266   size_t am = 0;
267   assert(0 != wwu);
268   assert(0 != wwum);
269   assert(0 != wwa);
270   assert(0 != wwam);
271
272   for (i = 0, tmp = whowas; i < NICKNAMEHISTORYLENGTH; i++, tmp++) {
273     if (tmp->hashv != WHOWAS_UNUSED) {
274       u++;
275       um += (strlen(tmp->name) + 1);
276       um += (strlen(tmp->username) + 1);
277       um += (strlen(tmp->hostname) + 1);
278       um += (strlen(tmp->servername) + 1);
279       if (tmp->away) {
280         a++;
281         am += (strlen(tmp->away) + 1);
282       }
283     }
284   }
285   *wwu = u;
286   *wwum = um;
287   *wwa = a;
288   *wwam = am;
289 }
290
291
292 void initwhowas(void)
293 {
294   int i;
295
296   for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
297     whowas[i].hashv = WHOWAS_UNUSED;
298 }
299
300 unsigned int hash_whowas_name(const char *name)
301 {
302   unsigned int hash = 0;
303   unsigned int hash2 = 0;
304   unsigned char lower;
305
306   do
307   {
308     lower = ToLower(*name);
309     hash = (hash << 1) + lower;
310     hash2 = (hash2 >> 1) + lower;
311   }
312   while (*++name);
313
314   return ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) +
315       (hash2 & BITS_PER_COL_MASK);
316 }
317