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