a893490e02d32c56c29192274a8cb8729269408b
[ircu2.10.12-pk.git] / ircd / whowas.c
1
2 /*
3  * IRC - Internet Relay Chat, ircd/whowas.c
4  * Copyright (C) 1990 Markku Savela
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 1, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 /*
22  * --- avalon --- 6th April 1992
23  * rewritten to scrap linked lists and use a table of structures which
24  * is referenced like a circular loop. Should be faster and more efficient.
25  */
26
27 /*
28  * --- comstud --- 25th March 1997
29  * Everything rewritten from scratch.  Avalon's code was bad.  My version
30  * is faster and more efficient.  No more hangs on /squits and you can
31  * safely raise NICKNAMEHISTORYLENGTH to a higher value without hurting
32  * performance.
33  */
34
35 /*
36  * --- comstud --- 5th August 1997
37  * Fixed for Undernet..
38  */
39
40 /*
41  * --- Run --- 27th August 1997
42  * Speeded up the code, added comments.
43  */
44
45 #include "sys.h"
46 #include <stdlib.h>
47 #include "common.h"
48 #include "h.h"
49 #include "struct.h"
50 #include "numeric.h"
51 #include "send.h"
52 #include "s_misc.h"
53 #include "s_err.h"
54 #include "whowas.h"
55 #include "ircd.h"
56 #include "list.h"
57 #include "s_user.h"
58 #include "support.h"
59
60 RCSTAG_CC("$Id$");
61
62 static aWhowas whowas[NICKNAMEHISTORYLENGTH];
63 static aWhowas *whowashash[WW_MAX];
64 static aWhowas *whowas_next = whowas;
65
66 static unsigned int hash_whowas_name(register const char *name);
67
68 extern char *canonize(char *);
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 'aWhowas' 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 typedef union {
143   aWhowas *newww;
144   aWhowas *oldww;
145 } Current;
146
147 #define WHOWAS_UNUSED ((unsigned int)-1)
148
149 /*
150  * add_history
151  *
152  * Add a client (cptr) that just changed nick (still_on == true), or
153  * just signed off (still_on == false) to the `whowas' table.
154  *
155  * If the entry used was already in use, then this entry is
156  * freed (lost).
157  */
158 void add_history(aClient *cptr, int still_on)
159 {
160   register Current ww;
161   ww.newww = whowas_next;
162
163   /* If this entry has already been used, remove it from the lists */
164   if (ww.newww->hashv != WHOWAS_UNUSED)
165   {
166     if (ww.oldww->online)       /* No need to update cnext/cprev when offline! */
167     {
168       /* Remove ww.oldww from the linked list with the same `online' pointers */
169       *ww.oldww->cprevnextp = ww.oldww->cnext;
170
171       if (ww.oldww->cnext)
172         MyCoreDump;
173 #if 0
174       if (ww.oldww->cnext)      /* Never true, we always catch the
175                                    oldwwest nick of this client first */
176         ww.oldww->cnext->cprevnextp = ww.oldww->cprevnextp;
177 #endif
178
179     }
180     /* Remove ww.oldww from the linked list with the same `hashv' */
181     *ww.oldww->hprevnextp = ww.oldww->hnext;
182
183     if (ww.oldww->hnext)
184       MyCoreDump;
185 #if 0
186     if (ww.oldww->hnext)
187       ww.oldww->hnext->hprevnextp = ww.oldww->hprevnextp;
188 #endif
189
190     if (ww.oldww->name)
191       RunFree(ww.oldww->name);
192     if (ww.oldww->username)
193       RunFree(ww.oldww->username);
194     if (ww.oldww->hostname)
195       RunFree(ww.oldww->hostname);
196     if (ww.oldww->servername)
197       RunFree(ww.oldww->servername);
198     if (ww.oldww->realname)
199       RunFree(ww.oldww->realname);
200     if (ww.oldww->away)
201       RunFree(ww.oldww->away);
202   }
203
204   /* Initialize aWhoWas struct `newww' */
205   ww.newww->hashv = hash_whowas_name(cptr->name);
206   ww.newww->logoff = now;
207   DupString(ww.newww->name, cptr->name);
208   DupString(ww.newww->username, cptr->user->username);
209   DupString(ww.newww->hostname, cptr->user->host);
210   /* Should be changed to server numeric */
211   DupString(ww.newww->servername, cptr->user->server->name);
212   DupString(ww.newww->realname, cptr->info);
213   if (cptr->user->away)
214     DupString(ww.newww->away, cptr->user->away);
215   else
216     ww.newww->away = NULL;
217
218   /* Update/initialize online/cnext/cprev: */
219   if (still_on)                 /* User just changed nicknames */
220   {
221     ww.newww->online = cptr;
222     /* Add aWhowas struct `newww' to start of 'online list': */
223     if ((ww.newww->cnext = cptr->whowas))
224       ww.newww->cnext->cprevnextp = &ww.newww->cnext;
225     ww.newww->cprevnextp = &cptr->whowas;
226     cptr->whowas = ww.newww;
227   }
228   else                          /* User quitting */
229     ww.newww->online = NULL;
230
231   /* Add aWhowas struct `newww' to start of 'hashv list': */
232   if ((ww.newww->hnext = whowashash[ww.newww->hashv]))
233     ww.newww->hnext->hprevnextp = &ww.newww->hnext;
234   ww.newww->hprevnextp = &whowashash[ww.newww->hashv];
235   whowashash[ww.newww->hashv] = ww.newww;
236
237   /* Advance `whowas_next' to next entry in the `whowas' table: */
238   if (++whowas_next == &whowas[NICKNAMEHISTORYLENGTH])
239     whowas_next = whowas;
240 }
241
242 /*
243  * off_history
244  *
245  * Client `cptr' signed off: Set all `online' pointers
246  * corresponding to this client to NULL.
247  */
248 void off_history(const aClient *cptr)
249 {
250   aWhowas *temp;
251
252   for (temp = cptr->whowas; temp; temp = temp->cnext)
253     temp->online = NULL;
254 }
255
256 /*
257  * get_history
258  *
259  * Return a pointer to a client that had nick `nick' not more then
260  * `timelimit' seconds ago, if still on line.  Otherwise return NULL.
261  *
262  * This function is used for "nick chasing"; since the use of numeric
263  * nicks for "upstream" messages in ircu2.10, this is only used for
264  * looking up non-existing nicks in client->server messages.
265  */
266 aClient *get_history(const char *nick, time_t timelimit)
267 {
268   aWhowas *temp = whowashash[hash_whowas_name(nick)];
269   timelimit = now - timelimit;
270
271   for (; temp; temp = temp->hnext)
272     if (!strCasediff(nick, temp->name) && temp->logoff > timelimit)
273       return temp->online;
274
275   return NULL;
276 }
277
278 void count_whowas_memory(int *wwu, size_t *wwum, int *wwa, size_t *wwam)
279 {
280   register aWhowas *tmp;
281   register int i;
282   int u = 0, a = 0;
283   size_t um = 0, am = 0;
284
285   for (i = 0, tmp = whowas; i < NICKNAMEHISTORYLENGTH; i++, tmp++)
286     if (tmp->hashv != WHOWAS_UNUSED)
287     {
288       u++;
289       um += (strlen(tmp->name) + 1);
290       um += (strlen(tmp->username) + 1);
291       um += (strlen(tmp->hostname) + 1);
292       um += (strlen(tmp->servername) + 1);
293       if (tmp->away)
294       {
295         a++;
296         am += (strlen(tmp->away) + 1);
297       }
298     }
299
300   *wwu = u;
301   *wwum = um;
302   *wwa = a;
303   *wwam = am;
304 }
305
306 /*
307  * m_whowas
308  *
309  * parv[0] = sender prefix
310  * parv[1] = nickname queried
311  * parv[2] = maximum returned items (optional, default is unlimitted)
312  * parv[3] = remote server target (Opers only, max returned items 20)
313  */
314 int m_whowas(aClient *cptr, aClient *sptr, int parc, char *parv[])
315 {
316   register aWhowas *temp;
317   register int cur = 0;
318   int max = -1, found = 0;
319   char *p, *nick, *s;
320
321   if (parc < 2)
322   {
323     sendto_one(sptr, err_str(ERR_NONICKNAMEGIVEN), me.name, parv[0]);
324     return 0;
325   }
326   if (parc > 2)
327     max = atoi(parv[2]);
328   if (parc > 3)
329     if (hunt_server(1, cptr, sptr, ":%s WHOWAS %s %s :%s", 3, parc, parv))
330       return 0;
331
332   parv[1] = canonize(parv[1]);
333   if (!MyConnect(sptr) && (max > 20))
334     max = 20;                   /* Set max replies at 20 */
335   for (s = parv[1]; (nick = strtoken(&p, s, ",")); s = NULL)
336   {
337     /* Search through bucket, finding all nicknames that match */
338     found = 0;
339     for (temp = whowashash[hash_whowas_name(nick)]; temp; temp = temp->hnext)
340     {
341       if (!strCasediff(nick, temp->name))
342       {
343         sendto_one(sptr, rpl_str(RPL_WHOWASUSER),
344             me.name, parv[0], temp->name, temp->username,
345             temp->hostname, temp->realname);
346         sendto_one(sptr, rpl_str(RPL_WHOISSERVER), me.name, parv[0],
347             temp->name, temp->servername, myctime(temp->logoff));
348         if (temp->away)
349           sendto_one(sptr, rpl_str(RPL_AWAY),
350               me.name, parv[0], temp->name, temp->away);
351         cur++;
352         found++;
353       }
354       if (max >= 0 && cur >= max)
355         break;
356     }
357     if (!found)
358       sendto_one(sptr, err_str(ERR_WASNOSUCHNICK), me.name, parv[0], nick);
359     /* To keep parv[1] intact for ENDOFWHOWAS */
360     if (p)
361       p[-1] = ',';
362   }
363   sendto_one(sptr, rpl_str(RPL_ENDOFWHOWAS), me.name, parv[0], parv[1]);
364   return 0;
365 }
366
367 void initwhowas(void)
368 {
369   register int i;
370
371   for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
372     whowas[i].hashv = WHOWAS_UNUSED;
373 }
374
375 static unsigned int hash_whowas_name(register const char *name)
376 {
377   register unsigned int hash = 0;
378   register unsigned int hash2 = 0;
379   register char lower;
380
381   do
382   {
383     lower = toLower(*name);
384     hash = (hash << 1) + lower;
385     hash2 = (hash2 >> 1) + lower;
386   }
387   while (*++name);
388
389   return ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) +
390       (hash2 & BITS_PER_COL_MASK);
391 }