fixed ssl.c bug when ssl backend returns IO_BLOCKED but IO engine doesn't get informe...
[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 #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. */
61 static 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. */
68 struct Whowas* whowashash[WW_MAX];
69
70 /** @file
71  * @brief Manipulation functions for the whowas list.
72  * @version $Id: whowas.c 1633 2006-03-25 03:46:56Z entrope $
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  */
150 static struct Whowas *
151 whowas_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  */
198 static void
199 whowas_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
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  */
218 static struct Whowas *
219 whowas_alloc(void)
220 {
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   }
231
232   assert(ww != NULL);
233   memset(ww, 0, sizeof(*ww));
234   return ww;
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  */
241 void
242 whowas_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  */
263 void 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  */
310 void 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  */
323 struct 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  */
342 void 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. */
372 void 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  */
384 unsigned 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