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-2021 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:
WhoisContextImpl(LocalUser * sourceuser,User * targetuser,Events::ModuleEventProvider & evprov)50 	WhoisContextImpl(LocalUser* sourceuser, User* targetuser, Events::ModuleEventProvider& evprov)
51 		: Whois::Context(sourceuser, targetuser)
52 		, lineevprov(evprov)
53 	{
54 	}
55 
56 	using Whois::Context::SendLine;
57 	void SendLine(Numeric::Numeric& numeric) CXX11_OVERRIDE;
58 };
59 
SendLine(Numeric::Numeric & numeric)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 	 */
CommandWhois(Module * parent)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:
WhoisNumericSink(WhoisContextImpl & whoisref)118 	WhoisNumericSink(WhoisContextImpl& whoisref)
119 		: whois(whoisref)
120 	{
121 	}
122 
operator ()(Numeric::Numeric & numeric) const123 	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:
WhoisChanListNumericBuilder(WhoisContextImpl & whois)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 
AddMember(Membership * memb,WhoisChanListNumericBuilder & out)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:
WhoisChanList(WhoisContextImpl & whois,const SplitWhoisState & sws)156 	WhoisChanList(WhoisContextImpl& whois, const SplitWhoisState& sws)
157 		: splitwhois(sws)
158 		, num(whois)
159 		, secretnum(whois)
160 	{
161 	}
162 
AddVisible(Membership * memb)163 	void AddVisible(Membership* memb)
164 	{
165 		AddMember(memb, num);
166 	}
167 
AddHidden(Membership * memb)168 	void AddHidden(Membership* memb)
169 	{
170 		AddMember(memb, splitwhois == SPLITWHOIS_NONE ? num : secretnum);
171 	}
172 
Flush(WhoisContextImpl & whois)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 
SendChanList(WhoisContextImpl & whois)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 
DoWhois(LocalUser * user,User * dest,time_t signon,unsigned long idle)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 (!dest->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, dest->server->IsULine() ? "is a network service" : "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 
HandleRemote(RemoteUser * target,const Params & parameters)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 
HandleLocal(LocalUser * user,const Params & parameters)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:
CoreModWhois()343 	CoreModWhois()
344 		: cmd(this)
345 	{
346 	}
347 
ReadConfig(ConfigStatus &)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 
GetVersion()367 	Version GetVersion() CXX11_OVERRIDE
368 	{
369 		return Version("Provides the WHOIS command", VF_VENDOR|VF_CORE);
370 	}
371 };
372 
373 MODULE_INIT(CoreModWhois)
374