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