]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_dccallow.cpp
m_chanfilter: Apply filters to part messages (#1702)
[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. 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
102 class DCCAllowExt : public SimpleExtItem<dccallowlist>
103 {
104  public:
105         unsigned int maxentries;
106
107         DCCAllowExt(Module* Creator)
108                 : SimpleExtItem<dccallowlist>("dccallow", ExtensionItem::EXT_USER, Creator)
109         {
110         }
111
112         void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE
113         {
114                 LocalUser* user = IS_LOCAL(static_cast<User*>(container));
115                 if (!user)
116                         return;
117
118                 // Remove the old list and create a new one.
119                 unset(user);
120                 dccallowlist* list = new dccallowlist();
121
122                 irc::spacesepstream ts(value);
123                 while (!ts.StreamEnd())
124                 {
125                         // Check we have space for another entry.
126                         if (list->size() >= maxentries)
127                         {
128                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Oversized DCC allow list received for %s: %s",
129                                         user->uuid.c_str(), value.c_str());
130                                 delete list;
131                                 return;
132                         }
133
134                         // Extract the fields.
135                         DCCAllow dccallow;
136                         if (!ts.GetToken(dccallow.nickname) ||
137                                 !ts.GetToken(dccallow.hostmask) ||
138                                 !ts.GetNumericToken(dccallow.set_on) ||
139                                 !ts.GetNumericToken(dccallow.length))
140                         {
141                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Malformed DCC allow list received for %s: %s",
142                                         user->uuid.c_str(), value.c_str());
143                                 delete list;
144                                 return;
145                         }
146
147                         // Store the DCC allow entry.
148                         list->push_back(dccallow);
149                 }
150
151         }
152
153         std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE
154         {
155                 dccallowlist* list = static_cast<dccallowlist*>(item);
156                 std::string buf;
157                 for (dccallowlist::const_iterator iter = list->begin(); iter != list->end(); ++iter)
158                 {
159                         if (iter != list->begin())
160                                 buf.push_back(' ');
161
162                         buf.append(iter->nickname);
163                         buf.push_back(' ');
164                         buf.append(iter->hostmask);
165                         buf.push_back(' ');
166                         buf.append(ConvToStr(iter->set_on));
167                         buf.push_back(' ');
168                         buf.append(ConvToStr(iter->length));
169                 }
170                 return buf;
171         }
172 };
173
174 class CommandDccallow : public Command
175 {
176  public:
177         DCCAllowExt& ext;
178         unsigned long defaultlength;
179         CommandDccallow(Module* parent, DCCAllowExt& Ext)
180                 : Command(parent, "DCCALLOW", 0)
181                 , ext(Ext)
182         {
183                 syntax = "[(+|-)<nick> [<time>]]|[LIST|HELP]";
184                 /* XXX we need to fix this so it can work with translation stuff (i.e. move +- into a seperate param */
185         }
186
187         CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
188         {
189                 /* syntax: DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP] */
190                 if (!parameters.size())
191                 {
192                         // display current DCCALLOW list
193                         DisplayDCCAllowList(user);
194                         return CMD_FAILURE;
195                 }
196                 else if (parameters.size() > 0)
197                 {
198                         char action = *parameters[0].c_str();
199
200                         // if they didn't specify an action, this is probably a command
201                         if (action != '+' && action != '-')
202                         {
203                                 if (!strcasecmp(parameters[0].c_str(), "LIST"))
204                                 {
205                                         // list current DCCALLOW list
206                                         DisplayDCCAllowList(user);
207                                         return CMD_FAILURE;
208                                 }
209                                 else if (!strcasecmp(parameters[0].c_str(), "HELP"))
210                                 {
211                                         // display help
212                                         DisplayHelp(user);
213                                         return CMD_FAILURE;
214                                 }
215                                 else
216                                 {
217                                         user->WriteNumeric(ERR_UNKNOWNDCCALLOWCMD, "DCCALLOW command not understood. For help on DCCALLOW, type /DCCALLOW HELP");
218                                         return CMD_FAILURE;
219                                 }
220                         }
221
222                         std::string nick(parameters[0], 1);
223                         User *target = ServerInstance->FindNickOnly(nick);
224
225                         if ((target) && (!target->quitting) && (target->registered == REG_ALL))
226                         {
227
228                                 if (action == '-')
229                                 {
230                                         // check if it contains any entries
231                                         dl = ext.get(user);
232                                         if (dl)
233                                         {
234                                                 for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i)
235                                                 {
236                                                         // search through list
237                                                         if (i->nickname == target->nick)
238                                                         {
239                                                                 dl->erase(i);
240                                                                 user->WriteNumeric(RPL_DCCALLOWREMOVED, user->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", target->nick.c_str()));
241                                                                 break;
242                                                         }
243                                                 }
244                                         }
245                                 }
246                                 else if (action == '+')
247                                 {
248                                         if (target == user)
249                                         {
250                                                 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "You cannot add yourself to your own DCCALLOW list!");
251                                                 return CMD_FAILURE;
252                                         }
253
254                                         dl = ext.get(user);
255                                         if (!dl)
256                                         {
257                                                 dl = new dccallowlist;
258                                                 ext.set(user, dl);
259                                                 // add this user to the userlist
260                                                 ul.push_back(user);
261                                         }
262
263                                         if (dl->size() >= ext.maxentries)
264                                         {
265                                                 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "Too many nicks on DCCALLOW list");
266                                                 return CMD_FAILURE;
267                                         }
268
269                                         for (dccallowlist::const_iterator k = dl->begin(); k != dl->end(); ++k)
270                                         {
271                                                 if (k->nickname == target->nick)
272                                                 {
273                                                         user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, InspIRCd::Format("%s is already on your DCCALLOW list", target->nick.c_str()));
274                                                         return CMD_FAILURE;
275                                                 }
276                                         }
277
278                                         std::string mask = target->nick+"!"+target->ident+"@"+target->GetDisplayedHost();
279                                         unsigned long length;
280                                         if (parameters.size() < 2)
281                                         {
282                                                 length = defaultlength;
283                                         }
284                                         else if (!InspIRCd::IsValidDuration(parameters[1]))
285                                         {
286                                                 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, InspIRCd::Format("%s is not a valid DCCALLOW duration", parameters[1].c_str()));
287                                                 return CMD_FAILURE;
288                                         }
289                                         else
290                                         {
291                                                 if (!InspIRCd::Duration(parameters[1], length))
292                                                 {
293                                                         user->WriteNotice("*** Invalid duration for DCC allow");
294                                                         return CMD_FAILURE;
295                                                 }
296                                         }
297
298                                         if (!InspIRCd::IsValidMask(mask))
299                                         {
300                                                 return CMD_FAILURE;
301                                         }
302
303                                         dl->push_back(DCCAllow(target->nick, mask, ServerInstance->Time(), length));
304
305                                         if (length > 0)
306                                         {
307                                                 user->WriteNumeric(RPL_DCCALLOWTIMED, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for %s", target->nick.c_str(), InspIRCd::DurationString(length).c_str()));
308                                         }
309                                         else
310                                         {
311                                                 user->WriteNumeric(RPL_DCCALLOWPERMANENT, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for this session", target->nick.c_str()));
312                                         }
313
314                                         /* route it. */
315                                         return CMD_SUCCESS;
316                                 }
317                         }
318                         else
319                         {
320                                 // nick doesn't exist
321                                 user->WriteNumeric(Numerics::NoSuchNick(nick));
322                                 return CMD_FAILURE;
323                         }
324                 }
325                 return CMD_FAILURE;
326         }
327
328         RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
329         {
330                 return ROUTE_BROADCAST;
331         }
332
333         void DisplayHelp(User* user)
334         {
335                 user->WriteNumeric(RPL_HELPSTART, "*", "DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP]");
336                 for (size_t i = 0; i < sizeof(helptext)/sizeof(helptext[0]); i++)
337                         user->WriteNumeric(RPL_HELPTXT, "*", helptext[i]);
338                 user->WriteNumeric(RPL_ENDOFHELP, "*", "End of DCCALLOW HELP");
339
340                 LocalUser* localuser = IS_LOCAL(user);
341                 if (localuser)
342                         localuser->CommandFloodPenalty += 4000;
343         }
344
345         void DisplayDCCAllowList(User* user)
346         {
347                  // display current DCCALLOW list
348                 user->WriteNumeric(RPL_DCCALLOWSTART, "Users on your DCCALLOW list:");
349
350                 dl = ext.get(user);
351                 if (dl)
352                 {
353                         for (dccallowlist::const_iterator c = dl->begin(); c != dl->end(); ++c)
354                         {
355                                 user->WriteNumeric(RPL_DCCALLOWLIST, user->nick, InspIRCd::Format("%s (%s)", c->nickname.c_str(), c->hostmask.c_str()));
356                         }
357                 }
358
359                 user->WriteNumeric(RPL_DCCALLOWEND, "End of DCCALLOW list");
360         }
361
362 };
363
364 class ModuleDCCAllow : public Module
365 {
366         DCCAllowExt ext;
367         CommandDccallow cmd;
368         bool blockchat;
369         std::string defaultaction;
370
371  public:
372         ModuleDCCAllow()
373                 : ext(this)
374                 , cmd(this, ext)
375                 , blockchat(false)
376         {
377         }
378
379         void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE
380         {
381                 dccallowlist* udl = ext.get(user);
382
383                 // remove their DCCALLOW list if they have one
384                 if (udl)
385                         stdalgo::erase(ul, user);
386
387                 // remove them from any DCCALLOW lists
388                 // they are currently on
389                 RemoveNick(user);
390         }
391
392         void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE
393         {
394                 RemoveNick(user);
395         }
396
397         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
398         {
399                 if (!IS_LOCAL(user))
400                         return MOD_RES_PASSTHRU;
401
402                 if (target.type == MessageTarget::TYPE_USER)
403                 {
404                         User* u = target.Get<User>();
405
406                         /* Always allow a user to dcc themselves (although... why?) */
407                         if (user == u)
408                                 return MOD_RES_PASSTHRU;
409
410                         if ((details.text.length()) && (details.text[0] == '\1'))
411                         {
412                                 Expire();
413
414                                 // :jamie!jamie@test-D4457903BA652E0F.silverdream.org PRIVMSG eimaj :DCC SEND m_dnsbl.cpp 3232235786 52650 9676
415                                 // :jamie!jamie@test-D4457903BA652E0F.silverdream.org PRIVMSG eimaj :VERSION
416
417                                 if (strncmp(details.text.c_str(), "\1DCC ", 5) == 0)
418                                 {
419                                         dl = ext.get(u);
420                                         if (dl && dl->size())
421                                         {
422                                                 for (dccallowlist::const_iterator iter = dl->begin(); iter != dl->end(); ++iter)
423                                                         if (InspIRCd::Match(user->GetFullHost(), iter->hostmask))
424                                                                 return MOD_RES_PASSTHRU;
425                                         }
426
427                                         std::string buf = details.text.substr(5);
428                                         size_t s = buf.find(' ');
429                                         if (s == std::string::npos)
430                                                 return MOD_RES_PASSTHRU;
431
432                                         const std::string type = buf.substr(0, s);
433
434                                         if (stdalgo::string::equalsci(type, "SEND"))
435                                         {
436                                                 size_t first;
437
438                                                 buf = buf.substr(s + 1);
439
440                                                 if (!buf.empty() && buf[0] == '"')
441                                                 {
442                                                         s = buf.find('"', 1);
443
444                                                         if (s == std::string::npos || s <= 1)
445                                                                 return MOD_RES_PASSTHRU;
446
447                                                         --s;
448                                                         first = 1;
449                                                 }
450                                                 else
451                                                 {
452                                                         s = buf.find(' ');
453                                                         first = 0;
454                                                 }
455
456                                                 if (s == std::string::npos)
457                                                         return MOD_RES_PASSTHRU;
458
459                                                 std::string filename = buf.substr(first, s);
460
461                                                 bool found = false;
462                                                 for (unsigned int i = 0; i < bfl.size(); i++)
463                                                 {
464                                                         if (InspIRCd::Match(filename, bfl[i].filemask, ascii_case_insensitive_map))
465                                                         {
466                                                                 /* We have a matching badfile entry, override whatever the default action is */
467                                                                 if (stdalgo::string::equalsci(bfl[i].action, "allow"))
468                                                                         return MOD_RES_PASSTHRU;
469                                                                 else
470                                                                 {
471                                                                         found = true;
472                                                                         break;
473                                                                 }
474                                                         }
475                                                 }
476
477                                                 /* only follow the default action if no badfile matches were found above */
478                                                 if ((!found) && (defaultaction == "allow"))
479                                                         return MOD_RES_PASSTHRU;
480
481                                                 user->WriteNotice("The user " + u->nick + " is not accepting DCC SENDs from you. Your file " + filename + " was not sent.");
482                                                 u->WriteNotice(user->nick + " (" + user->ident + "@" + user->GetDisplayedHost() + ") attempted to send you a file named " + filename + ", which was blocked.");
483                                                 u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.");
484                                                 return MOD_RES_DENY;
485                                         }
486                                         else if ((blockchat) && (stdalgo::string::equalsci(type, "CHAT")))
487                                         {
488                                                 user->WriteNotice("The user " + u->nick + " is not accepting DCC CHAT requests from you.");
489                                                 u->WriteNotice(user->nick + " (" + user->ident + "@" + user->GetDisplayedHost() + ") attempted to initiate a DCC CHAT session, which was blocked.");
490                                                 u->WriteNotice("If you trust " + user->nick + " and were expecting this, you can type /DCCALLOW HELP for information on the DCCALLOW system.");
491                                                 return MOD_RES_DENY;
492                                         }
493                                 }
494                         }
495                 }
496                 return MOD_RES_PASSTHRU;
497         }
498
499         void Expire()
500         {
501                 for (userlist::iterator iter = ul.begin(); iter != ul.end();)
502                 {
503                         User* u = (User*)(*iter);
504                         dl = ext.get(u);
505                         if (dl)
506                         {
507                                 if (dl->size())
508                                 {
509                                         dccallowlist::iterator iter2 = dl->begin();
510                                         while (iter2 != dl->end())
511                                         {
512                                                 time_t expires = iter2->set_on + iter2->length;
513                                                 if (iter2->length != 0 && expires <= ServerInstance->Time())
514                                                 {
515                                                         u->WriteNumeric(RPL_DCCALLOWEXPIRED, u->nick, InspIRCd::Format("DCCALLOW entry for %s has expired", iter2->nickname.c_str()));
516                                                         iter2 = dl->erase(iter2);
517                                                 }
518                                                 else
519                                                 {
520                                                         ++iter2;
521                                                 }
522                                         }
523                                 }
524                                 ++iter;
525                         }
526                         else
527                         {
528                                 iter = ul.erase(iter);
529                         }
530                 }
531         }
532
533         void RemoveNick(User* user)
534         {
535                 /* Iterate through all DCCALLOW lists and remove user */
536                 for (userlist::iterator iter = ul.begin(); iter != ul.end();)
537                 {
538                         User *u = (User*)(*iter);
539                         dl = ext.get(u);
540                         if (dl)
541                         {
542                                 if (dl->size())
543                                 {
544                                         for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i)
545                                         {
546                                                 if (i->nickname == user->nick)
547                                                 {
548
549                                                         u->WriteNotice(i->nickname + " left the network or changed their nickname and has been removed from your DCCALLOW list");
550                                                         u->WriteNumeric(RPL_DCCALLOWREMOVED, u->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", i->nickname.c_str()));
551                                                         dl->erase(i);
552                                                         break;
553                                                 }
554                                         }
555                                 }
556                                 ++iter;
557                         }
558                         else
559                         {
560                                 iter = ul.erase(iter);
561                         }
562                 }
563         }
564
565         void RemoveFromUserlist(User *user)
566         {
567                 // remove user from userlist
568                 for (userlist::iterator j = ul.begin(); j != ul.end(); ++j)
569                 {
570                         User* u = (User*)(*j);
571                         if (u == user)
572                         {
573                                 ul.erase(j);
574                                 break;
575                         }
576                 }
577         }
578
579         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
580         {
581                 bannedfilelist newbfl;
582                 ConfigTagList tags = ServerInstance->Config->ConfTags("banfile");
583                 for (ConfigIter i = tags.first; i != tags.second; ++i)
584                 {
585                         BannedFileList bf;
586                         bf.filemask = i->second->getString("pattern");
587                         bf.action = i->second->getString("action");
588                         newbfl.push_back(bf);
589                 }
590                 bfl.swap(newbfl);
591
592                 ConfigTag* tag = ServerInstance->Config->ConfValue("dccallow");
593                 cmd.ext.maxentries = tag->getUInt("maxentries", 20);
594                 cmd.defaultlength = tag->getDuration("length", 0);
595                 blockchat = tag->getBool("blockchat");
596                 defaultaction = tag->getString("action");
597         }
598
599         Version GetVersion() CXX11_OVERRIDE
600         {
601                 return Version("Provides the DCCALLOW command", VF_COMMON | VF_VENDOR);
602         }
603 };
604
605 MODULE_INIT(ModuleDCCAllow)