struct Client *servptr; /* Points to server this Client is on */
struct Client *from; /* == self, if Local Client, *NEVER* NULL! */
- struct Whowas *whowas; /* Pointers to whowas structs */
+ rb_dlink_list whowas_clist;
+
time_t tsinfo; /* TS on the nick, SVINFO on server */
unsigned int umodes; /* opers, normal users subset */
unsigned int flags; /* client flags */
#define NUMERIC_STR_366 ":%s 366 %s %s :End of /NAMES list."
#define NUMERIC_STR_367 ":%s 367 %s %s %s %s %lu"
#define NUMERIC_STR_368 ":%s 368 %s %s :End of Channel Ban List"
-#define NUMERIC_STR_369 ":%s 369 %s %s :End of WHOWAS"
+#define NUMERIC_STR_369 "%s :End of WHOWAS"
#define NUMERIC_STR_371 ":%s"
#define NUMERIC_STR_372 ":%s 372 %s :- %s"
#define NUMERIC_STR_374 ":End of /INFO list."
#define NUMERIC_STR_403 "%s :No such channel"
#define NUMERIC_STR_404 "%s :Cannot send to channel"
#define NUMERIC_STR_405 ":%s 405 %s %s :You have joined too many channels"
-#define NUMERIC_STR_406 ":%s 406 %s %s :There was no such nickname"
+#define NUMERIC_STR_406 ":%s :There was no such nickname"
#define NUMERIC_STR_407 ":%s 407 %s %s :Too many recipients."
#define NUMERIC_STR_409 ":%s 409 %s :No origin specified"
#define NUMERIC_STR_410 ":%s 410 %s %s :Invalid CAP subcommand"
#include "setup.h"
-/*
- * Whowas hash table size
- *
- */
-#define WW_MAX_BITS 16
-#define WW_MAX 65536
-
struct User;
struct Client;
*/
struct Whowas
{
- int hashv;
+ struct whowas_top *wtop;
+ rb_dlink_node wnode; /* for the wtop linked list */
+ rb_dlink_node cnode; /* node for online clients */
+ rb_dlink_node whowas_node; /* node for the whowas linked list */
char name[NICKLEN + 1];
char username[USERLEN + 1];
char hostname[HOSTLEN + 1];
const char *servername;
time_t logoff;
struct Client *online; /* Pointer to new nickname for chasing or NULL */
- struct Whowas *next; /* for hash table... */
- struct Whowas *prev; /* for hash table... */
- struct Whowas *cnext; /* for client struct linked list */
- struct Whowas *cprev; /* for client struct linked list */
};
/* Flags */
/*
** initwhowas
*/
-extern void initwhowas(void);
+extern void whowas_init(void);
/*
** add_history
** Client must be a fully registered user (specifically,
** the user structure must have been allocated).
*/
-void add_history(struct Client *, int);
+void whowas_add_history(struct Client *, int);
/*
** off_history
** structures and it must know when they cease to exist. This
** also implicitly calls AddHistory.
*/
-void off_history(struct Client *);
+void whowas_off_history(struct Client *);
/*
** get_history
** nickname within the timelimit. Returns NULL, if no
** one found...
*/
-struct Client *get_history(const char *, time_t);
+struct Client *whowas_get_history(const char *, time_t);
/* Nick name */
/* Time limit in seconds */
-/*
-** for debugging...counts related structures stored in whowas array.
-*/
-void count_whowas_memory(size_t *, size_t *);
-
-/* XXX m_whowas.c in modules needs these */
-extern struct Whowas WHOWAS[];
-extern struct Whowas *WHOWASHASH[];
-extern unsigned int hash_whowas_name(const char *name);
+rb_dlink_list *whowas_get_list(const char *name);
+void whowas_set_size(int whowas_length);
+void whowas_memory_usage(size_t *count, size_t *memused);
#endif /* INCLUDED_whowas_h */
/* Do all of the nick-changing gymnastics. */
client_p->tsinfo = rb_current_time();
- add_history(client_p, 1);
+ whowas_add_history(client_p, 1);
monitor_signoff(client_p);
if(who || IsDigit(*user))
return who;
- if(!(who = get_history(user, (long) KILLCHASETIMELIMIT)))
+ if(!(who = whowas_get_history(user, (long) KILLCHASETIMELIMIT)))
{
sendto_one_numeric(source_p, ERR_NOSUCHNICK,
form_str(ERR_NOSUCHNICK), user);
/* Clean up allow lists */
del_all_accepts(source_p);
- add_history(source_p, 0);
- off_history(source_p);
+ whowas_add_history(source_p, 0);
+ whowas_off_history(source_p);
monitor_signoff(source_p);
init_hook();
init_channels();
initclass();
- initwhowas();
+ whowas_init();
init_reject();
init_cache();
init_monitor();
rb_strlcpy(target_p->host, host, sizeof target_p->host);
if (changed)
- add_history(target_p, 1);
+ whowas_add_history(target_p, 1);
del_from_client_hash(target_p->name, target_p);
rb_strlcpy(target_p->name, nick, NICKLEN);
*
* Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
* Copyright (C) 1996-2002 Hybrid Development Team
- * Copyright (C) 2002-2005 ircd-ratbox development team
+ * Copyright (C) 2002-2012 ircd-ratbox development team
+ * Copyright (C) 2016 William Pitcock <nenolod@dereferenced.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
- *
- * $Id: whowas.c 1717 2006-07-04 14:41:11Z jilles $
*/
#include "stdinc.h"
-
-#include "whowas.h"
-#include "client.h"
-#include "common.h"
#include "hash.h"
+#include "whowas.h"
#include "match.h"
#include "ircd.h"
-#include "ircd_defs.h"
#include "numeric.h"
+#include "s_assert.h"
#include "s_serv.h"
#include "s_user.h"
#include "send.h"
#include "s_conf.h"
+#include "client.h"
+#include "send.h"
+#include "logger.h"
#include "scache.h"
-#include "s_assert.h"
+#include "irc_radixtree.h"
-/* internally defined function */
-static void add_whowas_to_clist(struct Whowas **, struct Whowas *);
-static void del_whowas_from_clist(struct Whowas **, struct Whowas *);
-static void add_whowas_to_list(struct Whowas **, struct Whowas *);
-static void del_whowas_from_list(struct Whowas **, struct Whowas *);
+struct whowas_top
+{
+ char *name;
+ rb_dlink_list wwlist;
+};
-struct Whowas WHOWAS[NICKNAMEHISTORYLENGTH];
-struct Whowas *WHOWASHASH[WW_MAX];
+static struct irc_radixtree *whowas_tree = NULL;
+static rb_dlink_list whowas_list = {NULL, NULL, 0};
+static unsigned int whowas_list_length = NICKNAMEHISTORYLENGTH;
+static void whowas_trim(void *unused);
-static int whowas_next = 0;
+static void
+whowas_free_wtop(struct whowas_top *wtop)
+{
+ if(rb_dlink_list_length(&wtop->wwlist) == 0)
+ {
+ irc_radixtree_delete(whowas_tree, wtop->name);
+ rb_free(wtop->name);
+ rb_free(wtop);
+ }
+}
-unsigned int hash_whowas_name(const char *name)
+static struct whowas_top *
+whowas_get_top(const char *name)
{
- return fnv_hash_upper((const unsigned char *) name, WW_MAX_BITS);
+ struct whowas_top *wtop;
+
+ wtop = irc_radixtree_retrieve(whowas_tree, name);
+ if (wtop != NULL)
+ return wtop;
+
+ wtop = rb_malloc(sizeof(struct whowas_top));
+ wtop->name = rb_strdup(name);
+ irc_radixtree_add(whowas_tree, wtop->name, wtop);
+
+ return wtop;
}
-void add_history(struct Client *client_p, int online)
+rb_dlink_list *
+whowas_get_list(const char *name)
{
- struct Whowas *who = &WHOWAS[whowas_next];
+ struct whowas_top *wtop;
+ wtop = irc_radixtree_retrieve(whowas_tree, name);
+ if(wtop == NULL)
+ return NULL;
+ return &wtop->wwlist;
+}
+void
+whowas_add_history(struct Client *client_p, int online)
+{
+ struct whowas_top *wtop;
+ struct Whowas *who;
s_assert(NULL != client_p);
if(client_p == NULL)
return;
- if(who->hashv != -1)
- {
- if(who->online)
- del_whowas_from_clist(&(who->online->whowas), who);
- del_whowas_from_list(&WHOWASHASH[who->hashv], who);
- }
- who->hashv = hash_whowas_name(client_p->name);
+ /* trim some of the entries if we're getting well over our history length */
+ if(rb_dlink_list_length(&whowas_list) > whowas_list_length + 100)
+ whowas_trim(NULL);
+
+ wtop = whowas_get_top(client_p->name);
+ who = rb_malloc(sizeof(struct Whowas));
+ who->wtop = wtop;
who->logoff = rb_current_time();
- /*
- * NOTE: strcpy ok here, the sizes in the client struct MUST
- * match the sizes in the whowas struct
- */
+
rb_strlcpy(who->name, client_p->name, sizeof(who->name));
- strcpy(who->username, client_p->username);
- strcpy(who->hostname, client_p->host);
- strcpy(who->realname, client_p->info);
- strcpy(who->suser, client_p->user->suser);
- strcpy(who->sockhost, client_p->sockhost);
+ rb_strlcpy(who->username, client_p->username, sizeof(who->username));
+ rb_strlcpy(who->hostname, client_p->host, sizeof(who->hostname));
+ rb_strlcpy(who->realname, client_p->info, sizeof(who->realname));
+ rb_strlcpy(who->sockhost, client_p->sockhost, sizeof(who->sockhost));
+
who->flags = (IsIPSpoof(client_p) ? WHOWAS_IP_SPOOFING : 0) |
(IsDynSpoof(client_p) ? WHOWAS_DYNSPOOF : 0);
+ /* this is safe do to with the servername cache */
who->servername = scache_get_name(client_p->servptr->serv->nameinfo);
if(online)
{
who->online = client_p;
- add_whowas_to_clist(&(client_p->whowas), who);
+ rb_dlinkAdd(who, &who->cnode, &client_p->whowas_clist);
}
else
who->online = NULL;
- add_whowas_to_list(&WHOWASHASH[who->hashv], who);
- whowas_next++;
- if(whowas_next == NICKNAMEHISTORYLENGTH)
- whowas_next = 0;
+
+ rb_dlinkAdd(who, &who->wnode, &wtop->wwlist);
+ rb_dlinkAdd(who, &who->whowas_node, &whowas_list);
}
-void off_history(struct Client *client_p)
+
+void
+whowas_off_history(struct Client *client_p)
{
- struct Whowas *temp, *next;
+ rb_dlink_node *ptr, *next;
- for (temp = client_p->whowas; temp; temp = next)
+ RB_DLINK_FOREACH_SAFE(ptr, next, client_p->whowas_clist.head)
{
- next = temp->cnext;
- temp->online = NULL;
- del_whowas_from_clist(&(client_p->whowas), temp);
+ struct Whowas *who = ptr->data;
+ who->online = NULL;
+ rb_dlinkDelete(&who->cnode, &client_p->whowas_clist);
}
}
-struct Client *get_history(const char *nick, time_t timelimit)
+struct Client *
+whowas_get_history(const char *nick, time_t timelimit)
{
- struct Whowas *temp;
- int blah;
+ struct whowas_top *wtop;
+ rb_dlink_node *ptr;
+
+ wtop = irc_radixtree_retrieve(whowas_tree, nick);
+ if(wtop == NULL)
+ return NULL;
timelimit = rb_current_time() - timelimit;
- blah = hash_whowas_name(nick);
- temp = WHOWASHASH[blah];
- for (; temp; temp = temp->next)
+
+ RB_DLINK_FOREACH_PREV(ptr, wtop->wwlist.tail)
{
- if(irccmp(nick, temp->name))
- continue;
- if(temp->logoff < timelimit)
- continue;
- return temp->online;
+ struct Whowas *who = ptr->data;
+ if(who->logoff >= timelimit)
+ {
+ return who->online;
+ }
}
+
return NULL;
}
-void count_whowas_memory(size_t * wwu, size_t * wwum)
+static void
+whowas_trim(void *unused)
{
- *wwu = NICKNAMEHISTORYLENGTH;
- *wwum = NICKNAMEHISTORYLENGTH * sizeof(struct Whowas);
-}
+ long over;
-void
-initwhowas()
-{
- int i;
+ if(rb_dlink_list_length(&whowas_list) < whowas_list_length)
+ return;
+ over = rb_dlink_list_length(&whowas_list) - whowas_list_length;
- for (i = 0; i < NICKNAMEHISTORYLENGTH; i++)
+ /* remove whowas entries over the configured length */
+ for(long i = 0; i < over; i++)
{
- memset((void *) &WHOWAS[i], 0, sizeof(struct Whowas));
- WHOWAS[i].hashv = -1;
+ if(whowas_list.tail != NULL && whowas_list.tail->data != NULL)
+ {
+ struct Whowas *twho = whowas_list.tail->data;
+ if(twho->online != NULL)
+ rb_dlinkDelete(&twho->cnode, &twho->online->whowas_clist);
+ rb_dlinkDelete(&twho->wnode, &twho->wtop->wwlist);
+ rb_dlinkDelete(&twho->whowas_node, &whowas_list);
+ whowas_free_wtop(twho->wtop);
+ rb_free(twho);
+ }
}
- for (i = 0; i < WW_MAX; i++)
- WHOWASHASH[i] = NULL;
-}
-
-
-static void
-add_whowas_to_clist(struct Whowas **bucket, struct Whowas *whowas)
-{
- whowas->cprev = NULL;
- if((whowas->cnext = *bucket) != NULL)
- whowas->cnext->cprev = whowas;
- *bucket = whowas;
}
-static void
-del_whowas_from_clist(struct Whowas **bucket, struct Whowas *whowas)
+void
+whowas_init(void)
{
- if(whowas->cprev)
- whowas->cprev->cnext = whowas->cnext;
- else
- *bucket = whowas->cnext;
- if(whowas->cnext)
- whowas->cnext->cprev = whowas->cprev;
+ whowas_tree = irc_radixtree_create("whowas", irc_radixtree_irccasecanon);
+ if(whowas_list_length == 0)
+ {
+ whowas_list_length = NICKNAMEHISTORYLENGTH;
+ }
+ rb_event_add("whowas_trim", whowas_trim, NULL, 10);
}
-static void
-add_whowas_to_list(struct Whowas **bucket, struct Whowas *whowas)
+void
+whowas_set_size(int len)
{
- whowas->prev = NULL;
- if((whowas->next = *bucket) != NULL)
- whowas->next->prev = whowas;
- *bucket = whowas;
+ whowas_list_length = len;
+ whowas_trim(NULL);
}
-static void
-del_whowas_from_list(struct Whowas **bucket, struct Whowas *whowas)
+void
+whowas_memory_usage(size_t * count, size_t * memused)
{
- if(whowas->prev)
- whowas->prev->next = whowas->next;
- else
- *bucket = whowas->next;
- if(whowas->next)
- whowas->next->prev = whowas->prev;
+ *count = rb_dlink_list_length(&whowas_list);
+ *memused += *count * sizeof(struct Whowas);
+ *memused += sizeof(struct whowas_top) * irc_radixtree_size(whowas_tree);
}
** rewrite the KILL for this new nickname--this keeps
** servers in synch when nick change and kill collide
*/
- if((target_p = get_history(user, (long) KILLCHASETIMELIMIT)) == NULL)
+ if((target_p = whowas_get_history(user, (long) KILLCHASETIMELIMIT)) == NULL)
{
if (strchr(user, '.'))
sendto_one_numeric(source_p, ERR_CANTKILLSERVER, form_str(ERR_CANTKILLSERVER));
* not an uid, automatically rewrite the KILL for this new nickname.
* --this keeps servers in synch when nick change and kill collide
*/
- if(IsDigit(*user) || (!(target_p = get_history(user, (long) KILLCHASETIMELIMIT))))
+ if(IsDigit(*user) || (!(target_p = whowas_get_history(user, (long) KILLCHASETIMELIMIT))))
{
sendto_one_numeric(source_p, ERR_NOSUCHNICK,
form_str(ERR_NOSUCHNICK), IsDigit(*user) ? "*" : user);
/* send the nick change to servers.. */
if(source_p->user)
{
- add_history(source_p, 1);
+ whowas_add_history(source_p, 1);
if (dosend)
{
if(source_p->user)
{
- add_history(source_p, 1);
+ whowas_add_history(source_p, 1);
if (dosend)
{
sendto_server(client_p, NULL, CAP_TS6, NOCAPS, ":%s NICK %s :%ld",
target_p->name, target_p->username,
target_p->host, parv[2]);
- add_history(target_p, 1);
+ whowas_add_history(target_p, 1);
sendto_server(NULL, NULL, CAP_TS6, NOCAPS, ":%s NICK %s :%ld",
use_id(target_p), parv[2], (long) target_p->tsinfo);
size_t total_memory = 0;
- count_whowas_memory(&ww, &wwm);
+ whowas_memory_usage(&ww, &wwm);
RB_DLINK_FOREACH(ptr, global_client_list.head)
{
static int
m_whowas(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
{
- struct Whowas *temp;
+ rb_dlink_list *whowas_list;
+ rb_dlink_node *ptr;
int cur = 0;
- int max = -1, found = 0;
+ int max = -1;
char *p;
const char *nick;
char tbuf[26];
{
sendto_one(source_p, form_str(RPL_LOAD2HI),
me.name, source_p->name, "WHOWAS");
- sendto_one(source_p, form_str(RPL_ENDOFWHOWAS),
- me.name, source_p->name, parv[1]);
+ sendto_one_numeric(source_p, RPL_ENDOFWHOWAS, form_str(RPL_ENDOFWHOWAS),
+ parv[1]);
return 0;
}
else
nick = parv[1];
sendq_limit = get_sendq(client_p) * 9 / 10;
+ whowas_list = whowas_get_list(nick);
- temp = WHOWASHASH[hash_whowas_name(nick)];
- found = 0;
- for (; temp; temp = temp->next)
+ if(whowas_list == NULL)
{
- if(!irccmp(nick, temp->name))
+ sendto_one_numeric(source_p, ERR_WASNOSUCHNICK, form_str(ERR_WASNOSUCHNICK), nick);
+ sendto_one_numeric(source_p, RPL_ENDOFWHOWAS, form_str(RPL_ENDOFWHOWAS), parv[1]);
+ return 0;
+ }
+
+ RB_DLINK_FOREACH(ptr, whowas_list->head)
+ {
+ struct Whowas *temp = ptr->data;
+ if(cur > 0 && rb_linebuf_len(&client_p->localClient->buf_sendq) > sendq_limit)
{
- if(cur > 0 && rb_linebuf_len(&client_p->localClient->buf_sendq) > sendq_limit)
- {
- sendto_one(source_p, form_str(ERR_TOOMANYMATCHES),
- me.name, source_p->name, "WHOWAS");
- break;
- }
- sendto_one(source_p, form_str(RPL_WHOWASUSER),
- me.name, source_p->name, temp->name,
- temp->username, temp->hostname, temp->realname);
- if (!EmptyString(temp->sockhost) &&
- strcmp(temp->sockhost, "0") &&
- show_ip_whowas(temp, source_p))
-#if 0
- sendto_one(source_p, form_str(RPL_WHOWASREAL),
- me.name, source_p->name, temp->name,
- "<untracked>", temp->sockhost);
-#else
- sendto_one_numeric(source_p, RPL_WHOISACTUALLY,
- form_str(RPL_WHOISACTUALLY),
- temp->name, temp->sockhost);
-#endif
- if (!EmptyString(temp->suser))
- sendto_one_numeric(source_p, RPL_WHOISLOGGEDIN,
- "%s %s :was logged in as",
- temp->name, temp->suser);
- sendto_one_numeric(source_p, RPL_WHOISSERVER,
- form_str(RPL_WHOISSERVER),
- temp->name, temp->servername,
- rb_ctime(temp->logoff, tbuf, sizeof(tbuf)));
- cur++;
- found++;
+ sendto_one(source_p, form_str(ERR_TOOMANYMATCHES),
+ me.name, source_p->name, "WHOWAS");
+ break;
}
+
+ sendto_one(source_p, form_str(RPL_WHOWASUSER),
+ me.name, source_p->name, temp->name,
+ temp->username, temp->hostname, temp->realname);
+ if (!EmptyString(temp->sockhost) &&
+ strcmp(temp->sockhost, "0") &&
+ show_ip_whowas(temp, source_p))
+ sendto_one_numeric(source_p, RPL_WHOISACTUALLY,
+ form_str(RPL_WHOISACTUALLY),
+ temp->name, temp->sockhost);
+
+ if (!EmptyString(temp->suser))
+ sendto_one_numeric(source_p, RPL_WHOISLOGGEDIN,
+ "%s %s :was logged in as",
+ temp->name, temp->suser);
+
+ sendto_one_numeric(source_p, RPL_WHOISSERVER,
+ form_str(RPL_WHOISSERVER),
+ temp->name, temp->servername,
+ rb_ctime(temp->logoff, tbuf, sizeof(tbuf)));
+
+ cur++;
if(max > 0 && cur >= max)
break;
}
- if(!found)
- sendto_one(source_p, form_str(ERR_WASNOSUCHNICK),
- me.name, source_p->name, nick);
-
- sendto_one(source_p, form_str(RPL_ENDOFWHOWAS),
- me.name, source_p->name, parv[1]);
+ sendto_one_numeric(source_p, RPL_ENDOFWHOWAS, form_str(RPL_ENDOFWHOWAS), parv[1]);
return 0;
}