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