2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com>
5 * Copyright (C) 2017-2019 Sadie Powell <sadie@witchery.services>
6 * Copyright (C) 2017-2018 Adam <Adam@anope.org>
7 * Copyright (C) 2013-2014, 2016 Attila Molnar <attilamolnar@hush.com>
8 * Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
9 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
10 * Copyright (C) 2009 John Brooks <special@inspircd.org>
11 * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
12 * Copyright (C) 2007-2008, 2010 Craig Edwards <brain@inspircd.org>
13 * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
15 * This file is part of InspIRCd. InspIRCd is free software: you can
16 * redistribute it and/or modify it under the terms of the GNU General Public
17 * License as published by the Free Software Foundation, version 2.
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
24 * You should have received a copy of the GNU General Public License
25 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 #include "modules/account.h"
31 #include "modules/who.h"
43 static const char whox_field_order[] = "tcuihsnfdlaor";
44 static const char who_field_order[] = "cuhsnf";
46 struct WhoData : public Who::Request
48 bool GetFieldIndex(char flag, size_t& out) const CXX11_OVERRIDE
52 const char* pos = strchr(who_field_order, flag);
56 out = pos - who_field_order;
60 if (!whox_fields[flag])
64 for (const char* c = whox_field_order; *c && *c != flag; ++c)
70 return whox_field_order[out];
73 WhoData(const CommandBase::Params& parameters)
75 // Find the matchtext and swap the 0 for a * so we can use InspIRCd::Match on it.
76 matchtext = parameters.size() > 2 ? parameters[2] : parameters[0];
80 // If flags have been specified by the source.
81 if (parameters.size() > 1)
83 std::bitset<UCHAR_MAX>* current_bitset = &flags;
84 for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter)
86 unsigned char chr = static_cast<unsigned char>(*iter);
88 // If the source specifies a percentage the rest of the flags are WHOX fields.
92 current_bitset = &whox_fields;
96 // If we are in WHOX mode and the source specifies a comma
97 // the rest of the parameter is the query type.
98 if (whox && chr == ',')
100 whox_querytype.assign(++iter, parameters[1].end());
104 // The source specified a matching flag.
105 current_bitset->set(chr);
109 // Fuzzy matches are when the source has not specified a specific user.
110 fuzzy_match = flags.any() || (matchtext.find_first_of("*?.") != std::string::npos);
114 class CommandWho : public SplitCommand
117 ChanModeReference secretmode;
118 ChanModeReference privatemode;
119 UserModeReference hidechansmode;
120 UserModeReference invisiblemode;
121 Events::ModuleEventProvider whoevprov;
123 /** Determines whether a user can view the users of a channel. */
124 bool CanView(Channel* chan, User* user)
126 // If we are in a channel we can view all users in it.
127 if (chan->HasUser(user))
130 // Opers with the users/auspex priv can see everything.
131 if (user->HasPrivPermission("users/auspex"))
134 // You can see inside a channel from outside unless it is secret or private.
135 return !chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode);
138 /** Gets the first channel which is visible between the source and the target users. */
139 Membership* GetFirstVisibleChannel(LocalUser* source, User* user)
141 for (User::ChanList::iterator iter = user->chans.begin(); iter != user->chans.end(); ++iter)
143 Membership* memb = *iter;
145 // TODO: move the +I check into m_hidechans.
146 bool has_modes = memb->chan->IsModeSet(secretmode) || memb->chan->IsModeSet(privatemode) || user->IsModeSet(hidechansmode);
147 if (source == user || !has_modes || memb->chan->HasUser(source))
153 /** Determines whether WHO flags match a specific channel user. */
154 bool MatchChannel(LocalUser* source, Membership* memb, WhoData& data);
156 /** Determines whether WHO flags match a specific user. */
157 static bool MatchUser(LocalUser* source, User* target, WhoData& data);
159 /** Performs a WHO request on a channel. */
160 void WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* c, WhoData& data);
162 /** Template for getting a user from various types of collection. */
164 static User* GetUser(T& t);
166 /** Performs a WHO request on a list of users. */
168 void WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data);
171 CommandWho(Module* parent)
172 : SplitCommand(parent, "WHO", 1, 3)
173 , secretmode(parent, "secret")
174 , privatemode(parent, "private")
175 , hidechansmode(parent, "hidechans")
176 , invisiblemode(parent, "invisible")
177 , whoevprov(parent, "event/who")
179 allow_empty_last_param = false;
180 syntax = "<server>|<nick>|<channel>|<realname>|<host>|0 [[Aafhilmnoprstux][%acdfhilnorstu] <server>|<nick>|<channel>|<realname>|<host>|0]";
183 /** Sends a WHO reply to a user. */
184 void SendWhoLine(LocalUser* user, const std::vector<std::string>& parameters, Membership* memb, User* u, WhoData& data);
186 CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE;
189 template<> User* CommandWho::GetUser(UserManager::OperList::const_iterator& t) { return *t; }
190 template<> User* CommandWho::GetUser(user_hash::const_iterator& t) { return t->second; }
192 bool CommandWho::MatchChannel(LocalUser* source, Membership* memb, WhoData& data)
194 bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
195 bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
197 // The source only wants remote users. This user is eligible if:
198 // (1) The source can't see server information.
199 // (2) The source is not local to the current server.
200 LocalUser* lu = IS_LOCAL(memb->user);
201 if (data.flags['f'] && source_can_see_server && lu)
204 // The source only wants local users. This user is eligible if:
205 // (1) The source can't see server information.
206 // (2) The source is local to the current server.
207 if (data.flags['l'] && source_can_see_server && !lu)
210 // Only show operators if the oper flag has been specified.
211 if (data.flags['o'] && !memb->user->IsOper())
214 // All other flags are ignored for channels.
218 bool CommandWho::MatchUser(LocalUser* source, User* user, WhoData& data)
220 // Users who are not fully registered can never match.
221 if (user->registered != REG_ALL)
224 bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
225 bool source_can_see_target = source == user || source_has_users_auspex;
226 bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
228 // The source only wants remote users. This user is eligible if:
229 // (1) The source can't see server information.
230 // (2) The source is not local to the current server.
231 LocalUser* lu = IS_LOCAL(user);
232 if (data.flags['f'] && source_can_see_server && lu)
235 // The source only wants local users. This user is eligible if:
236 // (1) The source can't see server information.
237 // (2) The source is local to the current server.
238 if (data.flags['l'] && source_can_see_server && !lu)
241 // The source wants to match against users' away messages.
244 match = user->IsAway() && InspIRCd::Match(user->awaymsg, data.matchtext, ascii_case_insensitive_map);
246 // The source wants to match against users' account names.
247 else if (data.flags['a'])
249 const AccountExtItem* accountext = GetAccountExtItem();
250 const std::string* account = accountext ? accountext->get(user) : NULL;
251 match = account && InspIRCd::Match(*account, data.matchtext);
254 // The source wants to match against users' hostnames.
255 else if (data.flags['h'])
257 const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
258 match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
261 // The source wants to match against users' IP addresses.
262 else if (data.flags['i'])
263 match = source_can_see_target && InspIRCd::MatchCIDR(user->GetIPString(), data.matchtext, ascii_case_insensitive_map);
265 // The source wants to match against users' modes.
266 else if (data.flags['m'])
268 if (source_can_see_target)
271 for (std::string::const_iterator iter = data.matchtext.begin(); iter != data.matchtext.end(); ++iter)
273 unsigned char chr = static_cast<unsigned char>(*iter);
276 // The following user modes should be set.
281 // The following user modes should be unset.
287 if (user->IsModeSet(chr) != set)
293 // All of the modes matched.
298 // The source wants to match against users' nicks.
299 else if (data.flags['n'])
300 match = InspIRCd::Match(user->nick, data.matchtext);
302 // The source wants to match against users' connection ports.
303 else if (data.flags['p'])
305 if (source_can_see_target && lu)
307 irc::portparser portrange(data.matchtext, false);
309 while ((port = portrange.GetToken()))
311 if (port == lu->server_sa.port())
320 // The source wants to match against users' real names.
321 else if (data.flags['r'])
322 match = InspIRCd::Match(user->GetRealName(), data.matchtext, ascii_case_insensitive_map);
324 else if (data.flags['s'])
326 bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
327 const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
328 match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
331 // The source wants to match against users' connection times.
332 else if (data.flags['t'])
334 time_t seconds = ServerInstance->Time() - InspIRCd::Duration(data.matchtext);
335 if (user->signon >= seconds)
339 // The source wants to match against users' idents.
340 else if (data.flags['u'])
341 match = InspIRCd::Match(user->ident, data.matchtext, ascii_case_insensitive_map);
343 // The <name> passed to WHO is matched against users' host, server,
344 // real name and nickname if the channel <name> cannot be found.
347 const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
348 match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
352 bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
353 const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
354 match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
358 match = InspIRCd::Match(user->GetRealName(), data.matchtext, ascii_case_insensitive_map);
361 match = InspIRCd::Match(user->nick, data.matchtext);
367 void CommandWho::WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* chan, WhoData& data)
369 if (!CanView(chan, source))
372 bool inside = chan->HasUser(source);
373 const Channel::MemberMap& users = chan->GetUsers();
374 for (Channel::MemberMap::const_iterator iter = users.begin(); iter != users.end(); ++iter)
376 User* user = iter->first;
377 Membership* memb = iter->second;
379 // Only show invisible users if the source is in the channel or has the users/auspex priv.
380 if (!inside && user->IsModeSet(invisiblemode) && !source->HasPrivPermission("users/auspex"))
383 // Skip the user if it doesn't match the query.
384 if (!MatchChannel(source, memb, data))
387 SendWhoLine(source, parameters, memb, user, data);
392 void CommandWho::WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data)
394 bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
395 for (typename T::const_iterator iter = users.begin(); iter != users.end(); ++iter)
397 User* user = GetUser(iter);
399 // Only show users in response to a fuzzy WHO if we can see them normally.
400 bool can_see_normally = user == source || source->SharesChannelWith(user) || !user->IsModeSet(invisiblemode);
401 if (data.fuzzy_match && !can_see_normally && !source_has_users_auspex)
404 // Skip the user if it doesn't match the query.
405 if (!MatchUser(source, user, data))
408 SendWhoLine(source, parameters, NULL, user, data);
412 void CommandWho::SendWhoLine(LocalUser* source, const std::vector<std::string>& parameters, Membership* memb, User* user, WhoData& data)
415 memb = GetFirstVisibleChannel(source, user);
417 bool source_can_see_target = source == user || source->HasPrivPermission("users/auspex");
418 Numeric::Numeric wholine(data.whox ? RPL_WHOSPCRPL : RPL_WHOREPLY);
421 // The source used WHOX so we send a fancy customised response.
423 // Include the query type in the reply.
424 if (data.whox_fields['t'])
425 wholine.push(data.whox_querytype.empty() || data.whox_querytype.length() > 3 ? "0" : data.whox_querytype);
427 // Include the first channel name.
428 if (data.whox_fields['c'])
429 wholine.push(memb ? memb->chan->name : "*");
431 // Include the user's ident.
432 if (data.whox_fields['u'])
433 wholine.push(user->ident);
435 // Include the user's IP address.
436 if (data.whox_fields['i'])
437 wholine.push(source_can_see_target ? user->GetIPString() : "255.255.255.255");
439 // Include the user's hostname.
440 if (data.whox_fields['h'])
441 wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
443 // Include the server name.
444 if (data.whox_fields['s'])
446 if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
447 wholine.push(user->server->GetName());
449 wholine.push(ServerInstance->Config->HideServer);
452 // Include the user's nickname.
453 if (data.whox_fields['n'])
454 wholine.push(user->nick);
456 // Include the user's flags.
457 if (data.whox_fields['f'])
460 std::string flags(user->IsAway() ? "G" : "H");
464 flags.push_back('*');
466 // Membership prefix.
469 char prefix = memb->GetPrefixChar();
471 flags.push_back(prefix);
477 // Include the number of hops between the users.
478 if (data.whox_fields['d'])
481 // Include the user's idle time.
482 if (data.whox_fields['l'])
484 LocalUser* lu = IS_LOCAL(user);
485 unsigned long idle = lu ? ServerInstance->Time() - lu->idle_lastmsg : 0;
486 wholine.push(ConvToStr(idle));
489 // Include the user's account name.
490 if (data.whox_fields['a'])
492 const AccountExtItem* accountext = GetAccountExtItem();
493 const std::string* account = accountext ? accountext->get(user) : NULL;
494 wholine.push(account ? *account : "0");
497 // Include the user's operator rank level.
498 if (data.whox_fields['o'])
499 wholine.push(memb ? ConvToStr(memb->getRank()) : "0");
501 // Include the user's real name.
502 if (data.whox_fields['r'])
503 wholine.push(user->GetRealName());
507 // We are not using WHOX so we just send a plain RFC response.
509 // Include the channel name.
510 wholine.push(memb ? memb->chan->name : "*");
512 // Include the user's ident.
513 wholine.push(user->ident);
515 // Include the user's hostname.
516 wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
518 // Include the server name.
519 if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
520 wholine.push(user->server->GetName());
522 wholine.push(ServerInstance->Config->HideServer);
524 // Include the user's nick.
525 wholine.push(user->nick);
527 // Include the user's flags.
530 std::string flags(user->IsAway() ? "G" : "H");
534 flags.push_back('*');
536 // Membership prefix.
539 char prefix = memb->GetPrefixChar();
541 flags.push_back(prefix);
547 // Include the number of hops between the users and the user's real name.
549 wholine.GetParams().back().append(user->GetRealName());
553 FIRST_MOD_RESULT_CUSTOM(whoevprov, Who::EventListener, OnWhoLine, res, (data, source, user, memb, wholine));
554 if (res != MOD_RES_DENY)
555 data.results.push_back(wholine);
558 CmdResult CommandWho::HandleLocal(LocalUser* user, const Params& parameters)
560 WhoData data(parameters);
562 // Is the source running a WHO on a channel?
563 Channel* chan = ServerInstance->FindChan(data.matchtext);
565 WhoChannel(user, parameters, chan, data);
567 // If we only want to match against opers we only have to iterate the oper list.
568 else if (data.flags['o'])
569 WhoUsers(user, parameters, ServerInstance->Users->all_opers, data);
571 // Otherwise we have to use the global user list.
573 WhoUsers(user, parameters, ServerInstance->Users->GetUsers(), data);
575 // Send the results to the source.
576 for (std::vector<Numeric::Numeric>::const_iterator n = data.results.begin(); n != data.results.end(); ++n)
577 user->WriteNumeric(*n);
578 user->WriteNumeric(RPL_ENDOFWHO, (data.matchtext.empty() ? "*" : data.matchtext.c_str()), "End of /WHO list.");
580 // Penalize the source a bit for large queries with one unit of penalty per 200 results.
581 user->CommandFloodPenalty += data.results.size() * 5;
585 class CoreModWho : public Module
596 void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
601 Version GetVersion() CXX11_OVERRIDE
603 return Version("Provides the WHO command", VF_VENDOR|VF_CORE);
607 MODULE_INIT(CoreModWho)