1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013, 2017-2019, 2021 Sadie Powell <sadie@witchery.services>
5  *   Copyright (C) 2013 Adam <Adam@anope.org>
6  *   Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com>
7  *   Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
8  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
9  *   Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
10  *   Copyright (C) 2009 John Brooks <special@inspircd.org>
11  *   Copyright (C) 2008-2009 Robin Burchell <robin+git@viroteck.net>
12  *   Copyright (C) 2008, 2010 Craig Edwards <brain@inspircd.org>
13  *   Copyright (C) 2008 Thomas Stagner <aquanight@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/callerid.h"
31 #include "modules/ctctags.h"
32 
33 enum
34 {
35 	RPL_ACCEPTLIST = 281,
36 	RPL_ENDOFACCEPT = 282,
37 	ERR_ACCEPTFULL = 456,
38 	ERR_ACCEPTEXIST = 457,
39 	ERR_ACCEPTNOT = 458,
40 	ERR_TARGUMODEG = 716,
41 	RPL_TARGNOTIFY = 717,
42 	RPL_UMODEGMSG = 718
43 };
44 
45 class callerid_data
46 {
47  public:
48 	typedef insp::flat_set<User*> UserSet;
49 	typedef std::vector<callerid_data*> CallerIdDataSet;
50 
51 	time_t lastnotify;
52 
53 	/** Users I accept messages from
54 	 */
55 	UserSet accepting;
56 
57 	/** Users who list me as accepted
58 	 */
59 	CallerIdDataSet wholistsme;
60 
callerid_data()61 	callerid_data() : lastnotify(0) { }
62 
ToString(bool human) const63 	std::string ToString(bool human) const
64 	{
65 		std::ostringstream oss;
66 		oss << lastnotify;
67 		for (UserSet::const_iterator i = accepting.begin(); i != accepting.end(); ++i)
68 		{
69 			User* u = *i;
70 			if (human)
71 				oss << ' ' << u->nick;
72 			else
73 				oss << ',' << u->uuid;
74 		}
75 		return oss.str();
76 	}
77 };
78 
79 struct CallerIDExtInfo : public ExtensionItem
80 {
CallerIDExtInfoCallerIDExtInfo81 	CallerIDExtInfo(Module* parent)
82 		: ExtensionItem("callerid_data", ExtensionItem::EXT_USER, parent)
83 	{
84 	}
85 
ToHumanCallerIDExtInfo86 	std::string ToHuman(const Extensible* container, void* item) const CXX11_OVERRIDE
87 	{
88 		callerid_data* dat = static_cast<callerid_data*>(item);
89 		return dat->ToString(true);
90 	}
91 
ToInternalCallerIDExtInfo92 	std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE
93 	{
94 		callerid_data* dat = static_cast<callerid_data*>(item);
95 		return dat->ToString(false);
96 	}
97 
FromInternalCallerIDExtInfo98 	void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE
99 	{
100 		void* old = get_raw(container);
101 		if (old)
102 			this->free(NULL, old);
103 		callerid_data* dat = new callerid_data;
104 		set_raw(container, dat);
105 
106 		irc::commasepstream s(value);
107 		std::string tok;
108 		if (s.GetToken(tok))
109 			dat->lastnotify = ConvToNum<time_t>(tok);
110 
111 		while (s.GetToken(tok))
112 		{
113 			User *u = ServerInstance->FindNick(tok);
114 			if ((u) && (u->registered == REG_ALL) && (!u->quitting))
115 			{
116 				if (dat->accepting.insert(u).second)
117 				{
118 					callerid_data* other = this->get(u, true);
119 					other->wholistsme.push_back(dat);
120 				}
121 			}
122 		}
123 	}
124 
getCallerIDExtInfo125 	callerid_data* get(User* user, bool create)
126 	{
127 		callerid_data* dat = static_cast<callerid_data*>(get_raw(user));
128 		if (create && !dat)
129 		{
130 			dat = new callerid_data;
131 			set_raw(user, dat);
132 		}
133 		return dat;
134 	}
135 
freeCallerIDExtInfo136 	void free(Extensible* container, void* item) CXX11_OVERRIDE
137 	{
138 		callerid_data* dat = static_cast<callerid_data*>(item);
139 
140 		// We need to walk the list of users on our accept list, and remove ourselves from their wholistsme.
141 		for (callerid_data::UserSet::iterator it = dat->accepting.begin(); it != dat->accepting.end(); ++it)
142 		{
143 			callerid_data* target = this->get(*it, false);
144 			if (!target)
145 			{
146 				ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (1)");
147 				continue; // shouldn't happen, but oh well.
148 			}
149 
150 			if (!stdalgo::vector::swaperase(target->wholistsme, dat))
151 				ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (2)");
152 		}
153 		delete dat;
154 	}
155 };
156 
157 class CommandAccept : public Command
158 {
159 	/** Pair: first is the target, second is true to add, false to remove
160 	 */
161 	typedef std::pair<User*, bool> ACCEPTAction;
162 
GetTargetAndAction(std::string & tok,User * cmdfrom=NULL)163 	static ACCEPTAction GetTargetAndAction(std::string& tok, User* cmdfrom = NULL)
164 	{
165 		bool remove = (tok[0] == '-');
166 		if ((remove) || (tok[0] == '+'))
167 			tok.erase(tok.begin());
168 
169 		User* target;
170 		if (!cmdfrom || !IS_LOCAL(cmdfrom))
171 			target = ServerInstance->FindNick(tok);
172 		else
173 			target = ServerInstance->FindNickOnly(tok);
174 
175 		if ((!target) || (target->registered != REG_ALL) || (target->quitting))
176 			target = NULL;
177 
178 		return std::make_pair(target, !remove);
179 	}
180 
181 public:
182 	CallerIDExtInfo extInfo;
183 	unsigned int maxaccepts;
CommandAccept(Module * Creator)184 	CommandAccept(Module* Creator) : Command(Creator, "ACCEPT", 1),
185 		extInfo(Creator)
186 	{
187 		allow_empty_last_param = false;
188 		syntax = "*|(+|-)<nick>[,(+|-)<nick>]+";
189 		TRANSLATE1(TR_CUSTOM);
190 	}
191 
EncodeParameter(std::string & parameter,unsigned int index)192 	void EncodeParameter(std::string& parameter, unsigned int index) CXX11_OVERRIDE
193 	{
194 		// Send lists as-is (part of 2.0 compat)
195 		if (parameter.find(',') != std::string::npos)
196 			return;
197 
198 		// Convert a (+|-)<nick> into a [-]<uuid>
199 		ACCEPTAction action = GetTargetAndAction(parameter);
200 		if (!action.first)
201 			return;
202 
203 		parameter = (action.second ? "" : "-") + action.first->uuid;
204 	}
205 
206 	/** Will take any number of nicks (up to MaxTargets), which can be separated by commas.
207 	 * - in front of any nick removes, and an * lists. This effectively means you can do:
208 	 * /accept nick1,nick2,nick3,*
209 	 * to add 3 nicks and then show your list
210 	 */
Handle(User * user,const Params & parameters)211 	CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
212 	{
213 		if (CommandParser::LoopCall(user, this, parameters, 0))
214 			return CMD_SUCCESS;
215 
216 		/* Even if callerid mode is not set, we let them manage their ACCEPT list so that if they go +g they can
217 		 * have a list already setup. */
218 
219 		if (parameters[0] == "*")
220 		{
221 			ListAccept(user);
222 			return CMD_SUCCESS;
223 		}
224 
225 		std::string tok = parameters[0];
226 		ACCEPTAction action = GetTargetAndAction(tok, user);
227 		if (!action.first)
228 		{
229 			user->WriteNumeric(Numerics::NoSuchNick(tok));
230 			return CMD_FAILURE;
231 		}
232 
233 		if ((!IS_LOCAL(user)) && (!IS_LOCAL(action.first)))
234 			// Neither source nor target is local, forward the command to the server of target
235 			return CMD_SUCCESS;
236 
237 		// The second item in the pair is true if the first char is a '+' (or nothing), false if it's a '-'
238 		if (action.second)
239 			return (AddAccept(user, action.first) ? CMD_SUCCESS : CMD_FAILURE);
240 		else
241 			return (RemoveAccept(user, action.first) ? CMD_SUCCESS : CMD_FAILURE);
242 	}
243 
GetRouting(User * user,const Params & parameters)244 	RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
245 	{
246 		// There is a list in parameters[0] in two cases:
247 		// Either when the source is remote, this happens because 2.0 servers send comma separated uuid lists,
248 		// we don't split those but broadcast them, as before.
249 		//
250 		// Or if the source is local then LoopCall() runs OnPostCommand() after each entry in the list,
251 		// meaning the linking module has sent an ACCEPT already for each entry in the list to the
252 		// appropriate server and the ACCEPT with the list of nicks (this) doesn't need to be sent anywhere.
253 		if ((!IS_LOCAL(user)) && (parameters[0].find(',') != std::string::npos))
254 			return ROUTE_BROADCAST;
255 
256 		// Find the target
257 		std::string targetstring = parameters[0];
258 		ACCEPTAction action = GetTargetAndAction(targetstring, user);
259 		if (!action.first)
260 			// Target is a "*" or source is local and the target is a list of nicks
261 			return ROUTE_LOCALONLY;
262 
263 		// Route to the server of the target
264 		return ROUTE_UNICAST(action.first->server);
265 	}
266 
ListAccept(User * user)267 	void ListAccept(User* user)
268 	{
269 		callerid_data* dat = extInfo.get(user, false);
270 		if (dat)
271 		{
272 			for (callerid_data::UserSet::iterator i = dat->accepting.begin(); i != dat->accepting.end(); ++i)
273 				user->WriteNumeric(RPL_ACCEPTLIST, (*i)->nick);
274 		}
275 		user->WriteNumeric(RPL_ENDOFACCEPT, "End of ACCEPT list");
276 	}
277 
AddAccept(User * user,User * whotoadd)278 	bool AddAccept(User* user, User* whotoadd)
279 	{
280 		// Add this user to my accept list first, so look me up..
281 		callerid_data* dat = extInfo.get(user, true);
282 		if (dat->accepting.size() >= maxaccepts)
283 		{
284 			user->WriteNumeric(ERR_ACCEPTFULL, InspIRCd::Format("Accept list is full (limit is %d)", maxaccepts));
285 			return false;
286 		}
287 		if (!dat->accepting.insert(whotoadd).second)
288 		{
289 			user->WriteNumeric(ERR_ACCEPTEXIST, whotoadd->nick, "is already on your accept list");
290 			return false;
291 		}
292 
293 		// Now, look them up, and add me to their list
294 		callerid_data* target = extInfo.get(whotoadd, true);
295 		target->wholistsme.push_back(dat);
296 
297 		user->WriteNotice(whotoadd->nick + " is now on your accept list");
298 		return true;
299 	}
300 
RemoveAccept(User * user,User * whotoremove)301 	bool RemoveAccept(User* user, User* whotoremove)
302 	{
303 		// Remove them from my list, so look up my list..
304 		callerid_data* dat = extInfo.get(user, false);
305 		if (!dat)
306 		{
307 			user->WriteNumeric(ERR_ACCEPTNOT, whotoremove->nick, "is not on your accept list");
308 			return false;
309 		}
310 		if (!dat->accepting.erase(whotoremove))
311 		{
312 			user->WriteNumeric(ERR_ACCEPTNOT, whotoremove->nick, "is not on your accept list");
313 			return false;
314 		}
315 
316 		// Look up their list to remove me.
317 		callerid_data *dat2 = extInfo.get(whotoremove, false);
318 		if (!dat2)
319 		{
320 			// How the fuck is this possible.
321 			ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (3)");
322 			return false;
323 		}
324 
325 		if (!stdalgo::vector::swaperase(dat2->wholistsme, dat))
326 			ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (4)");
327 
328 
329 		user->WriteNotice(whotoremove->nick + " is no longer on your accept list");
330 		return true;
331 	}
332 };
333 
334 class CallerIDAPIImpl : public CallerID::APIBase
335 {
336  private:
337 	CallerIDExtInfo& ext;
338 
339  public:
CallerIDAPIImpl(Module * Creator,CallerIDExtInfo & Ext)340 	CallerIDAPIImpl(Module* Creator, CallerIDExtInfo& Ext)
341 		: CallerID::APIBase(Creator)
342 		, ext(Ext)
343 	{
344 	}
345 
IsOnAcceptList(User * source,User * target)346 	bool IsOnAcceptList(User* source, User* target) CXX11_OVERRIDE
347 	{
348 		callerid_data* dat = ext.get(target, true);
349 		return dat->accepting.count(source);
350 	}
351 };
352 
353 
354 class ModuleCallerID
355 	: public Module
356 	, public CTCTags::EventListener
357 {
358 	CommandAccept cmd;
359 	CallerIDAPIImpl api;
360 	SimpleUserModeHandler myumode;
361 
362 	// Configuration variables:
363 	bool tracknick; // Allow ACCEPT entries to update with nick changes.
364 	unsigned int notify_cooldown; // Seconds between notifications.
365 
366 	/** Removes a user from all accept lists
367 	 * @param who The user to remove from accepts
368 	 */
RemoveFromAllAccepts(User * who)369 	void RemoveFromAllAccepts(User* who)
370 	{
371 		// First, find the list of people who have me on accept
372 		callerid_data *userdata = cmd.extInfo.get(who, false);
373 		if (!userdata)
374 			return;
375 
376 		// Iterate over the list of people who accept me, and remove all entries
377 		for (callerid_data::CallerIdDataSet::iterator it = userdata->wholistsme.begin(); it != userdata->wholistsme.end(); ++it)
378 		{
379 			callerid_data *dat = *(it);
380 
381 			// Find me on their callerid list
382 			if (!dat->accepting.erase(who))
383 				ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (5)");
384 		}
385 
386 		userdata->wholistsme.clear();
387 	}
388 
389 public:
ModuleCallerID()390 	ModuleCallerID()
391 		: CTCTags::EventListener(this)
392 		, cmd(this)
393 		, api(this, cmd.extInfo)
394 		, myumode(this, "callerid", 'g')
395 	{
396 	}
397 
GetVersion()398 	Version GetVersion() CXX11_OVERRIDE
399 	{
400 		return Version("Provides user mode g (callerid) which allows users to require that other users are on their whitelist before messaging them.", VF_COMMON | VF_VENDOR);
401 	}
402 
On005Numeric(std::map<std::string,std::string> & tokens)403 	void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
404 	{
405 		tokens["ACCEPT"] = ConvToStr(cmd.maxaccepts);
406 		tokens["CALLERID"] = ConvToStr(myumode.GetModeChar());
407 	}
408 
HandleMessage(User * user,const MessageTarget & target)409 	ModResult HandleMessage(User* user, const MessageTarget& target)
410 	{
411 		if (!IS_LOCAL(user) || target.type != MessageTarget::TYPE_USER)
412 			return MOD_RES_PASSTHRU;
413 
414 		User* dest = target.Get<User>();
415 		if (!dest->IsModeSet(myumode) || (user == dest))
416 			return MOD_RES_PASSTHRU;
417 
418 		if (user->HasPrivPermission("users/ignore-callerid"))
419 			return MOD_RES_PASSTHRU;
420 
421 		callerid_data* dat = cmd.extInfo.get(dest, true);
422 		if (!dat->accepting.count(user))
423 		{
424 			time_t now = ServerInstance->Time();
425 			/* +g and *not* accepted */
426 			user->WriteNumeric(ERR_TARGUMODEG, dest->nick, "is in +g mode (server-side ignore).");
427 			if (now > (dat->lastnotify + (time_t)notify_cooldown))
428 			{
429 				user->WriteNumeric(RPL_TARGNOTIFY, dest->nick, "has been informed that you messaged them.");
430 				dest->WriteRemoteNumeric(RPL_UMODEGMSG, user->nick, InspIRCd::Format("%s@%s", user->ident.c_str(), user->GetDisplayedHost().c_str()), InspIRCd::Format("is messaging you, and you have user mode +g set. Use /ACCEPT +%s to allow.",
431 						user->nick.c_str()));
432 				dat->lastnotify = now;
433 			}
434 			return MOD_RES_DENY;
435 		}
436 		return MOD_RES_PASSTHRU;
437 	}
438 
OnUserPreMessage(User * user,const MessageTarget & target,MessageDetails & details)439 	ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
440 	{
441 		return HandleMessage(user, target);
442 	}
443 
OnUserPreTagMessage(User * user,const MessageTarget & target,CTCTags::TagMessageDetails & details)444 	ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
445 	{
446 		return HandleMessage(user, target);
447 	}
448 
OnUserPostNick(User * user,const std::string & oldnick)449 	void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE
450 	{
451 		if (!tracknick)
452 			RemoveFromAllAccepts(user);
453 	}
454 
OnUserQuit(User * user,const std::string & message,const std::string & oper_message)455 	void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE
456 	{
457 		RemoveFromAllAccepts(user);
458 	}
459 
ReadConfig(ConfigStatus & status)460 	void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
461 	{
462 		ConfigTag* tag = ServerInstance->Config->ConfValue("callerid");
463 		cmd.maxaccepts = tag->getUInt("maxaccepts", 30);
464 		tracknick = tag->getBool("tracknick");
465 		notify_cooldown = tag->getDuration("cooldown", 60);
466 	}
467 
Prioritize()468 	void Prioritize() CXX11_OVERRIDE
469 	{
470 		// Want to be after modules like silence or services_account
471 		ServerInstance->Modules->SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST);
472 	}
473 };
474 
475 MODULE_INIT(ModuleCallerID)
476