]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/coremods/core_who.cpp
Various text improvements: consistency, syntax, help and doc updates/fixes.
[user/henk/code/inspircd.git] / src / coremods / core_who.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2018 Peter Powell <petpow@saberuk.com>
5  *   Copyright (C) 2014 Adam <Adam@anope.org>
6  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
7  *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
8  *
9  * This file is part of InspIRCd.  InspIRCd is free software: you can
10  * redistribute it and/or modify it under the terms of the GNU General Public
11  * License as published by the Free Software Foundation, version 2.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22
23 #include "inspircd.h"
24 #include "modules/account.h"
25 #include "modules/who.h"
26
27 enum
28 {
29         // From RFC 1459.
30         RPL_ENDOFWHO = 315,
31         RPL_WHOREPLY = 352,
32
33         // From ircu.
34         RPL_WHOSPCRPL = 354
35 };
36
37 struct WhoData : public Who::Request
38 {
39         WhoData(const CommandBase::Params& parameters)
40         {
41                 // Find the matchtext and swap the 0 for a * so we can use InspIRCd::Match on it.
42                 matchtext = parameters.size() > 2 ? parameters[2] : parameters[0];
43                 if (matchtext == "0")
44                         matchtext = "*";
45
46                 // Fuzzy matches are when the source has not specified a specific user.
47                 fuzzy_match = (parameters.size() > 1) || (matchtext.find_first_of("*?.") != std::string::npos);
48
49                 // If flags have been specified by the source.
50                 if (parameters.size() > 1)
51                 {
52                         std::bitset<UCHAR_MAX>* current_bitset = &flags;
53                         for (std::string::const_iterator iter = parameters[1].begin(); iter != parameters[1].end(); ++iter)
54                         {
55                                 unsigned char chr = static_cast<unsigned char>(*iter);
56
57                                 // If the source specifies a percentage the rest of the flags are WHOX fields.
58                                 if (chr == '%')
59                                 {
60                                         whox = true;
61                                         current_bitset = &whox_fields;
62                                         continue;
63                                 }
64
65                                 // If we are in WHOX mode and the source specifies a comma
66                                 // the rest of the parameter is the query type.
67                                 if (whox && chr == ',')
68                                 {
69                                         whox_querytype.assign(++iter, parameters[1].end());
70                                         break;
71                                 }
72
73                                 // The source specified a matching flag.
74                                 current_bitset->set(chr);
75                         }
76                 }
77         }
78 };
79
80 class CommandWho : public SplitCommand
81 {
82  private:
83         ChanModeReference secretmode;
84         ChanModeReference privatemode;
85         UserModeReference hidechansmode;
86         UserModeReference invisiblemode;
87         Events::ModuleEventProvider whoevprov;
88
89         /** Determines whether a user can view the users of a channel. */
90         bool CanView(Channel* chan, User* user)
91         {
92                 // If we are in a channel we can view all users in it.
93                 if (chan->HasUser(user))
94                         return true;
95
96                 // Opers with the users/auspex priv can see everything.
97                 if (user->HasPrivPermission("users/auspex"))
98                         return true;
99
100                 // You can see inside a channel from outside unless it is secret or private.
101                 return !chan->IsModeSet(secretmode) && !chan->IsModeSet(privatemode);
102         }
103
104         /** Gets the first channel which is visible between the source and the target users. */
105         Membership* GetFirstVisibleChannel(LocalUser* source, User* user)
106         {
107                 for (User::ChanList::iterator iter = user->chans.begin(); iter != user->chans.end(); ++iter)
108                 {
109                         Membership* memb = *iter;
110
111                         // TODO: move the +I check into m_hidechans.
112                         bool has_modes = memb->chan->IsModeSet(secretmode) || memb->chan->IsModeSet(privatemode) || user->IsModeSet(hidechansmode);
113                         if (source == user || !has_modes || memb->chan->HasUser(source))
114                                 return memb;
115                 }
116                 return NULL;
117         }
118
119         /** Determines whether WHO flags match a specific channel user. */
120         bool MatchChannel(LocalUser* source, Membership* memb, WhoData& data);
121
122         /** Determines whether WHO flags match a specific user. */
123         static bool MatchUser(LocalUser* source, User* target, WhoData& data);
124
125         /** Performs a WHO request on a channel. */
126         void WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* c, WhoData& data);
127
128         /** Template for getting a user from various types of collection. */
129         template<typename T>
130         static User* GetUser(T& t);
131
132         /** Performs a WHO request on a list of users. */
133         template<typename T>
134         void WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data);
135
136  public:
137         CommandWho(Module* parent)
138                 : SplitCommand(parent, "WHO", 1, 3)
139                 , secretmode(parent, "secret")
140                 , privatemode(parent, "private")
141                 , hidechansmode(parent, "hidechans")
142                 , invisiblemode(parent, "invisible")
143                 , whoevprov(parent, "event/who")
144         {
145                 allow_empty_last_param = false;
146                 syntax = "<server>|<nick>|<channel>|<realname>|<host>|0 [[Aafhilmnoprstux][%acdfhilnorstu] <server>|<nick>|<channel>|<realname>|<host>|0]";
147         }
148
149         /** Sends a WHO reply to a user. */
150         void SendWhoLine(LocalUser* user, const std::vector<std::string>& parameters, Membership* memb, User* u, WhoData& data);
151
152         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE;
153 };
154
155 template<> User* CommandWho::GetUser(UserManager::OperList::const_iterator& t) { return *t; }
156 template<> User* CommandWho::GetUser(user_hash::const_iterator& t) { return t->second; }
157
158 bool CommandWho::MatchChannel(LocalUser* source, Membership* memb, WhoData& data)
159 {
160         bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
161         bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
162
163         // The source only wants remote users. This user is eligible if:
164         //   (1) The source can't see server information.
165         //   (2) The source is not local to the current server.
166         LocalUser* lu = IS_LOCAL(memb->user);
167         if (data.flags['f'] && source_can_see_server && lu)
168                 return false;
169
170         // The source only wants local users. This user is eligible if:
171         //   (1) The source can't see server information.
172         //   (2) The source is local to the current server.
173         if (data.flags['l'] && source_can_see_server && !lu)
174                 return false;
175
176         // Only show operators if the oper flag has been specified.
177         if (data.flags['o'] && !memb->user->IsOper())
178                 return false;
179
180         // All other flags are ignored for channels.
181         return true;
182 }
183
184 bool CommandWho::MatchUser(LocalUser* source, User* user, WhoData& data)
185 {
186         // Users who are not fully registered can never match.
187         if (user->registered != REG_ALL)
188                 return false;
189
190         bool source_has_users_auspex = source->HasPrivPermission("users/auspex");
191         bool source_can_see_target = source == user || source_has_users_auspex;
192         bool source_can_see_server = ServerInstance->Config->HideServer.empty() || source_has_users_auspex;
193
194         // The source only wants remote users. This user is eligible if:
195         //   (1) The source can't see server information.
196         //   (2) The source is not local to the current server.
197         LocalUser* lu = IS_LOCAL(user);
198         if (data.flags['f'] && source_can_see_server && lu)
199                 return false;
200
201         // The source only wants local users. This user is eligible if:
202         //   (1) The source can't see server information.
203         //   (2) The source is local to the current server.
204         if (data.flags['l'] && source_can_see_server && !lu)
205                 return false;
206
207         // The source wants to match against users' away messages.
208         bool match = false;
209         if (data.flags['A'])
210                 match = user->IsAway() && InspIRCd::Match(user->awaymsg, data.matchtext, ascii_case_insensitive_map);
211
212         // The source wants to match against users' account names.
213         else if (data.flags['a'])
214         {
215                 const AccountExtItem* accountext = GetAccountExtItem();
216                 const std::string* account = accountext ? accountext->get(user) : NULL;
217                 match = account && InspIRCd::Match(*account, data.matchtext);
218         }
219
220         // The source wants to match against users' hostnames.
221         else if (data.flags['h'])
222         {
223                 const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
224                 match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
225         }
226
227         // The source wants to match against users' IP addresses.
228         else if (data.flags['i'])
229                 match = source_can_see_target && InspIRCd::MatchCIDR(user->GetIPString(), data.matchtext, ascii_case_insensitive_map);
230
231         // The source wants to match against users' modes.
232         else if (data.flags['m'])
233         {
234                 if (source_can_see_target)
235                 {
236                         bool set = true;
237                         for (std::string::const_iterator iter = data.matchtext.begin(); iter != data.matchtext.end(); ++iter)
238                         {
239                                 unsigned char chr = static_cast<unsigned char>(*iter);
240                                 switch (chr)
241                                 {
242                                         // The following user modes should be set.
243                                         case '+':
244                                                 set = true;
245                                                 break;
246
247                                         // The following user modes should be unset.
248                                         case '-':
249                                                 set = false;
250                                                 break;
251
252                                         default:
253                                                 if (user->IsModeSet(chr) != set)
254                                                         return false;
255                                                 break;
256                                 }
257                         }
258
259                         // All of the modes matched.
260                         return true;
261                 }
262         }
263
264         // The source wants to match against users' nicks.
265         else if (data.flags['n'])
266                 match = InspIRCd::Match(user->nick, data.matchtext);
267
268         // The source wants to match against users' connection ports.
269         else if (data.flags['p'])
270         {
271                 if (source_can_see_target && lu)
272                 {
273                         irc::portparser portrange(data.matchtext, false);
274                         long port;
275                         while ((port = portrange.GetToken()))
276                         {
277                                 if (port == lu->server_sa.port())
278                                 {
279                                         match = true;
280                                         break;
281                                 }
282                         }
283                 }
284         }
285
286         // The source wants to match against users' real names.
287         else if (data.flags['r'])
288                 match = InspIRCd::Match(user->GetRealName(), data.matchtext, ascii_case_insensitive_map);
289
290         else if (data.flags['s'])
291         {
292                 bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
293                 const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
294                 match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
295         }
296
297         // The source wants to match against users' connection times.
298         else if (data.flags['t'])
299         {
300                 time_t seconds = ServerInstance->Time() - InspIRCd::Duration(data.matchtext);
301                 if (user->signon >= seconds)
302                         match = true;
303         }
304
305         // The source wants to match against users' idents.
306         else if (data.flags['u'])
307                 match = InspIRCd::Match(user->ident, data.matchtext, ascii_case_insensitive_map);
308
309         // The <name> passed to WHO is matched against users' host, server,
310         // real name and nickname if the channel <name> cannot be found.
311         else
312         {
313                 const std::string host = user->GetHost(source_can_see_target && data.flags['x']);
314                 match = InspIRCd::Match(host, data.matchtext, ascii_case_insensitive_map);
315
316                 if (!match)
317                 {
318                         bool show_real_server_name = ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']);
319                         const std::string server = show_real_server_name ? user->server->GetName() : ServerInstance->Config->HideServer;
320                         match = InspIRCd::Match(server, data.matchtext, ascii_case_insensitive_map);
321                 }
322
323                 if (!match)
324                         match = InspIRCd::Match(user->GetRealName(), data.matchtext, ascii_case_insensitive_map);
325
326                 if (!match)
327                         match = InspIRCd::Match(user->nick, data.matchtext);
328         }
329
330         return match;
331 }
332
333 void CommandWho::WhoChannel(LocalUser* source, const std::vector<std::string>& parameters, Channel* chan, WhoData& data)
334 {
335         if (!CanView(chan, source))
336                 return;
337
338         bool inside = chan->HasUser(source);
339         const Channel::MemberMap& users = chan->GetUsers();
340         for (Channel::MemberMap::const_iterator iter = users.begin(); iter != users.end(); ++iter)
341         {
342                 User* user = iter->first;
343                 Membership* memb = iter->second;
344
345                 // Only show invisible users if the source is in the channel or has the users/auspex priv.
346                 if (!inside && user->IsModeSet(invisiblemode) && !source->HasPrivPermission("users/auspex"))
347                         continue;
348
349                 // Skip the user if it doesn't match the query.
350                 if (!MatchChannel(source, memb, data))
351                         continue;
352
353                 SendWhoLine(source, parameters, memb, user, data);
354         }
355 }
356
357 template<typename T>
358 void CommandWho::WhoUsers(LocalUser* source, const std::vector<std::string>& parameters, const T& users, WhoData& data)
359 {
360         for (typename T::const_iterator iter = users.begin(); iter != users.end(); ++iter)
361         {
362                 User* user = GetUser(iter);
363
364                 // Only show users in response to a fuzzy WHO if we can see them normally.
365                 bool can_see_normally = user == source || source->SharesChannelWith(user) || !user->IsModeSet(invisiblemode);
366                 if (data.fuzzy_match && !can_see_normally && !source->HasPrivPermission("users/auspex"))
367                         continue;
368
369                 // Skip the user if it doesn't match the query.
370                 if (!MatchUser(source, user, data))
371                         continue;
372
373                 SendWhoLine(source, parameters, NULL, user, data);
374         }
375 }
376
377 void CommandWho::SendWhoLine(LocalUser* source, const std::vector<std::string>& parameters, Membership* memb, User* user, WhoData& data)
378 {
379         if (!memb)
380                 memb = GetFirstVisibleChannel(source, user);
381
382         bool source_can_see_target = source == user || source->HasPrivPermission("users/auspex");
383         Numeric::Numeric wholine(data.whox ? RPL_WHOSPCRPL : RPL_WHOREPLY);
384         if (data.whox)
385         {
386                 // The source used WHOX so we send a fancy customised response.
387
388                 // Include the query type in the reply.
389                 if (data.whox_fields['t'])
390                         wholine.push(data.whox_querytype.empty() || data.whox_querytype.length() > 3 ? "0" : data.whox_querytype);
391
392                 // Include the first channel name.
393                 if (data.whox_fields['c'])
394                         wholine.push(memb ? memb->chan->name : "*");
395
396                 // Include the user's ident.
397                 if (data.whox_fields['u'])
398                         wholine.push(user->ident);
399
400                 // Include the user's IP address.
401                 if (data.whox_fields['i'])
402                         wholine.push(source_can_see_target ? user->GetIPString() : "255.255.255.255");
403
404                 // Include the user's hostname.
405                 if (data.whox_fields['h'])
406                         wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
407
408                 // Include the server name.
409                 if (data.whox_fields['s'])
410                 {
411                         if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
412                                 wholine.push(user->server->GetName());
413                         else
414                                 wholine.push(ServerInstance->Config->HideServer);
415                 }
416
417                 // Include the user's nickname.
418                 if (data.whox_fields['n'])
419                         wholine.push(user->nick);
420
421                 // Include the user's flags.
422                 if (data.whox_fields['f'])
423                 {
424                         // Away state.
425                         std::string flags(user->IsAway() ? "G" : "H");
426
427                         // Operator status.
428                         if (user->IsOper())
429                                 flags.push_back('*');
430
431                         // Membership prefix.
432                         if (memb)
433                         {
434                                 char prefix = memb->GetPrefixChar();
435                                 if (prefix)
436                                         flags.push_back(prefix);
437                         }
438
439                         wholine.push(flags);
440                 }
441
442                 // Include the number of hops between the users.
443                 if (data.whox_fields['d'])
444                         wholine.push("0");
445
446                 // Include the user's idle time.
447                 if (data.whox_fields['l'])
448                 {
449                         LocalUser* lu = IS_LOCAL(user);
450                         unsigned long idle = lu ? ServerInstance->Time() - lu->idle_lastmsg : 0;
451                         wholine.push(ConvToStr(idle));
452                 }
453
454                 // Include the user's account name.
455                 if (data.whox_fields['a'])
456                 {
457                         const AccountExtItem* accountext = GetAccountExtItem();
458                         const std::string* account = accountext ? accountext->get(user) : NULL;
459                         wholine.push(account ? *account : "0");
460                 }
461
462                 // Include the user's operator rank level.
463                 if (data.whox_fields['o'])
464                         wholine.push(memb ? ConvToStr(memb->getRank()) : "0");
465
466                 // Include the user's real name.
467                 if (data.whox_fields['r'])
468                         wholine.push(user->GetRealName());
469         }
470         else
471         {
472                 // We are not using WHOX so we just send a plain RFC response.
473
474                 // Include the channel name.
475                 wholine.push(memb ? memb->chan->name : "*");
476
477                 // Include the user's ident.
478                 wholine.push(user->ident);
479
480                 // Include the user's hostname.
481                 wholine.push(user->GetHost(source_can_see_target && data.flags['x']));
482
483                 // Include the server name.
484                 if (ServerInstance->Config->HideServer.empty() || (source->HasPrivPermission("servers/auspex") && data.flags['x']))
485                         wholine.push(user->server->GetName());
486                 else
487                         wholine.push(ServerInstance->Config->HideServer);
488
489                 // Include the user's nick.
490                 wholine.push(user->nick);
491
492                 // Include the user's flags.
493                 {
494                         // Away state.
495                         std::string flags(user->IsAway() ? "G" : "H");
496
497                         // Operator status.
498                         if (user->IsOper())
499                                 flags.push_back('*');
500
501                         // Membership prefix.
502                         if (memb)
503                         {
504                                 char prefix = memb->GetPrefixChar();
505                                 if (prefix)
506                                         flags.push_back(prefix);
507                         }
508
509                         wholine.push(flags);
510                 }
511
512                 // Include the number of hops between the users and the user's real name.
513                 wholine.push("0 ");
514                 wholine.GetParams().back().append(user->GetRealName());
515         }
516
517         ModResult res;
518         FIRST_MOD_RESULT_CUSTOM(whoevprov, Who::EventListener, OnWhoLine, res, (data, source, user, memb, wholine));
519         if (res != MOD_RES_DENY)
520                 data.results.push_back(wholine);
521 }
522
523 CmdResult CommandWho::HandleLocal(LocalUser* user, const Params& parameters)
524 {
525         WhoData data(parameters);
526
527         // Is the source running a WHO on a channel?
528         Channel* chan = ServerInstance->FindChan(data.matchtext);
529         if (chan)
530                 WhoChannel(user, parameters, chan, data);
531
532         // If we only want to match against opers we only have to iterate the oper list.
533         else if (data.flags['o'])
534                 WhoUsers(user, parameters, ServerInstance->Users->all_opers, data);
535
536         // Otherwise we have to use the global user list.
537         else
538                 WhoUsers(user, parameters, ServerInstance->Users->GetUsers(), data);
539
540         // Send the results to the source.
541         for (std::vector<Numeric::Numeric>::const_iterator n = data.results.begin(); n != data.results.end(); ++n)
542                 user->WriteNumeric(*n);
543         user->WriteNumeric(RPL_ENDOFWHO, (data.matchtext.empty() ? "*" : data.matchtext.c_str()), "End of /WHO list.");
544
545         // Penalize the source a bit for large queries with one unit of penalty per 200 results.
546         user->CommandFloodPenalty += data.results.size() * 5;
547         return CMD_SUCCESS;
548 }
549
550 class CoreModWho : public Module
551 {
552  private:
553         CommandWho cmd;
554
555  public:
556         CoreModWho()
557                 : cmd(this)
558         {
559         }
560
561         void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
562         {
563                 tokens["WHOX"];
564         }
565
566         Version GetVersion() CXX11_OVERRIDE
567         {
568                 return Version("Provides the WHO command", VF_VENDOR|VF_CORE);
569         }
570 };
571
572 MODULE_INIT(CoreModWho)