X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fm_banredirect.cpp;h=047c55e7f9f292a3eefe0172b3365fa2d7ccdb4f;hb=0b63ccd0b5cb26883d6becb196fb98e4f95d0397;hp=2a0ed2ded6756e2184dcfc3cdc5a8bcfcfbbf2dd;hpb=bab14f0dd2345c9d7dcbc47c918563709e1ac094;p=user%2Fhenk%2Fcode%2Finspircd.git diff --git a/src/modules/m_banredirect.cpp b/src/modules/m_banredirect.cpp index 2a0ed2ded..d3490acc0 100644 --- a/src/modules/m_banredirect.cpp +++ b/src/modules/m_banredirect.cpp @@ -1 +1,339 @@ -/* +------------------------------------+ * | Inspire Internet Relay Chat Daemon | * +------------------------------------+ * * InspIRCd: (C) 2002-2007 InspIRCd Development Team * See: http://www.inspircd.org/wiki/index.php/Credits * * This program is free but copyrighted software; see * the file COPYING for details. * * --------------------------------------------------- */ #include "inspircd.h" #include "mode.h" #include "users.h" #include "channels.h" #include "modules.h" #include "u_listmode.h" /* $ModDesc: Allows an extended ban (+b) syntax redirecting banned users to another channel */ /* Originally written by Om, January 2007 */ class BanRedirectEntry { public: std::string targetchan; std::string banmask; BanRedirectEntry(const std::string &target = "", const std::string &mask = "") : targetchan(target), banmask(mask) { } }; typedef std::vector BanRedirectList; typedef std::deque StringDeque; class BanRedirect : public ModeWatcher { private: InspIRCd* Srv; public: BanRedirect(InspIRCd* Instance) : ModeWatcher(Instance, 'b', MODETYPE_CHANNEL), Srv(Instance) { } bool BeforeMode(userrec* source, userrec* dest, chanrec* channel, std::string ¶m, bool adding, ModeType type) { /* nick!ident@host -> nick!ident@host * nick!ident@host#chan -> nick!ident@host#chan * nick@host#chan -> nick!*@host#chan * nick!ident#chan -> nick!ident@*#chan * nick#chan -> nick!*@*#chan */ if(channel && (type == MODETYPE_CHANNEL) && param.length()) { BanRedirectList* redirects; std::string mask[4]; enum { NICK, IDENT, HOST, CHAN } current = NICK; std::string::iterator start_pos = param.begin(); long maxbans = channel->GetMaxBans(); if(channel->bans.size() > static_cast(maxbans)) { source->WriteServ("478 %s %s :Channel ban list for %s is full (maximum entries for this channel is %d)", source->nick, channel->name, channel->name, maxbans); return false; } for(std::string::iterator curr = start_pos; curr != param.end(); curr++) { switch(*curr) { case '!': mask[current].assign(start_pos, curr); current = IDENT; start_pos = curr+1; break; case '@': mask[current].assign(start_pos, curr); current = HOST; start_pos = curr+1; break; case '#': mask[current].assign(start_pos, curr); current = CHAN; start_pos = curr; break; } } if(mask[current].empty()) { mask[current].assign(start_pos, param.end()); } /* nick@host wants to be changed to *!nick@host rather than nick!*@host... */ if(mask[NICK].length() && mask[HOST].length() && mask[IDENT].empty()) { /* std::string::swap() is fast - it runs in constant time */ mask[NICK].swap(mask[IDENT]); } for(int i = 0; i < 3; i++) { if(mask[i].empty()) { mask[i].assign("*"); } } param.assign(mask[NICK]).append(1, '!').append(mask[IDENT]).append(1, '@').append(mask[HOST]); if(mask[CHAN].length()) { if(Srv->IsChannel(mask[CHAN].c_str())) { if(irc::string(channel->name) == irc::string(mask[CHAN].c_str())) { source->WriteServ("690 %s %s :You cannot set a ban redirection to the channel the ban is on", source->nick, channel->name); return false; } else { if(adding) { /* It's a properly valid redirecting ban, and we're adding it */ if(!channel->GetExt("banredirects", redirects)) { redirects = new BanRedirectList; channel->Extend("banredirects", redirects); } /* Here 'param' doesn't have the channel on it yet */ redirects->push_back(BanRedirectEntry(mask[CHAN].c_str(), param.c_str())); /* Now it does */ param.append(mask[CHAN]); } else { /* Removing a ban, if there's no extensible there are no redirecting bans and we're fine. */ if(channel->GetExt("banredirects", redirects)) { /* But there were, so we need to remove the matching one if there is one */ for(BanRedirectList::iterator redir = redirects->begin(); redir != redirects->end(); redir++) { /* Ugly as fuck */ if((irc::string(redir->targetchan.c_str()) == irc::string(mask[CHAN].c_str())) && (irc::string(redir->banmask.c_str()) == irc::string(param.c_str()))) { redirects->erase(redir); if(redirects->empty()) { DELETE(redirects); channel->Shrink("banredirects"); } break; } } } /* Append the channel so the default +b handler can remove the entry too */ param.append(mask[CHAN]); } } } else { source->WriteServ("403 %s %s :Invalid channel name in redirection (%s)", source->nick, channel->name, mask[CHAN].c_str()); return false; } } } return true; } }; class ModuleBanRedirect : public Module { BanRedirect* re; bool nofollow; Module* ExceptionModule; public: ModuleBanRedirect(InspIRCd* Me) : Module(Me) { re = new BanRedirect(Me); nofollow = false; if(!ServerInstance->AddModeWatcher(re)) throw ModuleException("Could not add mode watcher"); OnRehash(NULL, ""); } void Implements(char* List) { List[I_OnRehash] = List[I_OnUserPreJoin] = List[I_OnChannelDelete] = List[I_OnCleanup] = 1; } virtual void OnChannelDelete(chanrec* chan) { OnCleanup(TYPE_CHANNEL, chan); } virtual void OnCleanup(int target_type, void* item) { if(target_type == TYPE_CHANNEL) { chanrec* chan = static_cast(item); BanRedirectList* redirects; if(chan->GetExt("banredirects", redirects)) { irc::modestacker modestack(false); StringDeque stackresult; const char* mode_junk[MAXMODES+2]; userrec* myhorriblefakeuser = new userrec(ServerInstance); myhorriblefakeuser->SetFd(FD_MAGIC_NUMBER); mode_junk[0] = chan->name; for(BanRedirectList::iterator i = redirects->begin(); i != redirects->end(); i++) { modestack.Push('b', i->targetchan.insert(0, i->banmask)); } for(BanRedirectList::iterator i = redirects->begin(); i != redirects->end(); i++) { modestack.PushPlus(); modestack.Push('b', i->banmask); } while(modestack.GetStackedLine(stackresult)) { for(StringDeque::size_type i = 0; i < stackresult.size(); i++) { mode_junk[i+1] = stackresult[i].c_str(); } ServerInstance->SendMode(mode_junk, stackresult.size() + 1, myhorriblefakeuser); } DELETE(myhorriblefakeuser); DELETE(redirects); chan->Shrink("banredirects"); } } } virtual void OnRehash(userrec* user, const std::string ¶m) { ExceptionModule = ServerInstance->FindModule("m_banexception.so"); } virtual int OnUserPreJoin(userrec* user, chanrec* chan, const char* cname, std::string &privs) { /* This prevents recursion when a user sets multiple ban redirects in a chain * (thanks Potter) */ if (nofollow) return 0; /* Return 1 to prevent the join, 0 to allow it */ if (chan) { BanRedirectList* redirects; if(chan->GetExt("banredirects", redirects)) { /* We actually had some ban redirects to check */ /* This was replaced with user->MakeHostIP() when I had a snprintf(), but MakeHostIP() doesn't seem to add the nick. * Maybe we should have a GetFullIPHost() or something to match GetFullHost() and GetFullRealHost? */ if (ExceptionModule) { ListModeRequest n(this, ExceptionModule, user, chan); /* Users with ban exceptions are allowed to join without being redirected */ if (n.Send()) return 0; } std::string ipmask(user->nick); ipmask.append(1, '!').append(user->MakeHostIP()); for(BanRedirectList::iterator redir = redirects->begin(); redir != redirects->end(); redir++) { if(ServerInstance->MatchText(user->GetFullRealHost(), redir->banmask) || ServerInstance->MatchText(user->GetFullHost(), redir->banmask) || ServerInstance->MatchText(ipmask, redir->banmask)) { /* tell them they're banned and are being transferred */ chanrec* destchan = ServerInstance->FindChan(redir->targetchan); if(destchan && ServerInstance->FindModule("m_redirect.so") && destchan->IsModeSet('L') && destchan->limit && (destchan->GetUserCounter() >= destchan->limit)) { user->WriteServ("474 %s %s :Cannot join channel (You are banned)", user->nick, chan->name); return 1; } else { user->WriteServ("470 %s :You are banned from %s. You are being automatically redirected to %s", user->nick, chan->name, redir->targetchan.c_str()); nofollow = true; chanrec::JoinUser(ServerInstance, user, redir->targetchan.c_str(), false, "", ServerInstance->Time(true)); nofollow = false; return 1; } } } } } return 0; } virtual ~ModuleBanRedirect() { ServerInstance->Modes->DelModeWatcher(re); DELETE(re); } virtual Version GetVersion() { return Version(1, 0, 0, 0, VF_COMMON | VF_VENDOR, API_VERSION); } Priority Prioritize() { return (Priority)ServerInstance->PriorityBefore("m_banexception.so"); } }; MODULE_INIT(ModuleBanRedirect) \ No newline at end of file +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2009 Daniel De Graaf + * Copyright (C) 2007, 2009 Robin Burchell + * Copyright (C) 2008 Pippijn van Steenhoven + * Copyright (C) 2007 Dennis Friis + * Copyright (C) 2007 Craig Edwards + * Copyright (C) 2007 Oliver Lupton + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "inspircd.h" +#include "listmode.h" + +/* Originally written by Om, January 2009 + */ + +class BanRedirectEntry +{ + public: + std::string targetchan; + std::string banmask; + + BanRedirectEntry(const std::string &target = "", const std::string &mask = "") + : targetchan(target), banmask(mask) + { + } +}; + +typedef std::vector BanRedirectList; + +class BanRedirect : public ModeWatcher +{ + ChanModeReference ban; + public: + SimpleExtItem extItem; + BanRedirect(Module* parent) + : ModeWatcher(parent, "ban", MODETYPE_CHANNEL) + , ban(parent, "ban") + , extItem("banredirect", ExtensionItem::EXT_CHANNEL, parent) + { + } + + bool BeforeMode(User* source, User* dest, Channel* channel, std::string ¶m, bool adding) + { + /* nick!ident@host -> nick!ident@host + * nick!ident@host#chan -> nick!ident@host#chan + * nick@host#chan -> nick!*@host#chan + * nick!ident#chan -> nick!ident@*#chan + * nick#chan -> nick!*@*#chan + */ + + if ((channel) && !param.empty()) + { + BanRedirectList* redirects; + + std::string mask[4]; + enum { NICK, IDENT, HOST, CHAN } current = NICK; + std::string::iterator start_pos = param.begin(); + + if (param.length() >= 2 && param[1] == ':') + return true; + + if (param.find('#') == std::string::npos) + return true; + + ListModeBase* banlm = static_cast(*ban); + unsigned int maxbans = banlm->GetLimit(channel); + ListModeBase::ModeList* list = banlm->GetList(channel); + if ((list) && (adding) && (maxbans <= list->size())) + { + source->WriteNumeric(ERR_BANLISTFULL, "%s :Channel ban list for %s is full (maximum entries for this channel is %u)", channel->name.c_str(), channel->name.c_str(), maxbans); + return false; + } + + for(std::string::iterator curr = start_pos; curr != param.end(); curr++) + { + switch(*curr) + { + case '!': + if (current != NICK) + break; + mask[current].assign(start_pos, curr); + current = IDENT; + start_pos = curr+1; + break; + case '@': + if (current != IDENT) + break; + mask[current].assign(start_pos, curr); + current = HOST; + start_pos = curr+1; + break; + case '#': + if (current == CHAN) + break; + mask[current].assign(start_pos, curr); + current = CHAN; + start_pos = curr; + break; + } + } + + if(mask[current].empty()) + { + mask[current].assign(start_pos, param.end()); + } + + /* nick@host wants to be changed to *!nick@host rather than nick!*@host... */ + if(mask[NICK].length() && mask[HOST].length() && mask[IDENT].empty()) + { + /* std::string::swap() is fast - it runs in constant time */ + mask[NICK].swap(mask[IDENT]); + } + + if (!mask[NICK].empty() && mask[IDENT].empty() && mask[HOST].empty()) + { + if (mask[NICK].find('.') != std::string::npos || mask[NICK].find(':') != std::string::npos) + { + mask[NICK].swap(mask[HOST]); + } + } + + for(int i = 0; i < 3; i++) + { + if(mask[i].empty()) + { + mask[i].assign("*"); + } + } + + param.assign(mask[NICK]).append(1, '!').append(mask[IDENT]).append(1, '@').append(mask[HOST]); + + if(mask[CHAN].length()) + { + if (adding && IS_LOCAL(source)) + { + if (!ServerInstance->IsChannel(mask[CHAN])) + { + source->WriteNumeric(ERR_NOSUCHCHANNEL, "%s :Invalid channel name in redirection (%s)", channel->name.c_str(), mask[CHAN].c_str()); + return false; + } + + Channel *c = ServerInstance->FindChan(mask[CHAN]); + if (!c) + { + source->WriteNumeric(690, ":Target channel %s must exist to be set as a redirect.", mask[CHAN].c_str()); + return false; + } + else if (adding && c->GetPrefixValue(source) < OP_VALUE) + { + source->WriteNumeric(690, ":You must be opped on %s to set it as a redirect.", mask[CHAN].c_str()); + return false; + } + + if (assign(channel->name) == mask[CHAN]) + { + source->WriteNumeric(690, "%s :You cannot set a ban redirection to the channel the ban is on", channel->name.c_str()); + return false; + } + } + + if(adding) + { + /* It's a properly valid redirecting ban, and we're adding it */ + redirects = extItem.get(channel); + if (!redirects) + { + redirects = new BanRedirectList; + extItem.set(channel, redirects); + } + + /* Here 'param' doesn't have the channel on it yet */ + redirects->push_back(BanRedirectEntry(mask[CHAN], param)); + + /* Now it does */ + param.append(mask[CHAN]); + } + else + { + /* Removing a ban, if there's no extensible there are no redirecting bans and we're fine. */ + redirects = extItem.get(channel); + if (redirects) + { + /* But there were, so we need to remove the matching one if there is one */ + + for(BanRedirectList::iterator redir = redirects->begin(); redir != redirects->end(); redir++) + { + /* Ugly as fuck */ + if((irc::string(redir->targetchan.c_str()) == irc::string(mask[CHAN].c_str())) && (irc::string(redir->banmask.c_str()) == irc::string(param.c_str()))) + { + redirects->erase(redir); + + if(redirects->empty()) + { + extItem.unset(channel); + } + + break; + } + } + } + + /* Append the channel so the default +b handler can remove the entry too */ + param.append(mask[CHAN]); + } + } + } + + return true; + } +}; + +class ModuleBanRedirect : public Module +{ + BanRedirect re; + bool nofollow; + ChanModeReference limitmode; + ChanModeReference redirectmode; + + public: + ModuleBanRedirect() + : re(this) + , nofollow(false) + , limitmode(this, "limit") + , redirectmode(this, "redirect") + { + } + + void OnCleanup(int target_type, void* item) CXX11_OVERRIDE + { + if(target_type == TYPE_CHANNEL) + { + Channel* chan = static_cast(item); + BanRedirectList* redirects = re.extItem.get(chan); + + if(redirects) + { + ModeHandler* ban = ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL); + Modes::ChangeList changelist; + + for(BanRedirectList::iterator i = redirects->begin(); i != redirects->end(); i++) + changelist.push_remove(ban, i->targetchan.insert(0, i->banmask)); + + for(BanRedirectList::iterator i = redirects->begin(); i != redirects->end(); i++) + changelist.push_add(ban, i->banmask); + + ServerInstance->Modes->Process(ServerInstance->FakeClient, chan, NULL, changelist, ModeParser::MODE_LOCALONLY); + } + } + } + + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven) CXX11_OVERRIDE + { + if (chan) + { + BanRedirectList* redirects = re.extItem.get(chan); + + if (redirects) + { + /* We actually had some ban redirects to check */ + + /* This was replaced with user->MakeHostIP() when I had a snprintf(), but MakeHostIP() doesn't seem to add the nick. + * Maybe we should have a GetFullIPHost() or something to match GetFullHost() and GetFullRealHost? + */ + + ModResult result; + FIRST_MOD_RESULT(OnCheckChannelBan, result, (user, chan)); + if (result == MOD_RES_ALLOW) + { + // they have a ban exception + return MOD_RES_PASSTHRU; + } + + std::string ipmask(user->nick); + ipmask.append(1, '!').append(user->MakeHostIP()); + + for(BanRedirectList::iterator redir = redirects->begin(); redir != redirects->end(); redir++) + { + if(InspIRCd::Match(user->GetFullRealHost(), redir->banmask) || InspIRCd::Match(user->GetFullHost(), redir->banmask) || InspIRCd::MatchCIDR(ipmask, redir->banmask)) + { + /* This prevents recursion when a user sets multiple ban redirects in a chain + * (thanks Potter) + * + * If we're here and nofollow is true then we're already redirecting this user + * and there's a redirecting ban set on this channel that matches him, too. + * Deny both joins. + */ + if (nofollow) + return MOD_RES_DENY; + + /* tell them they're banned and are being transferred */ + Channel* destchan = ServerInstance->FindChan(redir->targetchan); + std::string destlimit; + + if (destchan) + destlimit = destchan->GetModeParameter(limitmode); + + if(destchan && destchan->IsModeSet(redirectmode) && !destlimit.empty() && (destchan->GetUserCounter() >= atoi(destlimit.c_str()))) + { + user->WriteNumeric(ERR_BANNEDFROMCHAN, "%s :Cannot join channel (You are banned)", chan->name.c_str()); + return MOD_RES_DENY; + } + else + { + user->WriteNumeric(ERR_BANNEDFROMCHAN, "%s :Cannot join channel (You are banned)", chan->name.c_str()); + user->WriteNumeric(470, "%s %s :You are banned from this channel, so you are automatically transferred to the redirected channel.", chan->name.c_str(), redir->targetchan.c_str()); + nofollow = true; + Channel::JoinUser(user, redir->targetchan); + nofollow = false; + return MOD_RES_DENY; + } + } + } + } + } + return MOD_RES_PASSTHRU; + } + + Version GetVersion() CXX11_OVERRIDE + { + return Version("Allows an extended ban (+b) syntax redirecting banned users to another channel", VF_COMMON|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleBanRedirect)