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