]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_dccallow.cpp
Make more modules rehash atomically (#1535)
[user/henk/code/inspircd.git] / src / modules / m_dccallow.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2008 John Brooks <john.brooks@dereferenced.net>
6  *   Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
7  *   Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
8  *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
9  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
10  *   Copyright (C) 2006 Jamie ??? <???@???>
11  *
12  * This file is part of InspIRCd.  InspIRCd is free software: you can
13  * redistribute it and/or modify it under the terms of the GNU General Public
14  * License as published by the Free Software Foundation, version 2.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25
26 #include "inspircd.h"
27
28 enum
29 {
30         // From ircd-ratbox.
31         RPL_HELPSTART = 704,
32         RPL_HELPTXT = 705,
33         RPL_ENDOFHELP = 706,
34
35         // InspIRCd-specific?
36         RPL_DCCALLOWSTART = 990,
37         RPL_DCCALLOWLIST = 991,
38         RPL_DCCALLOWEND = 992,
39         RPL_DCCALLOWTIMED = 993,
40         RPL_DCCALLOWPERMANENT = 994,
41         RPL_DCCALLOWREMOVED = 995,
42         ERR_DCCALLOWINVALID = 996,
43         RPL_DCCALLOWEXPIRED = 997,
44         ERR_UNKNOWNDCCALLOWCMD = 998
45 };
46
47 static const char* const helptext[] =
48 {
49         "You may allow DCCs from specific users by specifying a",
50         "DCC allow for the user you want to receive DCCs from.",
51         "For example, to allow the user Brain to send you inspircd.exe",
52         "you would type:",
53         "/DCCALLOW +Brain",
54         "Brain would then be able to send you files. They would have to",
55         "resend the file again if the server gave them an error message",
56         "before you added them to your DCCALLOW list.",
57         "DCCALLOW entries will be temporary by default, if you want to add",
58         "them to your DCCALLOW list until you leave IRC, type:",
59         "/DCCALLOW +Brain 0",
60         "To remove the user from your DCCALLOW list, type:",
61         "/DCCALLOW -Brain",
62         "To see the users in your DCCALLOW list, type:",
63         "/DCCALLOW LIST",
64         "NOTE: If the user leaves IRC or changes their nickname",
65         "  they will be removed from your DCCALLOW list.",
66         "  your DCCALLOW list will be deleted when you leave IRC."
67 };
68
69 class BannedFileList
70 {
71  public:
72         std::string filemask;
73         std::string action;
74 };
75
76 class DCCAllow
77 {
78  public:
79         std::string nickname;
80         std::string hostmask;
81         time_t set_on;
82         unsigned long length;
83
84         DCCAllow() { }
85
86         DCCAllow(const std::string& nick, const std::string& hm, time_t so, unsigned long ln)
87                 : nickname(nick)
88                 , hostmask(hm)
89                 , set_on(so)
90                 , length(ln)
91         {
92         }
93 };
94
95 typedef std::vector<User *> userlist;
96 userlist ul;
97 typedef std::vector<DCCAllow> dccallowlist;
98 dccallowlist* dl;
99 typedef std::vector<BannedFileList> bannedfilelist;
100 bannedfilelist bfl;
101 typedef SimpleExtItem<dccallowlist> DCCAllowExt;
102
103 class CommandDccallow : public Command
104 {
105         DCCAllowExt& ext;
106
107  public:
108         unsigned int maxentries;
109         unsigned long defaultlength;
110         CommandDccallow(Module* parent, DCCAllowExt& Ext)
111                 : Command(parent, "DCCALLOW", 0)
112                 , ext(Ext)
113         {
114                 syntax = "[(+|-)<nick> [<time>]]|[LIST|HELP]";
115                 /* XXX we need to fix this so it can work with translation stuff (i.e. move +- into a seperate param */
116         }
117
118         CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
119         {
120                 /* syntax: DCCALLOW [+|-]<nick> (<time>) */
121                 if (!parameters.size())
122                 {
123                         // display current DCCALLOW list
124                         DisplayDCCAllowList(user);
125                         return CMD_FAILURE;
126                 }
127                 else if (parameters.size() > 0)
128                 {
129                         char action = *parameters[0].c_str();
130
131                         // if they didn't specify an action, this is probably a command
132                         if (action != '+' && action != '-')
133                         {
134                                 if (!strcasecmp(parameters[0].c_str(), "LIST"))
135                                 {
136                                         // list current DCCALLOW list
137                                         DisplayDCCAllowList(user);
138                                         return CMD_FAILURE;
139                                 }
140                                 else if (!strcasecmp(parameters[0].c_str(), "HELP"))
141                                 {
142                                         // display help
143                                         DisplayHelp(user);
144                                         return CMD_FAILURE;
145                                 }
146                                 else
147                                 {
148                                         user->WriteNumeric(ERR_UNKNOWNDCCALLOWCMD, "DCCALLOW command not understood. For help on DCCALLOW, type /DCCALLOW HELP");
149                                         return CMD_FAILURE;
150                                 }
151                         }
152
153                         std::string nick(parameters[0], 1);
154                         User *target = ServerInstance->FindNickOnly(nick);
155
156                         if ((target) && (!target->quitting) && (target->registered == REG_ALL))
157                         {
158
159                                 if (action == '-')
160                                 {
161                                         // check if it contains any entries
162                                         dl = ext.get(user);
163                                         if (dl)
164                                         {
165                                                 for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i)
166                                                 {
167                                                         // search through list
168                                                         if (i->nickname == target->nick)
169                                                         {
170                                                                 dl->erase(i);
171                                                                 user->WriteNumeric(RPL_DCCALLOWREMOVED, user->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", target->nick.c_str()));
172                                                                 break;
173                                                         }
174                                                 }
175                                         }
176                                 }
177                                 else if (action == '+')
178                                 {
179                                         if (target == user)
180                                         {
181                                                 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "You cannot add yourself to your own DCCALLOW list!");
182                                                 return CMD_FAILURE;
183                                         }
184
185                                         dl = ext.get(user);
186                                         if (!dl)
187                                         {
188                                                 dl = new dccallowlist;
189                                                 ext.set(user, dl);
190                                                 // add this user to the userlist
191                                                 ul.push_back(user);
192                                         }
193
194                                         if (dl->size() >= maxentries)
195                                         {
196                                                 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "Too many nicks on DCCALLOW list");
197                                                 return CMD_FAILURE;
198                                         }
199
200                                         for (dccallowlist::const_iterator k = dl->begin(); k != dl->end(); ++k)
201                                         {
202                                                 if (k->nickname == target->nick)
203                                                 {
204                                                         user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, InspIRCd::Format("%s is already on your DCCALLOW list", target->nick.c_str()));
205                                                         return CMD_FAILURE;
206                                                 }
207                                         }
208
209                                         std::string mask = target->nick+"!"+target->ident+"@"+target->GetDisplayedHost();
210                                         unsigned long length;
211                                         if (parameters.size() < 2)
212                                         {
213                                                 length = defaultlength;
214                                         }
215                                         else if (!InspIRCd::IsValidDuration(parameters[1]))
216                                         {
217                                                 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, InspIRCd::Format("%s is not a valid DCCALLOW duration", parameters[1].c_str()));
218                                                 return CMD_FAILURE;
219                                         }
220                                         else
221                                         {
222                                                 length = InspIRCd::Duration(parameters[1]);
223                                         }
224
225                                         if (!InspIRCd::IsValidMask(mask))
226                                         {
227                                                 return CMD_FAILURE;
228                                         }
229
230                                         dl->push_back(DCCAllow(target->nick, mask, ServerInstance->Time(), length));
231
232                                         if (length > 0)
233                                         {
234                                                 user->WriteNumeric(RPL_DCCALLOWTIMED, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for %ld seconds", target->nick.c_str(), length));
235                                         }
236                                         else
237                                         {
238                                                 user->WriteNumeric(RPL_DCCALLOWPERMANENT, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for this session", target->nick.c_str()));
239                                         }
240
241                                         /* route it. */
242                                         return CMD_SUCCESS;
243                                 }
244                         }
245                         else
246                         {
247                                 // nick doesn't exist
248                                 user->WriteNumeric(Numerics::NoSuchNick(nick));
249                                 return CMD_FAILURE;
250                         }
251                 }
252                 return CMD_FAILURE;
253         }
254
255         RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
256         {
257                 return ROUTE_BROADCAST;
258         }
259
260         void DisplayHelp(User* user)
261         {
262                 user->WriteNumeric(RPL_HELPSTART, "*", "DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP]");
263                 for (size_t i = 0; i < sizeof(helptext)/sizeof(helptext[0]); i++)
264                         user->WriteNumeric(RPL_HELPTXT, "*", helptext[i]);
265                 user->WriteNumeric(RPL_ENDOFHELP, "*", "End of DCCALLOW HELP");
266
267                 LocalUser* localuser = IS_LOCAL(user);
268                 if (localuser)
269                         localuser->CommandFloodPenalty += 4000;
270         }
271
272         void DisplayDCCAllowList(User* user)
273         {
274                  // display current DCCALLOW list
275                 user->WriteNumeric(RPL_DCCALLOWSTART, "Users on your DCCALLOW list:");
276
277                 dl = ext.get(user);
278                 if (dl)
279                 {
280                         for (dccallowlist::const_iterator c = dl->begin(); c != dl->end(); ++c)
281                         {
282                                 user->WriteNumeric(RPL_DCCALLOWLIST, user->nick, InspIRCd::Format("%s (%s)", c->nickname.c_str(), c->hostmask.c_str()));
283                         }
284                 }
285
286                 user->WriteNumeric(RPL_DCCALLOWEND, "End of DCCALLOW list");
287         }
288
289 };
290
291 class ModuleDCCAllow : public Module
292 {
293         DCCAllowExt ext;
294         CommandDccallow cmd;
295         bool blockchat;
296         std::string defaultaction;
297
298  public:
299         ModuleDCCAllow()
300                 : ext("dccallow", ExtensionItem::EXT_USER, this)
301                 , cmd(this, ext)
302                 , blockchat(false)
303         {
304         }
305
306         void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE
307         {
308                 dccallowlist* udl = ext.get(user);
309
310                 // remove their DCCALLOW list if they have one
311                 if (udl)
312                         stdalgo::erase(ul, user);
313
314                 // remove them from any DCCALLOW lists
315                 // they are currently on
316                 RemoveNick(user);
317         }
318
319         void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE
320         {
321                 RemoveNick(user);
322         }
323
324         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
325         {
326                 if (!IS_LOCAL(user))
327                         return MOD_RES_PASSTHRU;
328
329                 if (target.type == MessageTarget::TYPE_USER)
330                 {
331                         User* u = target.Get<User>();
332
333                         /* Always allow a user to dcc themselves (although... why?) */
334                         if (user == u)
335                                 return MOD_RES_PASSTHRU;
336
337                         if ((details.text.length()) && (details.text[0] == '\1'))
338                         {
339                                 Expire();
340
341                                 // :jamie!jamie@test-D4457903BA652E0F.silverdream.org PRIVMSG eimaj :DCC SEND m_dnsbl.cpp 3232235786 52650 9676
342                                 // :jamie!jamie@test-D4457903BA652E0F.silverdream.org PRIVMSG eimaj :VERSION
343
344                                 if (strncmp(details.text.c_str(), "\1DCC ", 5) == 0)
345                                 {
346                                         dl = ext.get(u);
347                                         if (dl && dl->size())
348                                         {
349                                                 for (dccallowlist::const_iterator iter = dl->begin(); iter != dl->end(); ++iter)
350                                                         if (InspIRCd::Match(user->GetFullHost(), iter->hostmask))
351                                                                 return MOD_RES_PASSTHRU;
352                                         }
353
354                                         std::string buf = details.text.substr(5);
355                                         size_t s = buf.find(' ');
356                                         if (s == std::string::npos)
357                                                 return MOD_RES_PASSTHRU;
358
359                                         const std::string type = buf.substr(0, s);
360
361                                         if (stdalgo::string::equalsci(type, "SEND"))
362                                         {
363                                                 size_t first;
364
365                                                 buf = buf.substr(s + 1);
366
367                                                 if (!buf.empty() && buf[0] == '"')
368                                                 {
369                                                         s = buf.find('"', 1);
370
371                                                         if (s == std::string::npos || s <= 1)
372                                                                 return MOD_RES_PASSTHRU;
373
374                                                         --s;
375                                                         first = 1;
376                                                 }
377                                                 else
378                                                 {
379                                                         s = buf.find(' ');
380                                                         first = 0;
381                                                 }
382
383                                                 if (s == std::string::npos)
384                                                         return MOD_RES_PASSTHRU;
385
386                                                 std::string filename = buf.substr(first, s);
387
388                                                 bool found = false;
389                                                 for (unsigned int i = 0; i < bfl.size(); i++)
390                                                 {
391                                                         if (InspIRCd::Match(filename, bfl[i].filemask, ascii_case_insensitive_map))
392                                                         {
393                                                                 /* We have a matching badfile entry, override whatever the default action is */
394                                                                 if (stdalgo::string::equalsci(bfl[i].action, "allow"))
395                                                                         return MOD_RES_PASSTHRU;
396                                                                 else
397                                                                 {
398                                                                         found = true;
399                                                                         break;
400                                                                 }
401                                                         }
402                                                 }
403
404                                                 /* only follow the default action if no badfile matches were found above */
405                                                 if ((!found) && (defaultaction == "allow"))
406                                                         return MOD_RES_PASSTHRU;
407
408                                                 user->WriteNotice("The user " + u->nick + " is not accepting DCC SENDs from you. Your file " + filename + " was not sent.");
409                                                 u->WriteNotice(user->nick + " (" + user->ident + "@" + user->GetDisplayedHost() + ") attempted to send you a file named " + filename + ", which was blocked.");
410                                                 u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.");
411                                                 return MOD_RES_DENY;
412                                         }
413                                         else if ((blockchat) && (stdalgo::string::equalsci(type, "CHAT")))
414                                         {
415                                                 user->WriteNotice("The user " + u->nick + " is not accepting DCC CHAT requests from you.");
416                                                 u->WriteNotice(user->nick + " (" + user->ident + "@" + user->GetDisplayedHost() + ") attempted to initiate a DCC CHAT session, which was blocked.");
417                                                 u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.");
418                                                 return MOD_RES_DENY;
419                                         }
420                                 }
421                         }
422                 }
423                 return MOD_RES_PASSTHRU;
424         }
425
426         void Expire()
427         {
428                 for (userlist::iterator iter = ul.begin(); iter != ul.end();)
429                 {
430                         User* u = (User*)(*iter);
431                         dl = ext.get(u);
432                         if (dl)
433                         {
434                                 if (dl->size())
435                                 {
436                                         dccallowlist::iterator iter2 = dl->begin();
437                                         while (iter2 != dl->end())
438                                         {
439                                                 time_t expires = iter2->set_on + iter2->length;
440                                                 if (iter2->length != 0 && expires <= ServerInstance->Time())
441                                                 {
442                                                         u->WriteNumeric(RPL_DCCALLOWEXPIRED, u->nick, InspIRCd::Format("DCCALLOW entry for %s has expired", iter2->nickname.c_str()));
443                                                         iter2 = dl->erase(iter2);
444                                                 }
445                                                 else
446                                                 {
447                                                         ++iter2;
448                                                 }
449                                         }
450                                 }
451                                 ++iter;
452                         }
453                         else
454                         {
455                                 iter = ul.erase(iter);
456                         }
457                 }
458         }
459
460         void RemoveNick(User* user)
461         {
462                 /* Iterate through all DCCALLOW lists and remove user */
463                 for (userlist::iterator iter = ul.begin(); iter != ul.end();)
464                 {
465                         User *u = (User*)(*iter);
466                         dl = ext.get(u);
467                         if (dl)
468                         {
469                                 if (dl->size())
470                                 {
471                                         for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i)
472                                         {
473                                                 if (i->nickname == user->nick)
474                                                 {
475
476                                                         u->WriteNotice(i->nickname + " left the network or changed their nickname and has been removed from your DCCALLOW list");
477                                                         u->WriteNumeric(RPL_DCCALLOWREMOVED, u->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", i->nickname.c_str()));
478                                                         dl->erase(i);
479                                                         break;
480                                                 }
481                                         }
482                                 }
483                                 ++iter;
484                         }
485                         else
486                         {
487                                 iter = ul.erase(iter);
488                         }
489                 }
490         }
491
492         void RemoveFromUserlist(User *user)
493         {
494                 // remove user from userlist
495                 for (userlist::iterator j = ul.begin(); j != ul.end(); ++j)
496                 {
497                         User* u = (User*)(*j);
498                         if (u == user)
499                         {
500                                 ul.erase(j);
501                                 break;
502                         }
503                 }
504         }
505
506         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
507         {
508                 bannedfilelist newbfl;
509                 ConfigTagList tags = ServerInstance->Config->ConfTags("banfile");
510                 for (ConfigIter i = tags.first; i != tags.second; ++i)
511                 {
512                         BannedFileList bf;
513                         bf.filemask = i->second->getString("pattern");
514                         bf.action = i->second->getString("action");
515                         newbfl.push_back(bf);
516                 }
517                 bfl.swap(newbfl);
518
519                 ConfigTag* tag = ServerInstance->Config->ConfValue("dccallow");
520                 cmd.maxentries = tag->getUInt("maxentries", 20);
521                 cmd.defaultlength = tag->getDuration("length", 0);
522                 blockchat = tag->getBool("blockchat");
523                 defaultaction = tag->getString("action");
524         }
525
526         Version GetVersion() CXX11_OVERRIDE
527         {
528                 return Version("Provides support for the /DCCALLOW command", VF_COMMON | VF_VENDOR);
529         }
530 };
531
532 MODULE_INIT(ModuleDCCAllow)