you are at least a halfoperator, the channel topic will be
changed to the new one you provide.">
- <helpop key="who" value="/WHO <search pattern> [ohurmaiMplf]
-<helpop key="who" value="/WHO <pattern> [afhilMmoprt]
++<helpop key="who" value="/WHO <pattern> [<flags>][%[<fields>[,<querytype>]]] <pattern>
--Looks up the information of users matching the range you provide.
- You may only /WHO nicknames in channels or on servers where you
- share a common channel with them, or ones which are not +i (unless
- you are an IRC operator). The search-pattern may be a special
- sequence of characters determined by the flags given below, or
- it may be one of a nickname, a channel, a hostmask, an ip address
- mask or a server mask.
++Looks up information about users matching the provided pattern. You can specify
++a flag specific pattern, a channel name, user hostname, a user server name, a
++user real name, or a user nickname. Matching users will only be included in the
++WHO response if:
+
-You may only /WHO nicknames in channels or on servers where you
-share a common channel with them, or ones which are not +i (unless
-you are a server operator). The search pattern may be a special
-sequence of characters determined by the flags given below, or
-it may be one of a nickname, a channel, a hostmask, an IP address
-mask or a server mask.
++ 1) The specified pattern is an exact channel name that does not have the
++ private or secret channel modes set and the user does not have the invisible
++ user mode set.
++ 2) The specified pattern is an exact nickname.
++ 3) You share one or more common channels with the user.
++ 4) The user does not have the invisible user mode set.
++ 5) You are a server operator with the users/auspex privilege.
++
++If you specify any fields the response returned will be a WHOX response rather
++than a RFC 1459 WHO response.
Valid WHO Flags
---------------
- The following flags after the mask have the following effects:
-
- o Show online IRC operators matching the mask
-
- a Show all users who have an away message matching the given mask
- i Show all users who have an ident (username) matching the given mask
- p Show all users who are connected on the given port number (IRC
- operators only)
- r Show all users whose real name match the mask. When this
- flag is set it overrides the meaning of the search-pattern,
- which must contain a glob pattern intended to match the
- real name.
- m Search for all users with a given set of user modes. When
- this flag is set it overrides the meaning of the
- search-pattern, which must contain the mode sequence to
- search for, for example to find all users with +i and
- without +s, issue the command WHO +i-s m (IRC operators only)
- t Show users connected within this number of seconds
- M Show all users who have metadata attached to them with
- the given key name (IRC operators only)
-
- f Show only remote (far) users
- l Show only local users
-
- h Show real hostnames rather than masked hostnames (IRC
- operators only)
- u Unlimit the results past the maximum /who results value
- (IRC operators only)
-
- You may combine multiple flags in one WHO command except where stated in the table above.">
+ The following flags use <pattern> to match against the specified user data:
+
- a Show users who have an away message matching <pattern>.
- i Show users who have an ident (username) matching <pattern>.
- M Show users who have metadata attached to them with a key name matching
- <pattern> (server operators only).
++ A Show users who have an away message matching <pattern>.
++ a Show users who have an account name matching <pattern>.
++ h Show users who have a hostname matching <pattern>. If the 'x' modifier
++ is specified then this will match against the real hostname instead of
++ the display hostname.
++ i Show users who have an IP address matching <pattern>.
+ m Show users who have the modes listed in <pattern>. The pattern
+ should be in the same format as a mode change e.g. +ow-i (server
+ operators only).
++ n Show users who have a nickname matching <pattern>.
+ p Show users who are connected to a port in the <pattern> range (server
+ operators only).
+ r Show users who have a real name matching <pattern>.
++ s Show users who are on a server with a name matching <pattern>. If the 'x'
++ modifier is specified then this will match against the real server name
++ instead of the masked server name.
+ t Show users who have connected in the last <pattern> seconds.
++ u Show users who have an ident (username) matching <pattern>.
+
+ The following flags filter users by their status:
+
+ f Only show users on remote (far) servers.
+ l Only show users on the local server.
+ o Only show server operators.
+
+ The following flags modify the command output:
+
- h Show real hostnames rather than display hostnames (server operators
- only).
++ x Show sensitive data like real user hostnames and, when hideserver is
++ enabled, real server hostnames.
+
+ You may combine one flag from the first group and multiple from the others in
-one WHO command.">
++one WHO command.
++
++Valid WHO Fields
++----------------
++
++ a Include the user's account name in the response.
++ c Include the first common channel name in the response.
++ d Include the user's server distance from you in the response.
++ f Include the user's away status, oper status, and highest channel prefix
++ in the response.
++ h Include the user's hostname in the response. If the 'x' flag was
++ specified then this is the real host rather than the display host.
++ i Include the user's IP address in the response.
++ l Include the user's idle time in the response.
++ n Include the user's nickname in the response.
++ o Include the user's channel operator rank level in the response.
++ r Include the user's real name in the response.
++ s Include the user's server name in the response. If the 'x' flag was
++ specified then this is the real server name rather than the masked server
++ name.
++ t Include the query type in the response.
++ u Include the user's ident in the response.
++
++">
<helpop key="motd" value="/MOTD [<server>]
--- /dev/null
- ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "MATCH: %u", match);
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2018 Peter Powell <petpow@saberuk.com>
+ * Copyright (C) 2014 Adam <Adam@anope.org>
+ * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "modules/account.h"
+
+enum
+{
+ // From RFC 1459.
+ RPL_ENDOFWHO = 315,
+ RPL_WHOREPLY = 352,
+
+ // From ircu.
+ RPL_WHOSPCRPL = 354
+};
+
+struct WhoData
+{
+ // The flags for matching users to include.
+ std::bitset<UCHAR_MAX> flags;
+
+ // Whether we are matching using a wildcard or a flag.
+ bool fuzzy_match;
+
+ // The text to match against.
+ std::string matchtext;
+
+ // The WHO/WHOX responses we will send to the source.
+ std::vector<Numeric::Numeric> results;
+
+ // Whether the source requested a WHOX response.
+ bool whox;
+
+ // The fields to include in the WHOX response.
+ std::bitset<UCHAR_MAX> whox_fields;
+
+ // A user specified label for the WHOX response.
+ std::string whox_querytype;
+
+ WhoData(const std::vector<std::string>& parameters)
+ : whox(false)
+ {
+ // Find the matchtext and swap the 0 for a * so we can use InspIRCd::Match on it.
+ matchtext = parameters.size() > 2 ? parameters[2] : parameters[0];
+ if (matchtext == "0")
+ matchtext = "*";
+
+ // Fuzzy matches are when the source has not specified a specific user.
+ fuzzy_match = (parameters.size() > 1) || (matchtext.find_first_of("*?.") != std::string::npos);
+
+ // If flags have been specified by the source.
+ if (parameters.size() > 1)
+ {
+ std::bitset<UCHAR_MAX>* current_bitset = &flags;
+ for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter)
+ {
+ unsigned char chr = static_cast<unsigned char>(*iter);
+
+ // If the source specifies a percentage the rest of the flags are WHOX fields.
+ if (chr == '%')
+ {
+ whox = true;
+ current_bitset = &whox_fields;
+ continue;
+ }
+
+ // If we are in WHOX mode and the source specifies a comma
+ // the rest of the parameter is the query type.
+ if (whox && chr == ',')
+ {
+ whox_querytype.assign(++iter, parameters[1].end());
+ break;
+ }
+
+ // The source specified a matching flag.
+ current_bitset->set(chr);
+ }
+ }
+ }
+};
+
+class CommandWho : public SplitCommand
+{
+ private:
+ ChanModeReference secretmode;
+ ChanModeReference privatemode;
+ UserModeReference hidechansmode;
+ UserModeReference invisiblemode;
+
+ /** Determines whether a user can view the users of a channel. */
+ bool CanView(Channel* chan, User* user)
+ {
+ // If we are in a channel we can view all users in it.
+ if (chan->HasUser(user))
+ return true;
+
+ // Opers with the users/auspex priv can see everything.
+ if (user->HasPrivPermission("users/auspex"))
+ return true;
+
+ // You can see inside a channel from outside unless it is secret or private.
+ return !chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode);
+ }
+
+ /** Gets the first channel which is visible between the source and the target users. */
+ Membership* GetFirstVisibleChannel(LocalUser* source, User* user)
+ {
+ for (User::ChanList::iterator iter = user->chans.begin(); iter != user->chans.end(); ++iter)
+ {
+ Membership* memb = *iter;
+
+ // TODO: move the +I check into m_hidechans.
+ bool has_modes = memb->chan->IsModeSet(secretmode) || memb->chan->IsModeSet(privatemode) || user->IsModeSet(hidechansmode);
+ if (source == user || !has_modes || memb->chan->HasUser(source))
+ return memb;
+ }
+ return NULL;
+ }
+
+ /** Determines whether WHO flags match a specific channel user. */
+ static bool MatchChannel(LocalUser* source, Membership* memb, WhoData& data);
+
+ /** Determines whether WHO flags match a specific user. */
+ static bool MatchUser(LocalUser* source, User* target, WhoData& data);
+
+ /** Performs a WHO request on a channel. */
+ void WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* c, WhoData& data);
+
+ /** Template for getting a user from various types of collection. */
+ template<typename T>
+ static User* GetUser(T& t);
+
+ /** Performs a WHO request on a list of users. */
+ template<typename T>
+ void WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data);
+
+ public:
+ CommandWho(Module* parent)
+ : SplitCommand(parent, "WHO", 1, 3)
+ , secretmode(parent, "secret")
+ , privatemode(parent, "private")
+ , hidechansmode(parent, "hidechans")
+ , invisiblemode(parent, "invisible")
+ {
+ allow_empty_last_param = false;
+ syntax = "<server>|<nickname>|<channel>|<realname>|<host>|0 [[Aafhilmnoprstu] <server>|<nickname>|<channel>|<realname>|<host>|0]";
+ }
+
+ /** Sends a WHO reply to a user. */
+ void SendWhoLine(LocalUser* user, const std::vector<std::string>& parameters, Membership* memb, User* u, WhoData& data);
+
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE;
+};
+
+template<> User* CommandWho::GetUser(UserManager::OperList::const_iterator& t) { return *t; }
+template<> User* CommandWho::GetUser(user_hash::const_iterator& t) { return t->second; }
+
+bool CommandWho::MatchChannel(LocalUser* source, Membership* memb, WhoData& data)
+{
+ bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
+ bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
+
+ // The source only wants remote users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is not local to the current server.
+ LocalUser* lu = IS_LOCAL(memb->user);
+ if (data.flags['f'] && source_can_see_server && lu)
+ return false;
+
+ // The source only wants local users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is local to the current server.
+ if (data.flags['l'] && source_can_see_server && !lu)
+ return false;
+
+ // Only show operators if the oper flag has been specified.
+ if (data.flags['o'] && !memb->user->IsOper())
+ return false;
+
+ // All other flags are ignored for channels.
+ return true;
+}
+
+bool CommandWho::MatchUser(LocalUser* source, User* user, WhoData& data)
+{
+ // Users who are not fully registered can never match.
+ if (user->registered != REG_ALL)
+ return false;
+
+ bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
+ bool source_can_see_target = source == user || source_has_users_auspex;
+ bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
+
+ // The source only wants remote users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is not local to the current server.
+ LocalUser* lu = IS_LOCAL(user);
+ if (data.flags['f'] && source_can_see_server && lu)
+ return false;
+
+ // The source only wants local users. This user is eligible if:
+ // (1) The source can't see server information.
+ // (2) The source is local to the current server.
+ if (data.flags['l'] && source_can_see_server && !lu)
+ return false;
+
+ // The source wants to match against users' away messages.
+ bool match = false;
+ if (data.flags['A'])
+ match = user->IsAway() && InspIRCd::Match(user->awaymsg, data.matchtext, ascii_case_insensitive_map);
+
+ // The source wants to match against users' account names.
+ else if (data.flags['a'])
+ {
+ const AccountExtItem* accountext = GetAccountExtItem();
+ const std::string* account = accountext ? accountext->get(user) : NULL;
+ match = account && InspIRCd::Match(*account, data.matchtext);
+ }
+
+ // The source wants to match against users' hostnames.
+ else if (data.flags['h'])
+ {
+ const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
+ match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
+ }
+
+ // The source wants to match against users' IP addresses.
+ else if (data.flags['i'])
+ match = source_can_see_target && InspIRCd::MatchCIDR(user->GetIPString(), data.matchtext, ascii_case_insensitive_map);
+
+ // The source wants to match against users' modes.
+ else if (data.flags['m'])
+ {
+ if (source_can_see_target)
+ {
+ bool set = true;
+ for (std::string::const_iterator iter = data.matchtext.begin(); iter != data.matchtext.end(); ++iter)
+ {
+ unsigned char chr = static_cast<unsigned char>(*iter);
+ switch (chr)
+ {
+ // The following user modes should be set.
+ case '+':
+ set = true;
+ break;
+
+ // The following user modes should be unset.
+ case '-':
+ set = false;
+ break;
+
+ default:
+ if (user->IsModeSet(chr) != set)
+ return false;
+ break;
+ }
+ }
+
+ // All of the modes matched.
+ return true;
+ }
+ }
+
+ // The source wants to match against users' nicks.
+ else if (data.flags['n'])
+ match = InspIRCd::Match(user->nick, data.matchtext);
+
+ // The source wants to match against users' connection ports.
+ else if (data.flags['p'])
+ {
+ if (source_can_see_target && lu)
+ {
+ irc::portparser portrange(data.matchtext, false);
+ long port;
+ while ((port = portrange.GetToken()))
+ {
+ if (port == lu->GetServerPort())
+ {
+ match = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // The source wants to match against users' real names.
+ else if (data.flags['r'])
+ match = InspIRCd::Match(user->fullname, data.matchtext, ascii_case_insensitive_map);
+
+ else if (data.flags['s'])
+ {
+ bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
+ const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
+ match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
+ }
+
+ // The source wants to match against users' connection times.
+ else if (data.flags['t'])
+ {
+ time_t seconds = ServerInstance->Time() - InspIRCd::Duration(data.matchtext);
+ if (user->signon >= seconds)
+ match = true;
+ }
+
+ // The source wants to match against users' idents.
+ else if (data.flags['u'])
+ match = InspIRCd::Match(user->ident, data.matchtext, ascii_case_insensitive_map);
+
+ // The <name> passed to WHO is matched against users' host, server,
+ // real name and nickname if the channel <name> cannot be found.
+ else
+ {
+ const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
+ match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
+
+ if (!match)
+ {
+ bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
+ const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
+ match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
+ }
+
+ if (!match)
+ match = InspIRCd::Match(user->fullname, data.matchtext, ascii_case_insensitive_map);
+
+ if (!match)
+ match = InspIRCd::Match(user->nick, data.matchtext);
+ }
+
+ return match;
+}
+
+void CommandWho::WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* chan, WhoData& data)
+{
+ if (!CanView(chan, source))
+ return;
+
+ bool inside = chan->HasUser(source);
+ const Channel::MemberMap& users = chan->GetUsers();
+ for (Channel::MemberMap::const_iterator iter = users.begin(); iter != users.end(); ++iter)
+ {
+ User* user = iter->first;
+ Membership* memb = iter->second;
+
+ // Only show invisible users if the source is in the channel or has the users/auspex priv.
+ if (!inside && user->IsModeSet(invisiblemode) && !source->HasPrivPermission("users/auspex"))
+ continue;
+
+ // Skip the user if it doesn't match the query.
+ if (!MatchChannel(source, memb, data))
+ continue;
+
+ SendWhoLine(source, parameters, memb, user, data);
+ }
+}
+
+template<typename T>
+void CommandWho::WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data)
+{
+ for (typename T::const_iterator iter = users.begin(); iter != users.end(); ++iter)
+ {
+ User* user = GetUser(iter);
+
+ // Only show users in response to a fuzzy WHO if we can see them normally.
+ bool can_see_normally = user == source || source->SharesChannelWith(user) || !user->IsModeSet(invisiblemode);
+ if (data.fuzzy_match && !can_see_normally && !source->HasPrivPermission("users/auspex"))
+ continue;
+
+ // Skip the user if it doesn't match the query.
+ if (!MatchUser(source, user, data))
+ continue;
+
+ SendWhoLine(source, parameters, NULL, user, data);
+ }
+}
+
+void CommandWho::SendWhoLine(LocalUser* source, const std::vector<std::string>& parameters, Membership* memb, User* user, WhoData& data)
+{
+ if (!memb)
+ memb = GetFirstVisibleChannel(source, user);
+
+ bool source_can_see_target = source == user || source->HasPrivPermission("users/auspex");
+ Numeric::Numeric wholine(data.whox ? RPL_WHOSPCRPL : RPL_WHOREPLY);
+ if (data.whox)
+ {
+ // The source used WHOX so we send a fancy customised response.
+
+ // Include the query type in the reply.
+ if (data.whox_fields['t'])
+ wholine.push(data.whox_querytype.empty() || data.whox_querytype.length() > 3 ? "0" : data.whox_querytype);
+
+ // Include the first channel name.
+ if (data.whox_fields['c'])
+ wholine.push(memb ? memb->chan->name : "*");
+
+ // Include the user's ident.
+ if (data.whox_fields['u'])
+ wholine.push(user->ident);
+
+ // Include the user's IP address.
+ if (data.whox_fields['i'])
+ wholine.push(source_can_see_target ? user->GetIPString() : "255.255.255.255");
+
+ // Include the user's hostname.
+ if (data.whox_fields['h'])
+ wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
+
+ // Include the server name.
+ if (data.whox_fields['s'])
+ {
+ if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
+ wholine.push(user->server->GetName());
+ else
+ wholine.push(ServerInstance->Config->HideServer);
+ }
+
+ // Include the user's nickname.
+ if (data.whox_fields['n'])
+ wholine.push(user->nick);
+
+ // Include the user's flags.
+ if (data.whox_fields['f'])
+ {
+ // Away state.
+ std::string flags(user->IsAway() ? "G" : "H");
+
+ // Operator status.
+ if (user->IsOper())
+ flags.push_back('*');
+
+ // Membership prefix.
+ if (memb)
+ {
+ char prefix = memb->GetPrefixChar();
+ if (prefix)
+ flags.push_back(prefix);
+ }
+
+ wholine.push(flags);
+ }
+
+ // Include the number of hops between the users.
+ if (data.whox_fields['d'])
+ wholine.push("0");
+
+ // Include the user's idle time.
+ if (data.whox_fields['l'])
+ {
+ LocalUser* lu = IS_LOCAL(user);
+ unsigned long idle = lu ? ServerInstance->Time() - lu->idle_lastmsg : 0;
+ wholine.push(ConvToStr(idle));
+ }
+
+ // Include the user's account name.
+ if (data.whox_fields['a'])
+ {
+ const AccountExtItem* accountext = GetAccountExtItem();
+ const std::string* account = accountext ? accountext->get(user) : NULL;
+ wholine.push(account ? *account : "0");
+ }
+
+ // Include the user's operator rank level.
+ if (data.whox_fields['o'])
+ wholine.push(memb ? ConvToStr(memb->getRank()) : "0");
+
+ // Include the user's real name.
+ if (data.whox_fields['r'])
+ wholine.push(user->fullname);
+ }
+ else
+ {
+ // We are not using WHOX so we just send a plain RFC response.
+
+ // Include the channel name.
+ wholine.push(memb ? memb->chan->name : "*");
+
+ // Include the user's ident.
+ wholine.push(user->ident);
+
+ // Include the user's hostname.
+ wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
+
+ // Include the server name.
+ if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
+ wholine.push(user->server->GetName());
+ else
+ wholine.push(ServerInstance->Config->HideServer);
+
+ // Include the user's nick.
+ wholine.push(user->nick);
+
+ // Include the user's flags.
+ {
+ // Away state.
+ std::string flags(user->IsAway() ? "G" : "H");
+
+ // Operator status.
+ if (user->IsOper())
+ flags.push_back('*');
+
+ // Membership prefix.
+ if (memb)
+ {
+ char prefix = memb->GetPrefixChar();
+ if (prefix)
+ flags.push_back(prefix);
+ }
+
+ wholine.push(flags);
+ }
+
+ // Include the number of hops between the users and the user's real name.
+ wholine.push("0 ");
+ wholine.GetParams().back().append(user->fullname);
+ }
+
+ ModResult res;
+ FIRST_MOD_RESULT(OnSendWhoLine, res, (source, parameters, user, memb, wholine));
+ if (res != MOD_RES_DENY)
+ data.results.push_back(wholine);
+}
+
+CmdResult CommandWho::HandleLocal(const std::vector<std::string>& parameters, LocalUser* user)
+{
+ WhoData data(parameters);
+
+ // Is the source running a WHO on a channel?
+ Channel* chan = ServerInstance->FindChan(data.matchtext);
+ if (chan)
+ WhoChannel(user, parameters, chan, data);
+
+ // If we only want to match against opers we only have to iterate the oper list.
+ else if (data.flags['o'])
+ WhoUsers(user, parameters, ServerInstance->Users->all_opers, data);
+
+ // Otherwise we have to use the global user list.
+ else
+ WhoUsers(user, parameters, ServerInstance->Users->GetUsers(), data);
+
+ // Send the results to the source.
+ for (std::vector<Numeric::Numeric>::const_iterator n = data.results.begin(); n != data.results.end(); ++n)
+ user->WriteNumeric(*n);
+ user->WriteNumeric(RPL_ENDOFWHO, (data.matchtext.empty() ? "*" : data.matchtext.c_str()), "End of /WHO list.");
+
+ // Penalize the source a bit for large queries with one unit of penalty per 200 results.
+ user->CommandFloodPenalty += data.results.size() * 5;
+ return CMD_SUCCESS;
+}
+
+class CoreModWho : public Module
+{
+ private:
+ CommandWho cmd;
+
+ public:
+ CoreModWho()
+ : cmd(this)
+ {
+ }
+
+ void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
+ {
+ tokens["WHOX"];
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the WHO command", VF_VENDOR|VF_CORE);
+ }
+};
+
+MODULE_INIT(CoreModWho)