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