From b47f8a4fda31315010464212717fc8be226ba7dc Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Sat, 23 Jan 2016 11:16:34 -0500 Subject: [PATCH] ircd: import modified version of ratbox 3.1 whowas code --- include/client.h | 3 +- include/messages.h | 4 +- include/whowas.h | 36 ++----- ircd/client.c | 8 +- ircd/ircd.c | 2 +- ircd/s_user.c | 2 +- ircd/whowas.c | 246 +++++++++++++++++++++++------------------- modules/core/m_kill.c | 4 +- modules/core/m_nick.c | 4 +- modules/m_services.c | 2 +- modules/m_stats.c | 2 +- modules/m_whowas.c | 87 ++++++++------- 12 files changed, 205 insertions(+), 195 deletions(-) diff --git a/include/client.h b/include/client.h index 4300500e3..0261c7753 100644 --- a/include/client.h +++ b/include/client.h @@ -117,7 +117,8 @@ struct Client 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 */ diff --git a/include/messages.h b/include/messages.h index 8ae8ee87b..d54c81be4 100644 --- a/include/messages.h +++ b/include/messages.h @@ -131,7 +131,7 @@ #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." @@ -147,7 +147,7 @@ #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" diff --git a/include/whowas.h b/include/whowas.h index ca3dde9f7..9d75d1cd9 100644 --- a/include/whowas.h +++ b/include/whowas.h @@ -31,13 +31,6 @@ #include "setup.h" -/* - * Whowas hash table size - * - */ -#define WW_MAX_BITS 16 -#define WW_MAX 65536 - struct User; struct Client; @@ -48,7 +41,10 @@ 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]; @@ -59,10 +55,6 @@ struct Whowas 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 */ @@ -72,7 +64,7 @@ struct Whowas /* ** initwhowas */ -extern void initwhowas(void); +extern void whowas_init(void); /* ** add_history @@ -81,7 +73,7 @@ extern void initwhowas(void); ** 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 @@ -90,7 +82,7 @@ void add_history(struct Client *, int); ** 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 @@ -98,18 +90,12 @@ void off_history(struct Client *); ** 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 */ diff --git a/ircd/client.c b/ircd/client.c index 2fc1f2260..1948e995e 100644 --- a/ircd/client.c +++ b/ircd/client.c @@ -672,7 +672,7 @@ resv_nick_fnc(const char *mask, const char *reason, int temp_time) /* 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); @@ -872,7 +872,7 @@ find_chasing(struct Client *source_p, const char *user, int *chasing) 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); @@ -1247,8 +1247,8 @@ exit_generic_client(struct Client *client_p, struct Client *source_p, struct Cli /* 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); diff --git a/ircd/ircd.c b/ircd/ircd.c index 9fb9958e8..b55ce238e 100644 --- a/ircd/ircd.c +++ b/ircd/ircd.c @@ -653,7 +653,7 @@ charybdis_main(int argc, char *argv[]) init_hook(); init_channels(); initclass(); - initwhowas(); + whowas_init(); init_reject(); init_cache(); init_monitor(); diff --git a/ircd/s_user.c b/ircd/s_user.c index 55a0bb557..b2e3829c1 100644 --- a/ircd/s_user.c +++ b/ircd/s_user.c @@ -1513,7 +1513,7 @@ change_nick_user_host(struct Client *target_p, const char *nick, const char *use 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); diff --git a/ircd/whowas.c b/ircd/whowas.c index 716bdccd4..0bcd88fc2 100644 --- a/ircd/whowas.c +++ b/ircd/whowas.c @@ -4,7 +4,8 @@ * * 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 * * 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 @@ -18,178 +19,203 @@ * * 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 WHOWAS[NICKNAMEHISTORYLENGTH]; -struct Whowas *WHOWASHASH[WW_MAX]; - -static int whowas_next = 0; - -unsigned int hash_whowas_name(const char *name) +struct whowas_top { - return fnv_hash_upper((const unsigned char *) name, WW_MAX_BITS); + char *name; + rb_dlink_list wwlist; +}; + +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 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); + } } -void add_history(struct Client *client_p, int online) +static struct whowas_top * +whowas_get_top(const char *name) { - struct Whowas *who = &WHOWAS[whowas_next]; + 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; +} + +rb_dlink_list * +whowas_get_list(const char *name) +{ + 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) -{ - struct Whowas *temp, *next; - for (temp = client_p->whowas; temp; temp = next) +void +whowas_off_history(struct Client *client_p) +{ + rb_dlink_node *ptr, *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; + + if(rb_dlink_list_length(&whowas_list) < whowas_list_length) + return; + over = rb_dlink_list_length(&whowas_list) - whowas_list_length; + + /* remove whowas entries over the configured length */ + for(long i = 0; i < over; i++) + { + 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); + } + } } void -initwhowas() +whowas_init(void) { - int i; - - for (i = 0; i < NICKNAMEHISTORYLENGTH; i++) + whowas_tree = irc_radixtree_create("whowas", irc_radixtree_irccasecanon); + if(whowas_list_length == 0) { - memset((void *) &WHOWAS[i], 0, sizeof(struct Whowas)); - WHOWAS[i].hashv = -1; + whowas_list_length = NICKNAMEHISTORYLENGTH; } - for (i = 0; i < WW_MAX; i++) - WHOWASHASH[i] = NULL; + rb_event_add("whowas_trim", whowas_trim, NULL, 10); } - -static void -add_whowas_to_clist(struct Whowas **bucket, struct Whowas *whowas) +void +whowas_set_size(int len) { - whowas->cprev = NULL; - if((whowas->cnext = *bucket) != NULL) - whowas->cnext->cprev = whowas; - *bucket = whowas; + whowas_list_length = len; + whowas_trim(NULL); } -static void -del_whowas_from_clist(struct Whowas **bucket, struct Whowas *whowas) +void +whowas_memory_usage(size_t * count, size_t * memused) { - if(whowas->cprev) - whowas->cprev->cnext = whowas->cnext; - else - *bucket = whowas->cnext; - if(whowas->cnext) - whowas->cnext->cprev = whowas->cprev; -} - -static void -add_whowas_to_list(struct Whowas **bucket, struct Whowas *whowas) -{ - whowas->prev = NULL; - if((whowas->next = *bucket) != NULL) - whowas->next->prev = whowas; - *bucket = whowas; -} - -static void -del_whowas_from_list(struct Whowas **bucket, struct Whowas *whowas) -{ - 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); } diff --git a/modules/core/m_kill.c b/modules/core/m_kill.c index 7c232d5db..c6447445c 100644 --- a/modules/core/m_kill.c +++ b/modules/core/m_kill.c @@ -102,7 +102,7 @@ mo_kill(struct Client *client_p, struct Client *source_p, int parc, const char * ** 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)); @@ -221,7 +221,7 @@ ms_kill(struct Client *client_p, struct Client *source_p, int parc, const char * * 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); diff --git a/modules/core/m_nick.c b/modules/core/m_nick.c index a028a58c9..ab358a151 100644 --- a/modules/core/m_nick.c +++ b/modules/core/m_nick.c @@ -685,7 +685,7 @@ change_local_nick(struct Client *client_p, struct Client *source_p, /* send the nick change to servers.. */ if(source_p->user) { - add_history(source_p, 1); + whowas_add_history(source_p, 1); if (dosend) { @@ -745,7 +745,7 @@ change_remote_nick(struct Client *client_p, struct Client *source_p, 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", diff --git a/modules/m_services.c b/modules/m_services.c index 9d0c78956..ea619b38c 100644 --- a/modules/m_services.c +++ b/modules/m_services.c @@ -244,7 +244,7 @@ doit: 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); diff --git a/modules/m_stats.c b/modules/m_stats.c index b1d559674..21631e15d 100644 --- a/modules/m_stats.c +++ b/modules/m_stats.c @@ -1267,7 +1267,7 @@ stats_memory (struct Client *source_p) size_t total_memory = 0; - count_whowas_memory(&ww, &wwm); + whowas_memory_usage(&ww, &wwm); RB_DLINK_FOREACH(ptr, global_client_list.head) { diff --git a/modules/m_whowas.c b/modules/m_whowas.c index 1e9933c65..15032f6de 100644 --- a/modules/m_whowas.c +++ b/modules/m_whowas.c @@ -58,9 +58,10 @@ DECLARE_MODULE_AV1(whowas, NULL, NULL, whowas_clist, NULL, NULL, "$Revision: 171 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]; @@ -76,8 +77,8 @@ m_whowas(struct Client *client_p, struct Client *source_p, int parc, const char { 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 @@ -101,54 +102,50 @@ m_whowas(struct Client *client_p, struct Client *source_p, int parc, const char 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, - "", 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; }