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