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