3 * IRC - Internet Relay Chat, ircd/whowas.c
4 * Copyright (C) 1990 Markku Savela
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)
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.
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.
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.
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
36 * --- comstud --- 5th August 1997
37 * Fixed for Undernet..
41 * --- Run --- 27th August 1997
42 * Speeded up the code, added comments.
62 static aWhowas whowas[NICKNAMEHISTORYLENGTH];
63 static aWhowas *whowashash[WW_MAX];
64 static aWhowas *whowas_next = whowas;
66 static unsigned int hash_whowas_name(register const char *name);
68 extern char *canonize(char *);
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
80 * Note however that BOTH reasons make it redundant to keep a whowas history
81 * for users that split off.
83 * The rewrite of comstud was many to safe cpu during net.breaks and therefore
84 * a bit redundant imho (Run).
86 * But - it was written anyway. So lets look at the structure of the
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).
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 ;).
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
119 * Note that following:
121 * a) We *only* (need to) change the 'hash list' and the 'online' list
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
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.
137 * I incorporated these considerations into the code below.
147 #define WHOWAS_UNUSED ((unsigned int)-1)
152 * Add a client (cptr) that just changed nick (still_on == true), or
153 * just signed off (still_on == false) to the `whowas' table.
155 * If the entry used was already in use, then this entry is
158 void add_history(aClient *cptr, int still_on)
161 ww.newww = whowas_next;
163 /* If this entry has already been used, remove it from the lists */
164 if (ww.newww->hashv != WHOWAS_UNUSED)
166 if (ww.oldww->online) /* No need to update cnext/cprev when offline! */
168 /* Remove ww.oldww from the linked list with the same `online' pointers */
169 *ww.oldww->cprevnextp = ww.oldww->cnext;
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;
180 /* Remove ww.oldww from the linked list with the same `hashv' */
181 *ww.oldww->hprevnextp = ww.oldww->hnext;
187 ww.oldww->hnext->hprevnextp = ww.oldww->hprevnextp;
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);
201 RunFree(ww.oldww->away);
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);
216 ww.newww->away = NULL;
218 /* Update/initialize online/cnext/cprev: */
219 if (still_on) /* User just changed nicknames */
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;
228 else /* User quitting */
229 ww.newww->online = NULL;
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;
237 /* Advance `whowas_next' to next entry in the `whowas' table: */
238 if (++whowas_next == &whowas[NICKNAMEHISTORYLENGTH])
239 whowas_next = whowas;
245 * Client `cptr' signed off: Set all `online' pointers
246 * corresponding to this client to NULL.
248 void off_history(const aClient *cptr)
252 for (temp = cptr->whowas; temp; temp = temp->cnext)
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.
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.
266 aClient *get_history(const char *nick, time_t timelimit)
268 aWhowas *temp = whowashash[hash_whowas_name(nick)];
269 timelimit = now - timelimit;
271 for (; temp; temp = temp->hnext)
272 if (!strCasediff(nick, temp->name) && temp->logoff > timelimit)
278 void count_whowas_memory(int *wwu, size_t *wwum, int *wwa, size_t *wwam)
280 register aWhowas *tmp;
283 size_t um = 0, am = 0;
285 for (i = 0, tmp = whowas; i < NICKNAMEHISTORYLENGTH; i++, tmp++)
286 if (tmp->hashv != WHOWAS_UNUSED)
289 um += (strlen(tmp->name) + 1);
290 um += (strlen(tmp->username) + 1);
291 um += (strlen(tmp->hostname) + 1);
292 um += (strlen(tmp->servername) + 1);
296 am += (strlen(tmp->away) + 1);
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)
314 int m_whowas(aClient *cptr, aClient *sptr, int parc, char *parv[])
316 register aWhowas *temp;
317 register int cur = 0;
318 int max = -1, found = 0;
323 sendto_one(sptr, err_str(ERR_NONICKNAMEGIVEN), me.name, parv[0]);
329 if (hunt_server(1, cptr, sptr, ":%s WHOWAS %s %s :%s", 3, parc, parv))
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)
337 /* Search through bucket, finding all nicknames that match */
339 for (temp = whowashash[hash_whowas_name(nick)]; temp; temp = temp->hnext)
341 if (!strCasediff(nick, temp->name))
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));
349 sendto_one(sptr, rpl_str(RPL_AWAY),
350 me.name, parv[0], temp->name, temp->away);
354 if (max >= 0 && cur >= max)
358 sendto_one(sptr, err_str(ERR_WASNOSUCHNICK), me.name, parv[0], nick);
359 /* To keep parv[1] intact for ENDOFWHOWAS */
363 sendto_one(sptr, rpl_str(RPL_ENDOFWHOWAS), me.name, parv[0], parv[1]);
367 void initwhowas(void)
371 for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
372 whowas[i].hashv = WHOWAS_UNUSED;
375 static unsigned int hash_whowas_name(register const char *name)
377 register unsigned int hash = 0;
378 register unsigned int hash2 = 0;
383 lower = toLower(*name);
384 hash = (hash << 1) + lower;
385 hash2 = (hash2 >> 1) + lower;
389 return ((hash & WW_MAX_INITIAL_MASK) << BITS_PER_COL) +
390 (hash2 & BITS_PER_COL_MASK);