diff --git a/include/ircd/client.h b/include/ircd/client.h index 769e86940..cf424f6d9 100644 --- a/include/ircd/client.h +++ b/include/ircd/client.h @@ -94,8 +94,8 @@ struct client struct client *servptr; /* Points to server this Client is on */ struct client *from; /* == self, if Local Client, *NEVER* NULL! */ - rb_dlink_list whowas_clist; - + // unique whowas id associating any history to *this (TODO: replace with connid) + whowas::id_t wwid; time_t tsinfo; /* TS on the nick, SVINFO on server */ mode::mode mode; uint64_t flags; /* client flags */ @@ -927,7 +927,7 @@ extern void del_all_accepts(client *client_p); extern void dead_link(client *client_p, int sendqex); extern int show_ip(client *source_p, client *target_p); extern int show_ip_conf(struct ConfItem *aconf, client *source_p); -extern int show_ip_whowas(struct Whowas *whowas, client *source_p); +extern int show_ip_whowas(const whowas::whowas &, client &source); extern void close_connection(client *); extern void init_uid(void); diff --git a/include/ircd/stdinc.h b/include/ircd/stdinc.h index 7c20d8896..2be1d9baf 100644 --- a/include/ircd/stdinc.h +++ b/include/ircd/stdinc.h @@ -52,7 +52,6 @@ namespace ircd } struct ConfItem; - struct Whowas; struct DNSReply; struct Listener; struct Blacklist; @@ -75,6 +74,7 @@ namespace ircd #include "match.h" #include "cache.h" +#include "whowas.h" #include "tgchange.h" #include "msgbuf.h" #include "msg.h" @@ -119,6 +119,5 @@ namespace ircd #include "substitution.h" #include "supported.h" #include "s_user.h" -#include "whowas.h" #include "wsproc.h" #include "stringops.h" diff --git a/include/ircd/whowas.h b/include/ircd/whowas.h index 739951c40..c05cdc4ad 100644 --- a/include/ircd/whowas.h +++ b/include/ircd/whowas.h @@ -5,6 +5,7 @@ * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center * Copyright (C) 1996-2002 Hybrid Development Team * Copyright (C) 2002-2004 ircd-ratbox development team + * Copyright (C) 2016 Charybdis Development Team * * 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 @@ -26,73 +27,60 @@ #define HAVE_IRCD_WHOWAS_H #ifdef __cplusplus -namespace ircd { +namespace ircd { +namespace whowas { -struct User; +using client::client; +using id_t = uint64_t; -/* - lets speed this up... - also removed away information. *tough* - - Dianora +/* lets speed this up... also removed away information. *tough* + * - Dianora */ -struct Whowas +struct whowas { - 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 */ + enum flag + { + IP_SPOOFING = 0x01, + DYNSPOOF = 0x02, + }; + + id_t wwid; // Unique whowas index ID + client *online; // Pointer to online client or nullptr + time_t logoff; + std::shared_ptr scache; char name[NICKLEN + 1]; char username[USERLEN + 1]; char hostname[HOSTLEN + 1]; char sockhost[HOSTIPLEN + 1]; char realname[REALLEN + 1]; char suser[NICKLEN + 1]; - unsigned char flags; - std::shared_ptr scache; - time_t logoff; - client::client *online; /* Pointer to new nickname for chasing or NULL */ + enum flag flag; + + whowas(client &client); }; -/* Flags */ -#define WHOWAS_IP_SPOOFING 0x1 -#define WHOWAS_DYNSPOOF 0x2 +// Get a full history for nickname (may contain different users!) +std::vector> history(const std::string &name, const time_t &limit = 0, const bool &online = false); -/* -** initwhowas -*/ -extern void whowas_init(void); +// Get a full history for this unique whowas ID. This is effectively similar to +// a lookup by a client pointer address; allocators may reuse the same address, +// so this ID is used as the index instead. +std::vector> history(const id_t &wwid); -/* -** add_history -** Add the currently defined name of the client to history. -** usually called before changing to a new name (nick). -** Client must be a fully registered user (specifically, -** the user structure must have been allocated). -*/ -void whowas_add_history(client::client *, int); +// Get a full history for this client (must be online); +std::vector> history(const client &); -/* -** off_history -** This must be called when the client structure is about to -** be released. History mechanism keeps pointers to client -** structures and it must know when they cease to exist. This -** also implicitly calls AddHistory. -*/ -void whowas_off_history(client::client *); +// Add whowas information about client *before* a nick change or logoff. +void add(client &); -/* -** get_history -** Return the current client that was using the given -** nickname within the timelimit. Returns NULL, if no -** one found... -*/ -client::client *whowas_get_history(const char *, time_t); - /* Nick name */ - /* Time limit in seconds */ +// Notify this subsystem the client pointer is about to be invalidated (does not call add()) +void off(client &); -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); +// Util +void memory_usage(size_t *const &count, size_t *const &memused); +void set_size(const size_t &max); +void init(); +} // namespace whowas } // namespace ircd #endif // __cplusplus diff --git a/ircd/client.cc b/ircd/client.cc index 8dc7b27ba..87b79f42d 100644 --- a/ircd/client.cc +++ b/ircd/client.cc @@ -90,6 +90,24 @@ using namespace ircd; client::client::client() +:servptr{0} +,from{nullptr} +,wwid{0} +,tsinfo{0} +,mode{(umode)0} +,flags{(enum flags)0} +,snomask{0} +,hopcount{0} +,status{(enum status)0} +,handler{0} +,serial{0} +,first_received_message_time{0} +,received_number_of_privmsgs{0} +,flood_noticed{0} +,localClient{nullptr} +,preClient{nullptr} +,large_ctcp_sent{0} +,certfp{nullptr} { } @@ -773,7 +791,7 @@ client::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(); - whowas_add_history(client_p, 1); + whowas::add(*client_p); monitor_signoff(client_p); @@ -954,15 +972,17 @@ client::find_chasing(client *source_p, const char *user, int *chasing) if(who || rfc1459::is_digit(*user)) return who; - if(!(who = whowas_get_history(user, (long) KILLCHASETIMELIMIT))) + const auto history(whowas::history(user, KILLCHASETIMELIMIT)); + if(history.empty()) { - sendto_one_numeric(source_p, ERR_NOSUCHNICK, - form_str(ERR_NOSUCHNICK), user); - return (NULL); + sendto_one_numeric(source_p, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), user); + return nullptr; } + if(chasing) *chasing = 1; - return who; + + return history.back()->online; } /* @@ -1323,8 +1343,8 @@ client::exit_generic_client(client *client_p, client *source_p, client *from, /* Clean up allow lists */ del_all_accepts(source_p); - whowas_add_history(source_p, 0); - whowas_off_history(source_p); + whowas::add(*source_p); + whowas::off(*source_p); monitor_signoff(source_p); @@ -1806,14 +1826,16 @@ client::show_ip_conf(struct ConfItem *aconf, client *source_p) } int -client::show_ip_whowas(struct Whowas *whowas, client *source_p) +client::show_ip_whowas(const whowas::whowas &whowas, client &source) { - if(whowas->flags & WHOWAS_IP_SPOOFING) - if(ConfigFileEntry.hide_spoof_ips || !my_oper(*source_p)) + if(whowas.flag & whowas.IP_SPOOFING) + if(ConfigFileEntry.hide_spoof_ips || !my_oper(source)) return 0; - if(whowas->flags & WHOWAS_DYNSPOOF) - if(!is(*source_p, umode::OPER)) + + if(whowas.flag & whowas.DYNSPOOF) + if(!is(source, umode::OPER)) return 0; + return 1; } diff --git a/ircd/ircd.cc b/ircd/ircd.cc index c2e6ef838..bbfef99ab 100644 --- a/ircd/ircd.cc +++ b/ircd/ircd.cc @@ -630,7 +630,7 @@ charybdis_main(int argc, char * const argv[]) init_hook(); chan::init(); initclass(); - whowas_init(); + whowas::init(); init_reject(); cache::init(); init_monitor(); diff --git a/ircd/s_user.cc b/ircd/s_user.cc index c8499d140..b66a32ff0 100644 --- a/ircd/s_user.cc +++ b/ircd/s_user.cc @@ -1569,7 +1569,7 @@ change_nick_user_host(client::client *target_p, const char *nick, const char *us rb_strlcpy(target_p->host, host, sizeof target_p->host); if (changed) - whowas_add_history(target_p, 1); + whowas::add(*target_p); del_from_client_hash(target_p->name, target_p); rb_strlcpy(target_p->name, nick, NICKLEN); diff --git a/ircd/whowas.cc b/ircd/whowas.cc index 006d68cde..f96e3f6ef 100644 --- a/ircd/whowas.cc +++ b/ircd/whowas.cc @@ -5,7 +5,9 @@ * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center * Copyright (C) 1996-2002 Hybrid Development Team * Copyright (C) 2002-2012 ircd-ratbox development team + * Copyright (C) 2016 Charybdis Development Team * Copyright (C) 2016 William Pitcock + * Copyright (C) 2016 Jason Volk * * 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 @@ -23,185 +25,175 @@ * USA */ -namespace ircd { +namespace ircd { +namespace whowas { -struct whowas_top +id_t id_ctr = 1; +size_t list_max; +std::multimap> ids; +std::multimap, rfc1459::less> nicks; + +void trim(); + +} // namespace whowas +} // namespace ircd + +using namespace ircd; + +void +whowas::init() { - char *name; - rb_dlink_list wwlist; -}; - -static rb_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) - { - rb_radixtree_delete(whowas_tree, wtop->name); - rb_free(wtop->name); - rb_free(wtop); - } -} - -static struct whowas_top * -whowas_get_top(const char *name) -{ - struct whowas_top *wtop; - - wtop = (whowas_top *)rb_radixtree_retrieve(whowas_tree, name); - if (wtop != NULL) - return wtop; - - wtop = (whowas_top *)rb_malloc(sizeof(struct whowas_top)); - wtop->name = rb_strdup(name); - rb_radixtree_add(whowas_tree, wtop->name, wtop); - - return wtop; -} - -rb_dlink_list * -whowas_get_list(const char *name) -{ - struct whowas_top *wtop; - wtop = (whowas_top *)rb_radixtree_retrieve(whowas_tree, name); - if(wtop == NULL) - return NULL; - return &wtop->wwlist; + if(!list_max) + list_max = NICKNAMEHISTORYLENGTH; } void -whowas_add_history(client::client *client_p, int online) +whowas::set_size(const size_t &max) { - struct whowas_top *wtop; - struct Whowas *who; - s_assert(NULL != client_p); - - if(client_p == NULL) - return; - - /* 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 = (Whowas *)rb_malloc(sizeof(struct Whowas)); - who->wtop = wtop; - who->logoff = rb_current_time(); - - rb_strlcpy(who->name, client_p->name, sizeof(who->name)); - 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 = (is_ip_spoof(*client_p) ? WHOWAS_IP_SPOOFING : 0) | - (is_dyn_spoof(*client_p) ? WHOWAS_DYNSPOOF : 0); - - who->scache = nameinfo(serv(*client_p)); - - if(online) - { - who->online = client_p; - rb_dlinkAdd(who, &who->cnode, &client_p->whowas_clist); - } - else - who->online = NULL; - - rb_dlinkAdd(who, &who->wnode, &wtop->wwlist); - rb_dlinkAdd(who, &who->whowas_node, &whowas_list); + list_max = max; + trim(); } - void -whowas_off_history(client::client *client_p) +whowas::memory_usage(size_t *const &count, + size_t *const &memused) { - rb_dlink_node *ptr, *next; - - RB_DLINK_FOREACH_SAFE(ptr, next, client_p->whowas_clist.head) - { - struct Whowas *who = (Whowas *)ptr->data; - who->online = NULL; - rb_dlinkDelete(&who->cnode, &client_p->whowas_clist); - } + *count = ids.size(); + *memused = ids.size() * sizeof(struct whowas); } -client::client * -whowas_get_history(const char *nick, time_t timelimit) +void +whowas::off(client &client) { - struct whowas_top *wtop; - rb_dlink_node *ptr; - - wtop = (whowas_top *)rb_radixtree_retrieve(whowas_tree, nick); - if(wtop == NULL) - return NULL; - - timelimit = rb_current_time() - timelimit; - - RB_DLINK_FOREACH_PREV(ptr, wtop->wwlist.tail) + const auto &id(client.wwid); + const auto ppit(ids.equal_range(id)); + std::for_each(ppit.first, ppit.second, [] + (const auto &pit) { - struct Whowas *who = (Whowas *)ptr->data; - if(who->logoff >= timelimit) + auto &whowas(pit.second); + whowas->online = nullptr; + }); +} + +void +whowas::add(client &client) +{ + // trim some of the entries if we're getting well over our history length + trim(); + + // Client's wwid will be 0 if never seen before + auto &id(client.wwid); + if(!id) + id = id_ctr++; + + // This is an unconditional add to both maps. + auto it(ids.lower_bound(id)); + it = ids.emplace_hint(it, id, std::make_shared(client)); + nicks.emplace(client.name, it->second); +} + +std::vector> +whowas::history(const std::string &nick, + const time_t &limit, + const bool &online) +{ + const auto ppit(nicks.equal_range(nick)); + const auto num(std::distance(ppit.first, ppit.second)); + std::vector> ret(num); + std::transform(ppit.first, ppit.second, begin(ret), [] + (const auto &pit) + { + return pit.second; + }); + + // C++11 says the multimap has stronger ordering and preserves + // the insert order, which should already be the logoff time, so stuff below + // this comment can be optimized at a later pass. + std::sort(begin(ret), end(ret), [] + (const auto &a, const auto &b) + { + return a->logoff < b->logoff; + }); + + const auto e(std::remove_if(begin(ret), end(ret), [&limit, &online] + (const auto &whowas) + { + if(online && !whowas->online) + return true; + + if(limit && whowas->logoff + limit < rb_current_time()) + return true; + + return false; + })); + + ret.erase(e, end(ret)); + return ret; +} + +std::vector> +whowas::history(const client &client) +{ + return history(client.wwid); +} + +std::vector> +whowas::history(const id_t &wwid) +{ + const auto ppit(ids.equal_range(wwid)); + const auto num(std::distance(ppit.first, ppit.second)); + std::vector> ret(num); + std::transform(ppit.first, ppit.second, begin(ret), [] + (const auto &pit) + { + return pit.second; + }); + + return ret; +} + +void +whowas::trim() +{ + // Trims by oldest ID until satisfied. + + auto it(begin(ids)); + while(it != end(ids) && nicks.size() > list_max) + { + const auto &id(it->first); + const auto &whowas(it->second); + const auto nick_ppit(nicks.equal_range(whowas->name)); + for(auto pit(nick_ppit.first); pit != nick_ppit.second; ) { - return who->online; + const auto &nick_whowas(pit->second); + if(nick_whowas->wwid == whowas->wwid) + nicks.erase(pit++); + else + ++pit; } - } - return NULL; -} - -static void -whowas_trim(void *unused) -{ - 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 *)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); - } + ids.erase(it++); } } -void -whowas_init(void) +whowas::whowas::whowas(client &client) +:wwid{client.wwid} +,online{&client} +,logoff{rb_current_time()} +,scache { - whowas_tree = rb_radixtree_create("whowas", irccasecanon); - if(whowas_list_length == 0) - { - whowas_list_length = NICKNAMEHISTORYLENGTH; - } - rb_event_add("whowas_trim", whowas_trim, NULL, 10); + client.serv? nameinfo(serv(client)) : nullptr } - -void -whowas_set_size(int len) +,flag { - whowas_list_length = len; - whowas_trim(NULL); + is_ip_spoof(client)? IP_SPOOFING : (enum flag)0 | + is_dyn_spoof(client)? DYNSPOOF : (enum flag)0 } - -void -whowas_memory_usage(size_t * count, size_t * memused) { - *count = rb_dlink_list_length(&whowas_list); - *memused += *count * sizeof(struct Whowas); - *memused += sizeof(struct whowas_top) * rb_radixtree_size(whowas_tree); -} - + rb_strlcpy(name, client.name, sizeof(name)); + rb_strlcpy(username, client.username, sizeof(username)); + rb_strlcpy(hostname, client.host, sizeof(hostname)); + rb_strlcpy(realname, client.info, sizeof(realname)); + rb_strlcpy(sockhost, client.sockhost, sizeof(sockhost)); + assert(wwid); } diff --git a/modules/core/m_kill.cc b/modules/core/m_kill.cc index 0e7121875..0a9dfde7b 100644 --- a/modules/core/m_kill.cc +++ b/modules/core/m_kill.cc @@ -88,16 +88,21 @@ mo_kill(struct MsgBuf *msgbuf_p, client::client &client, client::client &source, ** rewrite the KILL for this new nickname--this keeps ** servers in synch when nick change and kill collide */ - if((target_p = whowas_get_history(user, (long) KILLCHASETIMELIMIT)) == NULL) + const auto history(whowas::history(user, KILLCHASETIMELIMIT, true)); + if(history.empty()) { if (strchr(user, '.')) sendto_one_numeric(&source, ERR_CANTKILLSERVER, form_str(ERR_CANTKILLSERVER)); else - sendto_one_numeric(&source, ERR_NOSUCHNICK, - form_str(ERR_NOSUCHNICK), user); + sendto_one_numeric(&source, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), + user); return; } - sendto_one_notice(&source, ":KILL changed from %s to %s", user, target_p->name); + + target_p = history.back()->online; + sendto_one_notice(&source, ":KILL changed from %s to %s", + user, + target_p->name); } if(!my_connect(*target_p) && (!IsOperGlobalKill(&source))) @@ -205,12 +210,20 @@ ms_kill(struct MsgBuf *msgbuf_p, client::client &client, client::client &source, * not an uid, automatically rewrite the KILL for this new nickname. * --this keeps servers in synch when nick change and kill collide */ - if(rfc1459::is_digit(*user) || (!(target_p = whowas_get_history(user, (long) KILLCHASETIMELIMIT)))) + if(rfc1459::is_digit(*user)) { - sendto_one_numeric(&source, ERR_NOSUCHNICK, - form_str(ERR_NOSUCHNICK), rfc1459::is_digit(*user) ? "*" : user); + sendto_one_numeric(&source, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), "*"); return; } + + const auto history(whowas::history(user, KILLCHASETIMELIMIT, true)); + if(history.empty()) + { + sendto_one_numeric(&source, ERR_NOSUCHNICK, form_str(ERR_NOSUCHNICK), user); + return; + } + + target_p = history.back()->online; sendto_one_notice(&source, ":KILL changed from %s to %s", user, target_p->name); } diff --git a/modules/core/m_nick.cc b/modules/core/m_nick.cc index 897cbc9d7..946c6f8c6 100644 --- a/modules/core/m_nick.cc +++ b/modules/core/m_nick.cc @@ -648,7 +648,7 @@ change_local_nick(client::client &client, client::client &source, /* send the nick change to servers.. */ if(source.user) { - whowas_add_history(&source, 1); + whowas::add(source); if (dosend) { @@ -708,7 +708,7 @@ change_remote_nick(client::client &client, client::client &source, if(source.user) { - whowas_add_history(&source, 1); + whowas::add(source); if (dosend) { sendto_server(&client, NULL, CAP_TS6, NOCAPS, ":%s NICK %s :%ld", diff --git a/modules/m_services.cc b/modules/m_services.cc index e30e53526..7a980cec1 100644 --- a/modules/m_services.cc +++ b/modules/m_services.cc @@ -223,7 +223,7 @@ doit: target_p->name, target_p->username, target_p->host, parv[2]); - whowas_add_history(target_p, 1); + whowas::add(*target_p); 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.cc b/modules/m_stats.cc index fa360ff38..808f29177 100644 --- a/modules/m_stats.cc +++ b/modules/m_stats.cc @@ -1322,7 +1322,7 @@ stats_memory (client::client &source) size_t total_memory = 0; - whowas_memory_usage(&ww, &wwm); + whowas::memory_usage(&ww, &wwm); RB_DLINK_FOREACH(ptr, global_client_list.head) { diff --git a/modules/m_whowas.cc b/modules/m_whowas.cc index 414553ae3..bc487834a 100644 --- a/modules/m_whowas.cc +++ b/modules/m_whowas.cc @@ -89,44 +89,47 @@ m_whowas(struct MsgBuf *msgbuf_p, client::client &client, client::client &source nick = parv[1]; sendq_limit = get_sendq(&client) * 9 / 10; - whowas_list = whowas_get_list(nick); - - if(whowas_list == NULL) + const auto history(whowas::history(nick)); + if(history.empty()) { sendto_one_numeric(&source, ERR_WASNOSUCHNICK, form_str(ERR_WASNOSUCHNICK), nick); sendto_one_numeric(&source, RPL_ENDOFWHOWAS, form_str(RPL_ENDOFWHOWAS), parv[1]); return; } - RB_DLINK_FOREACH(ptr, whowas_list->head) + for(const auto &ww : history) { - struct Whowas *temp = (Whowas *)ptr->data; if(cur > 0 && rb_linebuf_len(&client.localClient->buf_sendq) > sendq_limit) { sendto_one(&source, form_str(ERR_TOOMANYMATCHES), - me.name, source.name, "WHOWAS"); + me.name, + source.name, + "WHOWAS"); break; } sendto_one(&source, form_str(RPL_WHOWASUSER), - me.name, source.name, temp->name, - temp->username, temp->hostname, temp->realname); - if (!EmptyString(temp->sockhost) && - strcmp(temp->sockhost, "0") && - show_ip_whowas(temp, &source)) - sendto_one_numeric(&source, RPL_WHOISACTUALLY, - form_str(RPL_WHOISACTUALLY), - temp->name, temp->sockhost); + me.name, + source.name, + ww->name, + ww->username, + ww->hostname, + ww->realname); - if (!EmptyString(temp->suser)) - sendto_one_numeric(&source, RPL_WHOISLOGGEDIN, - "%s %s :was logged in as", - temp->name, temp->suser); + if(!EmptyString(ww->sockhost) && strcmp(ww->sockhost, "0") && show_ip_whowas(*ww, source)) + sendto_one_numeric(&source, RPL_WHOISACTUALLY, form_str(RPL_WHOISACTUALLY), + ww->name, + ww->sockhost); + + if(!EmptyString(ww->suser)) + sendto_one_numeric(&source, RPL_WHOISLOGGEDIN, "%s %s :was logged in as", + ww->name, + ww->suser); sendto_one_numeric(&source, RPL_WHOISSERVER, form_str(RPL_WHOISSERVER), - temp->name, - temp->scache? name(*temp->scache).c_str() : "*", - rb_ctime(temp->logoff, tbuf, sizeof(tbuf))); + ww->name, + ww->scache? name(*ww->scache).c_str() : "*", + rb_ctime(ww->logoff, tbuf, sizeof(tbuf))); cur++; if(max > 0 && cur >= max)