]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/coremods/core_whois.cpp
0f1fa8d20c82066621461f1c90a54d09b2c07a5c
[user/henk/code/inspircd.git] / src / coremods / core_whois.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
5  *   Copyright (C) 2018 linuxdaemon <linuxdaemon.irc@gmail.com>
6  *   Copyright (C) 2018 Dylan Frank <b00mx0r@aureus.pw>
7  *   Copyright (C) 2017-2018, 2020 Sadie Powell <sadie@witchery.services>
8  *   Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com>
9  *   Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
10  *   Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
11  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
12  *   Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
13  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
14  *   Copyright (C) 2006-2008, 2010 Craig Edwards <brain@inspircd.org>
15  *
16  * This file is part of InspIRCd.  InspIRCd is free software: you can
17  * redistribute it and/or modify it under the terms of the GNU General Public
18  * License as published by the Free Software Foundation, version 2.
19  *
20  * This program is distributed in the hope that it will be useful, but WITHOUT
21  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
23  * details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27  */
28
29
30 #include "inspircd.h"
31 #include "modules/whois.h"
32
33 enum SplitWhoisState
34 {
35         // Don't split private/secret channels into a separate RPL_WHOISCHANNELS numeric.
36         SPLITWHOIS_NONE,
37
38         // Split private/secret channels into a separate RPL_WHOISCHANNELS numeric.
39         SPLITWHOIS_SPLIT,
40
41         // Split private/secret channels into a separate RPL_WHOISCHANNELS numeric with RPL_CHANNELSMSG to explain the split.
42         SPLITWHOIS_SPLITMSG
43 };
44
45 class WhoisContextImpl : public Whois::Context
46 {
47         Events::ModuleEventProvider& lineevprov;
48
49  public:
50         WhoisContextImpl(LocalUser* src, User* targ, Events::ModuleEventProvider& evprov)
51                 : Whois::Context(src, targ)
52                 , lineevprov(evprov)
53         {
54         }
55
56         using Whois::Context::SendLine;
57         void SendLine(Numeric::Numeric& numeric) CXX11_OVERRIDE;
58 };
59
60 void WhoisContextImpl::SendLine(Numeric::Numeric& numeric)
61 {
62         ModResult MOD_RESULT;
63         FIRST_MOD_RESULT_CUSTOM(lineevprov, Whois::LineEventListener, OnWhoisLine, MOD_RESULT, (*this, numeric));
64
65         if (MOD_RESULT != MOD_RES_DENY)
66                 source->WriteNumeric(numeric);
67 }
68
69 /** Handle /WHOIS.
70  */
71 class CommandWhois : public SplitCommand
72 {
73         ChanModeReference secretmode;
74         ChanModeReference privatemode;
75         UserModeReference snomaskmode;
76         Events::ModuleEventProvider evprov;
77         Events::ModuleEventProvider lineevprov;
78
79         void DoWhois(LocalUser* user, User* dest, time_t signon, unsigned long idle);
80         void SendChanList(WhoisContextImpl& whois);
81
82  public:
83         /** If true then all opers are shown with a generic 'is a server operator' line rather than the oper type. */
84         bool genericoper;
85
86         /** How to handle private/secret channels in the WHOIS response. */
87         SplitWhoisState splitwhois;
88
89
90
91         /** Constructor for whois.
92          */
93         CommandWhois(Module* parent)
94                 : SplitCommand(parent, "WHOIS", 1)
95                 , secretmode(parent, "secret")
96                 , privatemode(parent, "private")
97                 , snomaskmode(parent, "snomask")
98                 , evprov(parent, "event/whois")
99                 , lineevprov(parent, "event/whoisline")
100         {
101                 Penalty = 2;
102                 syntax = "[<servername>] <nick>[,<nick>]+";
103         }
104
105         /** Handle command.
106          * @param parameters The parameters to the command
107          * @param user The user issuing the command
108          * @return A value from CmdResult to indicate command success or failure.
109          */
110         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE;
111         CmdResult HandleRemote(RemoteUser* target, const Params& parameters) CXX11_OVERRIDE;
112 };
113
114 class WhoisNumericSink
115 {
116         WhoisContextImpl& whois;
117  public:
118         WhoisNumericSink(WhoisContextImpl& whoisref)
119                 : whois(whoisref)
120         {
121         }
122
123         void operator()(Numeric::Numeric& numeric) const
124         {
125                 whois.SendLine(numeric);
126         }
127 };
128
129 class WhoisChanListNumericBuilder : public Numeric::GenericBuilder<' ', false, WhoisNumericSink>
130 {
131  public:
132         WhoisChanListNumericBuilder(WhoisContextImpl& whois)
133                 : Numeric::GenericBuilder<' ', false, WhoisNumericSink>(WhoisNumericSink(whois), RPL_WHOISCHANNELS, false, whois.GetSource()->nick.size() + whois.GetTarget()->nick.size() + 1)
134         {
135                 GetNumeric().push(whois.GetTarget()->nick).push(std::string());
136         }
137 };
138
139 class WhoisChanList
140 {
141         const SplitWhoisState& splitwhois;
142         WhoisChanListNumericBuilder num;
143         WhoisChanListNumericBuilder secretnum;
144         std::string prefixstr;
145
146         void AddMember(Membership* memb, WhoisChanListNumericBuilder& out)
147         {
148                 prefixstr.clear();
149                 const char prefix = memb->GetPrefixChar();
150                 if (prefix)
151                         prefixstr.push_back(prefix);
152                 out.Add(prefixstr, memb->chan->name);
153         }
154
155  public:
156         WhoisChanList(WhoisContextImpl& whois, const SplitWhoisState& sws)
157                 : splitwhois(sws)
158                 , num(whois)
159                 , secretnum(whois)
160         {
161         }
162
163         void AddVisible(Membership* memb)
164         {
165                 AddMember(memb, num);
166         }
167
168         void AddHidden(Membership* memb)
169         {
170                 AddMember(memb, splitwhois == SPLITWHOIS_NONE ? num : secretnum);
171         }
172
173         void Flush(WhoisContextImpl& whois)
174         {
175                 num.Flush();
176                 if (!secretnum.IsEmpty() && splitwhois == SPLITWHOIS_SPLITMSG)
177                         whois.SendLine(RPL_CHANNELSMSG, "is on private/secret channels:");
178                 secretnum.Flush();
179         }
180 };
181
182 void CommandWhois::SendChanList(WhoisContextImpl& whois)
183 {
184         WhoisChanList chanlist(whois, splitwhois);
185
186         User* const target = whois.GetTarget();
187         bool hasoperpriv = whois.GetSource()->HasPrivPermission("users/channel-spy");
188         for (User::ChanList::iterator i = target->chans.begin(); i != target->chans.end(); ++i)
189         {
190                 Membership* memb = *i;
191                 Channel* c = memb->chan;
192
193                 // Anyone can view channels which are not private or secret.
194                 if (!c->IsModeSet(privatemode) && !c->IsModeSet(secretmode))
195                         chanlist.AddVisible(memb);
196
197                 // Hidden channels are visible when the following conditions are true:
198                 // (1) The source user and the target user are the same.
199                 // (2) The source user is a member of the hidden channel.
200                 // (3) The source user is an oper with the users/channel-spy privilege.
201                 else if (whois.IsSelfWhois() || c->HasUser(whois.GetSource()) || hasoperpriv)
202                         chanlist.AddHidden(memb);
203         }
204
205         chanlist.Flush(whois);
206 }
207
208 void CommandWhois::DoWhois(LocalUser* user, User* dest, time_t signon, unsigned long idle)
209 {
210         WhoisContextImpl whois(user, dest, lineevprov);
211
212         whois.SendLine(RPL_WHOISUSER, dest->ident, dest->GetDisplayedHost(), '*', dest->GetRealName());
213         if (!user->server->IsULine() && (whois.IsSelfWhois() || user->HasPrivPermission("users/auspex")))
214         {
215                 whois.SendLine(RPL_WHOISHOST, InspIRCd::Format("is connecting from %s@%s %s", dest->ident.c_str(), dest->GetRealHost().c_str(), dest->GetIPString().c_str()));
216         }
217
218         SendChanList(whois);
219
220         if (!whois.IsSelfWhois() && !ServerInstance->Config->HideServer.empty() && !user->HasPrivPermission("servers/auspex"))
221         {
222                 whois.SendLine(RPL_WHOISSERVER, ServerInstance->Config->HideServer, ServerInstance->Config->Network);
223         }
224         else
225         {
226                 whois.SendLine(RPL_WHOISSERVER, dest->server->GetName(), dest->server->GetDesc());
227         }
228
229         if (dest->IsAway())
230         {
231                 whois.SendLine(RPL_AWAY, dest->awaymsg);
232         }
233
234         if (dest->IsOper())
235         {
236                 if (genericoper)
237                         whois.SendLine(RPL_WHOISOPERATOR, "is a server operator");
238                 else
239                         whois.SendLine(RPL_WHOISOPERATOR, InspIRCd::Format("is %s %s on %s", (strchr("AEIOUaeiou",dest->oper->name[0]) ? "an" : "a"), dest->oper->name.c_str(), ServerInstance->Config->Network.c_str()));
240         }
241
242         if (whois.IsSelfWhois() || user->HasPrivPermission("users/auspex"))
243         {
244                 if (dest->IsModeSet(snomaskmode))
245                 {
246                         whois.SendLine(RPL_WHOISMODES, InspIRCd::Format("is using modes %s %s", dest->GetModeLetters().c_str(), snomaskmode->GetUserParameter(dest).c_str()));
247                 }
248                 else
249                 {
250                         whois.SendLine(RPL_WHOISMODES, InspIRCd::Format("is using modes %s", dest->GetModeLetters().c_str()));
251                 }
252         }
253
254         FOREACH_MOD_CUSTOM(evprov, Whois::EventListener, OnWhois, (whois));
255
256         /*
257          * We only send these if we've been provided them. That is, if hideserver is turned off, and user is local, or
258          * if remote whois is queried, too. This is to keep the user hidden, and also since you can't reliably tell remote time. -- w00t
259          */
260         if ((idle) || (signon))
261         {
262                 whois.SendLine(RPL_WHOISIDLE, idle, signon, "seconds idle, signon time");
263         }
264
265         whois.SendLine(RPL_ENDOFWHOIS, "End of /WHOIS list.");
266 }
267
268 CmdResult CommandWhois::HandleRemote(RemoteUser* target, const Params& parameters)
269 {
270         if (parameters.size() < 2)
271                 return CMD_FAILURE;
272
273         User* user = ServerInstance->FindUUID(parameters[0]);
274         if (!user)
275                 return CMD_FAILURE;
276
277         // User doing the whois must be on this server
278         LocalUser* localuser = IS_LOCAL(user);
279         if (!localuser)
280                 return CMD_FAILURE;
281
282         unsigned long idle = ConvToNum<unsigned long>(parameters.back());
283         DoWhois(localuser, target, target->signon, idle);
284
285         return CMD_SUCCESS;
286 }
287
288 CmdResult CommandWhois::HandleLocal(LocalUser* user, const Params& parameters)
289 {
290         User *dest;
291         unsigned int userindex = 0;
292         unsigned long idle = 0;
293         time_t signon = 0;
294
295         if (CommandParser::LoopCall(user, this, parameters, 0))
296                 return CMD_SUCCESS;
297
298         /*
299          * If 2 parameters are specified (/whois nick nick), ignore the first one like spanningtree
300          * does, and use the second one, otherwise, use the only parameter. -- djGrrr
301          */
302         if (parameters.size() > 1)
303                 userindex = 1;
304
305         dest = ServerInstance->FindNickOnly(parameters[userindex]);
306
307         if ((dest) && (dest->registered == REG_ALL))
308         {
309                 /*
310                  * Okay. Umpteenth attempt at doing this, so let's re-comment...
311                  * For local users (/w localuser), we show idletime if hideserver is disabled
312                  * For local users (/w localuser localuser), we always show idletime, hence parameters.size() > 1 check.
313                  * For remote users (/w remoteuser), we do NOT show idletime
314                  * For remote users (/w remoteuser remoteuser), spanningtree will handle calling do_whois, so we can ignore this case.
315                  * Thanks to djGrrr for not being impatient while I have a crap day coding. :p -- w00t
316                  */
317                 LocalUser* localuser = IS_LOCAL(dest);
318                 if (localuser && (ServerInstance->Config->HideServer.empty() || parameters.size() > 1))
319                 {
320                         idle = labs((long)((localuser->idle_lastmsg)-ServerInstance->Time()));
321                         signon = dest->signon;
322                 }
323
324                 DoWhois(user,dest,signon,idle);
325         }
326         else
327         {
328                 /* no such nick/channel */
329                 user->WriteNumeric(Numerics::NoSuchNick(!parameters[userindex].empty() ? parameters[userindex] : "*"));
330                 user->WriteNumeric(RPL_ENDOFWHOIS, (!parameters[userindex].empty() ? parameters[userindex] : "*"), "End of /WHOIS list.");
331                 return CMD_FAILURE;
332         }
333
334         return CMD_SUCCESS;
335 }
336
337 class CoreModWhois : public Module
338 {
339  private:
340         CommandWhois cmd;
341
342  public:
343         CoreModWhois()
344                 : cmd(this)
345         {
346         }
347
348         void ReadConfig(ConfigStatus&) CXX11_OVERRIDE
349         {
350                 ConfigTag* tag = ServerInstance->Config->ConfValue("options");
351                 const std::string splitwhois = tag->getString("splitwhois", "no", 1);
352                 SplitWhoisState newsplitstate;
353                 if (stdalgo::string::equalsci(splitwhois, "no"))
354                         newsplitstate = SPLITWHOIS_NONE;
355                 else if (stdalgo::string::equalsci(splitwhois, "split"))
356                         newsplitstate = SPLITWHOIS_SPLIT;
357                 else if (stdalgo::string::equalsci(splitwhois, "splitmsg"))
358                         newsplitstate = SPLITWHOIS_SPLITMSG;
359                 else
360                         throw ModuleException(splitwhois + " is an invalid <options:splitwhois> value, at " + tag->getTagLocation());
361
362                 ConfigTag* security = ServerInstance->Config->ConfValue("security");
363                 cmd.genericoper = security->getBool("genericoper");
364                 cmd.splitwhois = newsplitstate;
365         }
366
367         Version GetVersion() CXX11_OVERRIDE
368         {
369                 return Version("Provides the WHOIS command", VF_VENDOR|VF_CORE);
370         }
371 };
372
373 MODULE_INIT(CoreModWhois)