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