2 * InspIRCd -- Internet Relay Chat Daemon
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>
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.
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
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/>.
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
53 static const char* const helptext[] =
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",
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:",
66 "To remove the user from your DCCALLOW list, type:",
68 "To see the users in your DCCALLOW list, type:",
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."
92 DCCAllow(const std::string& nick, const std::string& hm, time_t so, unsigned long ln)
101 typedef std::vector<User *> userlist;
103 typedef std::vector<DCCAllow> dccallowlist;
105 typedef std::vector<BannedFileList> bannedfilelist;
108 class DCCAllowExt : public SimpleExtItem<dccallowlist>
111 unsigned int maxentries;
113 DCCAllowExt(Module* Creator)
114 : SimpleExtItem<dccallowlist>("dccallow", ExtensionItem::EXT_USER, Creator)
118 void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE
120 LocalUser* user = IS_LOCAL(static_cast<User*>(container));
124 // Remove the old list and create a new one.
126 dccallowlist* list = new dccallowlist();
128 irc::spacesepstream ts(value);
129 while (!ts.StreamEnd())
131 // Check we have space for another entry.
132 if (list->size() >= maxentries)
134 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Oversized DCC allow list received for %s: %s",
135 user->uuid.c_str(), value.c_str());
140 // Extract the fields.
142 if (!ts.GetToken(dccallow.nickname) ||
143 !ts.GetToken(dccallow.hostmask) ||
144 !ts.GetNumericToken(dccallow.set_on) ||
145 !ts.GetNumericToken(dccallow.length))
147 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Malformed DCC allow list received for %s: %s",
148 user->uuid.c_str(), value.c_str());
153 // Store the DCC allow entry.
154 list->push_back(dccallow);
157 // The value was well formed.
161 std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE
163 dccallowlist* list = static_cast<dccallowlist*>(item);
165 for (dccallowlist::const_iterator iter = list->begin(); iter != list->end(); ++iter)
167 if (iter != list->begin())
170 buf.append(iter->nickname);
172 buf.append(iter->hostmask);
174 buf.append(ConvToStr(iter->set_on));
176 buf.append(ConvToStr(iter->length));
182 class CommandDccallow : public Command
186 unsigned long defaultlength;
187 CommandDccallow(Module* parent, DCCAllowExt& Ext)
188 : Command(parent, "DCCALLOW", 0)
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 */
195 CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
197 /* syntax: DCCALLOW [(+|-)<nick> [<time>]]|[LIST|HELP] */
198 if (!parameters.size())
200 // display current DCCALLOW list
201 DisplayDCCAllowList(user);
204 else if (parameters.size() > 0)
206 char action = *parameters[0].c_str();
208 // if they didn't specify an action, this is probably a command
209 if (action != '+' && action != '-')
211 if (irc::equals(parameters[0], "LIST"))
213 // list current DCCALLOW list
214 DisplayDCCAllowList(user);
217 else if (irc::equals(parameters[0], "HELP"))
225 user->WriteNumeric(ERR_UNKNOWNDCCALLOWCMD, "DCCALLOW command not understood. For help on DCCALLOW, type /DCCALLOW HELP");
230 std::string nick(parameters[0], 1);
231 User *target = ServerInstance->FindNickOnly(nick);
233 if ((target) && (!target->quitting) && (target->registered == REG_ALL))
238 // check if it contains any entries
242 for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i)
244 // search through list
245 if (i->nickname == target->nick)
248 user->WriteNumeric(RPL_DCCALLOWREMOVED, user->nick, InspIRCd::Format("Removed %s from your DCCALLOW list", target->nick.c_str()));
254 else if (action == '+')
258 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "You cannot add yourself to your own DCCALLOW list!");
265 dl = new dccallowlist;
267 // add this user to the userlist
271 if (dl->size() >= ext.maxentries)
273 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, "Too many nicks on DCCALLOW list");
277 for (dccallowlist::const_iterator k = dl->begin(); k != dl->end(); ++k)
279 if (k->nickname == target->nick)
281 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, InspIRCd::Format("%s is already on your DCCALLOW list", target->nick.c_str()));
286 std::string mask = target->nick+"!"+target->ident+"@"+target->GetDisplayedHost();
287 unsigned long length;
288 if (parameters.size() < 2)
290 length = defaultlength;
292 else if (!InspIRCd::IsValidDuration(parameters[1]))
294 user->WriteNumeric(ERR_DCCALLOWINVALID, user->nick, InspIRCd::Format("%s is not a valid DCCALLOW duration", parameters[1].c_str()));
299 if (!InspIRCd::Duration(parameters[1], length))
301 user->WriteNotice("*** Invalid duration for DCC allow");
306 if (!InspIRCd::IsValidMask(mask))
311 dl->push_back(DCCAllow(target->nick, mask, ServerInstance->Time(), length));
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()));
319 user->WriteNumeric(RPL_DCCALLOWPERMANENT, user->nick, InspIRCd::Format("Added %s to DCCALLOW list for this session", target->nick.c_str()));
328 // nick doesn't exist
329 user->WriteNumeric(Numerics::NoSuchNick(nick));
336 RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
338 return ROUTE_BROADCAST;
341 void DisplayHelp(User* user)
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");
348 LocalUser* localuser = IS_LOCAL(user);
350 localuser->CommandFloodPenalty += 4000;
353 void DisplayDCCAllowList(User* user)
355 // display current DCCALLOW list
356 user->WriteNumeric(RPL_DCCALLOWSTART, "Users on your DCCALLOW list:");
361 for (dccallowlist::const_iterator c = dl->begin(); c != dl->end(); ++c)
363 user->WriteNumeric(RPL_DCCALLOWLIST, user->nick, InspIRCd::Format("%s (%s)", c->nickname.c_str(), c->hostmask.c_str()));
367 user->WriteNumeric(RPL_DCCALLOWEND, "End of DCCALLOW list");
372 class ModuleDCCAllow : public Module
377 std::string defaultaction;
387 void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE
389 dccallowlist* udl = ext.get(user);
391 // remove their DCCALLOW list if they have one
393 stdalgo::erase(ul, user);
395 // remove them from any DCCALLOW lists
396 // they are currently on
400 void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE
405 ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
408 return MOD_RES_PASSTHRU;
410 if (target.type == MessageTarget::TYPE_USER)
412 User* u = target.Get<User>();
414 /* Always allow a user to dcc themselves (although... why?) */
416 return MOD_RES_PASSTHRU;
418 std::string ctcpname;
419 std::string ctcpbody;
420 if (details.IsCTCP(ctcpname, ctcpbody))
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
427 if (irc::equals(ctcpname, "DCC") && !ctcpbody.empty())
430 if (dl && dl->size())
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;
437 size_t s = ctcpbody.find(' ');
438 if (s == std::string::npos)
439 return MOD_RES_PASSTHRU;
441 const std::string type = ctcpbody.substr(0, s);
443 if (irc::equals(type, "SEND"))
447 std::string buf = ctcpbody.substr(s + 1);
449 if (!buf.empty() && buf[0] == '"')
451 s = buf.find('"', 1);
453 if (s == std::string::npos || s <= 1)
454 return MOD_RES_PASSTHRU;
465 if (s == std::string::npos)
466 return MOD_RES_PASSTHRU;
468 std::string filename = buf.substr(first, s);
471 for (unsigned int i = 0; i < bfl.size(); i++)
473 if (InspIRCd::Match(filename, bfl[i].filemask, ascii_case_insensitive_map))
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;
486 /* only follow the default action if no badfile matches were found above */
487 if ((!found) && (defaultaction == "allow"))
488 return MOD_RES_PASSTHRU;
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.");
495 else if (blockchat && irc::equals(type, "CHAT"))
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.");
505 return MOD_RES_PASSTHRU;
510 for (userlist::iterator iter = ul.begin(); iter != ul.end();)
512 User* u = (User*)(*iter);
518 dccallowlist::iterator iter2 = dl->begin();
519 while (iter2 != dl->end())
521 time_t expires = iter2->set_on + iter2->length;
522 if (iter2->length != 0 && expires <= ServerInstance->Time())
524 u->WriteNumeric(RPL_DCCALLOWEXPIRED, u->nick, InspIRCd::Format("DCCALLOW entry for %s has expired", iter2->nickname.c_str()));
525 iter2 = dl->erase(iter2);
537 iter = ul.erase(iter);
542 void RemoveNick(User* user)
544 /* Iterate through all DCCALLOW lists and remove user */
545 for (userlist::iterator iter = ul.begin(); iter != ul.end();)
547 User *u = (User*)(*iter);
553 for (dccallowlist::iterator i = dl->begin(); i != dl->end(); ++i)
555 if (i->nickname == user->nick)
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()));
569 iter = ul.erase(iter);
574 void RemoveFromUserlist(User *user)
576 // remove user from userlist
577 for (userlist::iterator j = ul.begin(); j != ul.end(); ++j)
579 User* u = (User*)(*j);
588 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
590 bannedfilelist newbfl;
591 ConfigTagList tags = ServerInstance->Config->ConfTags("banfile");
592 for (ConfigIter i = tags.first; i != tags.second; ++i)
595 bf.filemask = i->second->getString("pattern");
596 bf.action = i->second->getString("action");
597 newbfl.push_back(bf);
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");
608 Version GetVersion() CXX11_OVERRIDE
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);
614 MODULE_INIT(ModuleDCCAllow)