]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_callerid.cpp
4c490a3342c3f8268e91edbb2d421799ebde9112
[user/henk/code/inspircd.git] / src / modules / m_callerid.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include <set>
16 #include <sstream>
17 #include <algorithm>
18
19 /* $ModDesc: Implementation of callerid (umode +g & /accept, ala hybrid etc) */
20
21 class callerid_data : public classbase
22 {
23  public:
24         time_t lastnotify;
25
26         /** Users I accept messages from
27          */
28         std::set<User*> accepting;
29
30         /** Users who list me as accepted
31          */
32         std::list<callerid_data *> wholistsme;
33
34         callerid_data() : lastnotify(0) { }
35         callerid_data(const std::string& str, InspIRCd* ServerInstance)
36         {
37                 irc::commasepstream s(str);
38                 std::string tok;
39                 if (s.GetToken(tok))
40                 {
41                         lastnotify = ConvToInt(tok);
42                 }
43                 while (s.GetToken(tok))
44                 {
45                         if (tok.empty())
46                         {
47                                 continue;
48                         }
49
50                         User *u = ServerInstance->FindNick(tok);
51                         if (!u)
52                         {
53                                 continue;
54                         }
55                         accepting.insert(u);
56                 }
57         }
58
59         std::string ToString(Module* proto) const
60         {
61                 std::ostringstream oss;
62                 oss << lastnotify;
63                 for (std::set<User*>::const_iterator i = accepting.begin(); i != accepting.end(); ++i)
64                 {
65                         // Encode UIDs.
66                         oss << "," << proto->ProtoTranslate(*i);
67                 }
68                 oss << std::ends;
69                 return oss.str();
70         }
71 };
72
73 /** Retrieves the callerid information for a given user record
74  * @param who The user to retrieve callerid information for
75  * @param extend true to add callerid information if it doesn't already exist, false to return NULL if it doesn't exist
76  * @return NULL if extend is false and it doesn't exist, a callerid_data instance otherwise.
77  */
78 callerid_data* GetData(User* who, bool extend = true)
79 {
80         callerid_data* dat;
81         if (who->GetExt("callerid_data", dat))
82                 return dat;
83         else
84         {
85                 if (extend)
86                 {
87                         dat = new callerid_data;
88                         who->Extend("callerid_data", dat);
89                         return dat;
90                 }
91                 else
92                         return NULL;
93         }
94 }
95
96 void RemoveData(User* who)
97 {
98         callerid_data* dat;
99         who->GetExt("callerid_data", dat);
100
101         if (!dat)
102                 return;
103
104         // We need to walk the list of users on our accept list, and remove ourselves from their wholistsme.
105         for (std::set<User *>::iterator it = dat->accepting.begin(); it != dat->accepting.end(); it++)
106         {
107                 callerid_data *targ = GetData(*it, false);
108
109                 if (!targ)
110                         continue; // shouldn't happen, but oh well.
111
112                 for (std::list<callerid_data *>::iterator it2 = targ->wholistsme.begin(); it2 != targ->wholistsme.end(); it2++)
113                 {
114                         if (*it2 == dat)
115                         {
116                                 targ->wholistsme.erase(it2);
117                                 break;
118                         }
119                 }
120         }
121
122         who->Shrink("callerid_data");
123         delete dat;
124 }
125
126
127 class User_g : public SimpleUserModeHandler
128 {
129 public:
130         User_g(InspIRCd* Instance, Module* Creator) : SimpleUserModeHandler(Instance, Creator, 'g') { }
131 };
132
133 class CommandAccept : public Command
134 {
135 public:
136         unsigned int maxaccepts;
137         CommandAccept(InspIRCd* Instance, Module* Creator) : Command(Instance, Creator, "ACCEPT", 0, 1)
138         {
139                 syntax = "{[+|-]<nicks>}|*}";
140                 TRANSLATE2(TR_CUSTOM, TR_END);
141         }
142
143         virtual void EncodeParameter(std::string& parameter, int index)
144         {
145                 if (index != 0)
146                         return;
147                 std::string out = "";
148                 irc::commasepstream nicks(parameter);
149                 std::string tok;
150                 while (nicks.GetToken(tok))
151                 {
152                         if (tok == "*")
153                         {
154                                 continue; // Drop list requests, since remote servers ignore them anyway.
155                         }
156                         if (!out.empty())
157                                 out.append(",");
158                         bool dash = false;
159                         if (tok[0] == '-')
160                         {
161                                 dash = true;
162                                 tok.erase(0, 1); // Remove the dash.
163                         }
164                         User* u = ServerInstance->FindNick(tok);
165                         if (u)
166                         {
167                                 if (dash)
168                                         out.append("-");
169                                 out.append(u->uuid);
170                         }
171                         else
172                         {
173                                 if (dash)
174                                         out.append("-");
175                                 out.append(tok);
176                         }
177                 }
178                 parameter = out;
179         }
180
181         /** Will take any number of nicks (up to MaxTargets), which can be seperated by commas.
182          * - in front of any nick removes, and an * lists. This effectively means you can do:
183          * /accept nick1,nick2,nick3,*
184          * to add 3 nicks and then show your list
185          */
186         CmdResult Handle(const std::vector<std::string> &parameters, User* user)
187         {
188                 if (ServerInstance->Parser->LoopCall(user, this, parameters, 0))
189                         return CMD_SUCCESS;
190                 /* Even if callerid mode is not set, we let them manage their ACCEPT list so that if they go +g they can
191                  * have a list already setup. */
192
193                 std::string tok = parameters[0];
194
195                 if (tok == "*")
196                 {
197                         if (IS_LOCAL(user))
198                                 ListAccept(user);
199                         return CMD_LOCALONLY;
200                 }
201                 else if (tok[0] == '-')
202                 {
203                         User* whotoremove = ServerInstance->FindNick(tok.substr(1));
204                         if (whotoremove)
205                                 return (RemoveAccept(user, whotoremove, false) ? CMD_SUCCESS : CMD_FAILURE);
206                         else
207                                 return CMD_FAILURE;
208                 }
209                 else
210                 {
211                         User* whotoadd = ServerInstance->FindNick(tok[0] == '+' ? tok.substr(1) : tok);
212                         if (whotoadd)
213                                 return (AddAccept(user, whotoadd, false) ? CMD_SUCCESS : CMD_FAILURE);
214                         else
215                         {
216                                 user->WriteNumeric(401, "%s %s :No such nick/channel", user->nick.c_str(), tok.c_str());
217                                 return CMD_FAILURE;
218                         }
219                 }
220         }
221
222         void ListAccept(User* user)
223         {
224                 callerid_data* dat = GetData(user, false);
225                 if (dat)
226                 {
227                         for (std::set<User*>::iterator i = dat->accepting.begin(); i != dat->accepting.end(); ++i)
228                                 user->WriteNumeric(281, "%s %s", user->nick.c_str(), (*i)->nick.c_str());
229                 }
230                 user->WriteNumeric(282, "%s :End of ACCEPT list", user->nick.c_str());
231         }
232
233         bool AddAccept(User* user, User* whotoadd, bool quiet)
234         {
235                 // Add this user to my accept list first, so look me up..
236                 callerid_data* dat = GetData(user, true);
237                 if (dat->accepting.size() >= maxaccepts)
238                 {
239                         if (!quiet)
240                                 user->WriteNumeric(456, "%s :Accept list is full (limit is %d)", user->nick.c_str(), maxaccepts);
241
242                         return false;
243                 }
244                 if (!dat->accepting.insert(whotoadd).second)
245                 {
246                         if (!quiet)
247                                 user->WriteNumeric(457, "%s %s :is already on your accept list", user->nick.c_str(), whotoadd->nick.c_str());
248
249                         return false;
250                 }
251
252                 // Now, look them up, and add me to their list
253                 callerid_data *targ = GetData(whotoadd, true);
254                 targ->wholistsme.push_back(dat);
255
256                 user->WriteServ("NOTICE %s :%s is now on your accept list", user->nick.c_str(), whotoadd->nick.c_str());
257                 return true;
258         }
259
260         bool RemoveAccept(User* user, User* whotoremove, bool quiet)
261         {
262                 // Remove them from my list, so look up my list..
263                 callerid_data* dat = GetData(user, false);
264                 if (!dat)
265                 {
266                         if (!quiet)
267                                 user->WriteNumeric(458, "%s %s :is not on your accept list", user->nick.c_str(), whotoremove->nick.c_str());
268
269                         return false;
270                 }
271                 std::set<User*>::iterator i = dat->accepting.find(whotoremove);
272                 if (i == dat->accepting.end())
273                 {
274                         if (!quiet)
275                                 user->WriteNumeric(458, "%s %s :is not on your accept list", user->nick.c_str(), whotoremove->nick.c_str());
276
277                         return false;
278                 }
279
280                 dat->accepting.erase(i);
281
282                 // Look up their list to remove me.
283                 callerid_data *dat2 = GetData(whotoremove, false);
284                 if (!dat2)
285                 {
286                         // How the fuck is this possible.
287                         return false;
288                 }
289
290                 for (std::list<callerid_data *>::iterator it = dat2->wholistsme.begin(); it != dat2->wholistsme.end(); it++)
291                 {
292                         // Found me!
293                         if (*it == dat)
294                         {
295                                 dat2->wholistsme.erase(it);
296                                 break;
297                         }
298                 }
299
300                 user->WriteServ("NOTICE %s :%s is no longer on your accept list", user->nick.c_str(), whotoremove->nick.c_str());
301                 return true;
302         }
303 };
304
305 class ModuleCallerID : public Module
306 {
307 private:
308         CommandAccept mycommand;
309         User_g myumode;
310
311         // Configuration variables:
312         bool operoverride; // Operators can override callerid.
313         bool tracknick; // Allow ACCEPT entries to update with nick changes.
314         unsigned int notify_cooldown; // Seconds between notifications.
315
316         /** Removes a user from all accept lists
317          * @param who The user to remove from accepts
318          */
319         void RemoveFromAllAccepts(User* who)
320         {
321                 // First, find the list of people who have me on accept
322                 callerid_data *userdata = GetData(who, false);
323                 if (!userdata)
324                         return;
325
326                 // Iterate over the list of people who accept me, and remove all entries
327                 for (std::list<callerid_data *>::iterator it = userdata->wholistsme.begin(); it != userdata->wholistsme.end(); it++)
328                 {
329                         callerid_data *dat = *(it);
330
331                         // Find me on their callerid list
332                         std::set<User *>::iterator it2 = dat->accepting.find(who);
333
334                         if (it2 != dat->accepting.end())
335                                 dat->accepting.erase(it2);
336                 }
337
338                 userdata->wholistsme.clear();
339         }
340
341 public:
342         ModuleCallerID(InspIRCd* Me) : Module(Me), mycommand(Me, this), myumode(Me, this)
343         {
344                 OnRehash(NULL);
345
346                 if (!ServerInstance->Modes->AddMode(&myumode))
347                         throw ModuleException("Could not add usermode +g");
348
349                 ServerInstance->AddCommand(&mycommand);
350
351                 Implementation eventlist[] = { I_OnRehash, I_OnUserPreNick, I_OnUserQuit, I_On005Numeric, I_OnUserPreNotice, I_OnUserPreMessage, I_OnCleanup };
352                 ServerInstance->Modules->Attach(eventlist, this, 7);
353         }
354
355         virtual ~ModuleCallerID()
356         {
357                 ServerInstance->Modes->DelMode(&myumode);
358         }
359
360         virtual Version GetVersion()
361         {
362                 return Version("$Id$", VF_COMMON | VF_VENDOR, API_VERSION);
363         }
364
365         virtual void On005Numeric(std::string& output)
366         {
367                 output += " CALLERID=g";
368         }
369
370         ModResult PreText(User* user, User* dest, std::string& text, bool notice)
371         {
372                 if (!dest->IsModeSet('g'))
373                         return MOD_RES_PASSTHRU;
374
375                 if (operoverride && IS_OPER(user))
376                         return MOD_RES_PASSTHRU;
377
378                 callerid_data* dat = GetData(dest, true);
379                 std::set<User*>::iterator i = dat->accepting.find(user);
380
381                 if (i == dat->accepting.end())
382                 {
383                         time_t now = ServerInstance->Time();
384                         /* +g and *not* accepted */
385                         user->WriteNumeric(716, "%s %s :is in +g mode (server-side ignore).", user->nick.c_str(), dest->nick.c_str());
386                         if (now > (dat->lastnotify + (time_t)notify_cooldown))
387                         {
388                                 user->WriteNumeric(717, "%s %s :has been informed that you messaged them.", user->nick.c_str(), dest->nick.c_str());
389                                 if (IS_LOCAL(dest))
390                                 {
391                                         dest->WriteNumeric(718, "%s %s %s@%s :is messaging you, and you have umode +g. Use /ACCEPT +%s to allow.", dest->nick.c_str(), user->nick.c_str(), user->ident.c_str(), user->dhost.c_str(), user->nick.c_str());
392                                 }
393                                 else
394                                 {
395                                         ServerInstance->PI->PushToClient(dest, std::string("::") + ServerInstance->Config->ServerName + " 718 " + dest->nick + " " + user->nick + " " + user->ident + "@" + user->dhost + " :is messaging you,  and you have umode +g. Use /ACCEPT +" + user->nick + " to allow.");
396                                 }
397                                 dat->lastnotify = now;
398                         }
399                         return MOD_RES_DENY;
400                 }
401                 return MOD_RES_PASSTHRU;
402         }
403
404         virtual ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList &exempt_list)
405         {
406                 if (IS_LOCAL(user) && target_type == TYPE_USER)
407                         return PreText(user, (User*)dest, text, true);
408
409                 return MOD_RES_PASSTHRU;
410         }
411
412         virtual ModResult OnUserPreNotice(User* user, void* dest, int target_type, std::string& text, char status, CUList &exempt_list)
413         {
414                 if (IS_LOCAL(user) && target_type == TYPE_USER)
415                         return PreText(user, (User*)dest, text, true);
416
417                 return MOD_RES_PASSTHRU;
418         }
419
420         virtual void OnCleanup(int type, void* item)
421         {
422                 if (type != TYPE_USER)
423                         return;
424
425                 User* u = (User*)item;
426                 /* Cleanup only happens on unload (before dtor), so keep this O(n) instead of O(n^2) which deferring to OnUserQuit would do.  */
427                 RemoveData(u);
428         }
429
430         virtual void OnSyncUser(User* user, Module* proto, void* opaque)
431         {
432                 callerid_data* dat = GetData(user, false);
433                 if (dat)
434                 {
435                         std::string str = dat->ToString(proto);
436                         proto->ProtoSendMetaData(opaque, user, "callerid_data", str);
437                 }
438         }
439
440         virtual void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string& extdata)
441         {
442                 User* u = dynamic_cast<User*>(target);
443                 if (u && extname == "callerid_data")
444                 {
445                         callerid_data* dat = new callerid_data(extdata, ServerInstance);
446                         u->Extend("callerid_data", dat);
447                 }
448         }
449
450         virtual ModResult OnUserPreNick(User* user, const std::string& newnick)
451         {
452                 if (!tracknick)
453                         RemoveFromAllAccepts(user);
454                 return MOD_RES_PASSTHRU;
455         }
456
457         virtual void OnUserQuit(User* user, const std::string& message, const std::string& oper_message)
458         {
459                 RemoveFromAllAccepts(user);
460                 RemoveData(user);
461         }
462
463         virtual void OnRehash(User* user)
464         {
465                 ConfigReader Conf(ServerInstance);
466                 mycommand.maxaccepts = Conf.ReadInteger("callerid", "maxaccepts", "16", 0, true);
467                 operoverride = Conf.ReadFlag("callerid", "operoverride", "0", 0);
468                 tracknick = Conf.ReadFlag("callerid", "tracknick", "0", 0);
469                 notify_cooldown = Conf.ReadInteger("callerid", "cooldown", "60", 0, true);
470         }
471 };
472
473 MODULE_INIT(ModuleCallerID)
474
475