summaryrefslogtreecommitdiff
path: root/modules/chanserv/main/chanserv.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/chanserv/main/chanserv.cpp')
-rw-r--r--modules/chanserv/main/chanserv.cpp576
1 files changed, 576 insertions, 0 deletions
diff --git a/modules/chanserv/main/chanserv.cpp b/modules/chanserv/main/chanserv.cpp
new file mode 100644
index 000000000..31f0c2c88
--- /dev/null
+++ b/modules/chanserv/main/chanserv.cpp
@@ -0,0 +1,576 @@
+/*
+ * Anope IRC Services
+ *
+ * Copyright (C) 2003-2017 Anope Team <team@anope.org>
+ *
+ * This file is part of Anope. Anope 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 see <http://www.gnu.org/licenses/>.
+ */
+
+#include "module.h"
+#include "modules/chanserv/mode.h"
+#include "modules/help.h"
+#include "modules/botserv/bot.h"
+#include "modules/chanserv.h"
+#include "modules/chanserv/info.h"
+#include "modules/chanserv/akick.h"
+#include "channeltype.h"
+#include "leveltype.h"
+#include "modetype.h"
+#include "chanaccesstype.h"
+#include "modules/chanserv/main/chanaccess.h"
+
+class ChanServCore : public Module
+ , public ChanServ::ChanServService
+ , public EventHook<Event::ChannelCreate>
+ , public EventHook<Event::BotDelete>
+ , public EventHook<Event::BotPrivmsg>
+ , public EventHook<Event::DelCore>
+ , public EventHook<Event::DelChan>
+ , public EventHook<Event::Help>
+ , public EventHook<Event::CheckModes>
+ , public EventHook<Event::CanSet>
+ , public EventHook<Event::ChannelSync>
+ , public EventHook<Event::Log>
+ , public EventHook<Event::ExpireTick>
+ , public EventHook<Event::CheckDelete>
+ , public EventHook<Event::PreUplinkSync>
+ , public EventHook<Event::ChanRegistered>
+ , public EventHook<Event::JoinChannel>
+ , public EventHook<Event::ChannelModeSet>
+ , public EventHook<Event::ChanInfo>
+ , public EventHook<Event::SetCorrectModes>
+{
+ Reference<ServiceBot> ChanServ;
+ std::vector<Anope::string> defaults;
+ ExtensibleItem<bool> inhabit;
+ bool always_lower;
+ std::vector<ChanServ::Privilege> Privileges;
+ ChanServ::registered_channel_map registered_channels;
+ ChannelType channel_type;
+ LevelType level_type;
+ CSModeType mode_type;
+
+ public:
+ ChanServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR)
+ , ChanServService(this)
+ , EventHook<Event::ChannelCreate>(this)
+ , EventHook<Event::BotDelete>(this)
+ , EventHook<Event::BotPrivmsg>(this)
+ , EventHook<Event::DelCore>(this)
+ , EventHook<Event::DelChan>(this)
+ , EventHook<Event::Help>(this)
+ , EventHook<Event::CheckModes>(this)
+ , EventHook<Event::CanSet>(this)
+ , EventHook<Event::ChannelSync>(this)
+ , EventHook<Event::Log>(this)
+ , EventHook<Event::ExpireTick>(this)
+ , EventHook<Event::CheckDelete>(this)
+ , EventHook<Event::PreUplinkSync>(this)
+ , EventHook<Event::ChanRegistered>(this)
+ , EventHook<Event::JoinChannel>(this)
+ , EventHook<Event::ChannelModeSet>(this)
+ , EventHook<Event::ChanInfo>(this)
+ , EventHook<Event::SetCorrectModes>(this)
+ , inhabit(this, "inhabit")
+ , always_lower(false)
+ , channel_type(this)
+ , level_type(this)
+ , mode_type(this)
+ {
+ ChanServ::service = this;
+ }
+
+ ~ChanServCore()
+ {
+ ChanServ::service = nullptr;
+ }
+
+ ChanServ::Channel *Find(const Anope::string &name) override
+ {
+ return channel_type.FindChannel(name);
+ }
+
+ ChanServ::registered_channel_map& GetChannels() override
+ {
+ return registered_channels;
+ }
+
+ void Hold(Channel *c) override
+ {
+ /** A timer used to keep the BotServ bot/ChanServ in the channel
+ * after kicking the last user in a channel
+ */
+ class ChanServTimer : public Timer
+ {
+ Reference<ServiceBot> &ChanServ;
+ ExtensibleItem<bool> &inhabit;
+ Reference<Channel> c;
+
+ public:
+ /** Constructor
+ * @param chan The channel
+ */
+ ChanServTimer(Reference<ServiceBot> &cs, ExtensibleItem<bool> &i, Module *m, Channel *chan) : Timer(m, Config->GetModule(m)->Get<time_t>("inhabit", "15s")), ChanServ(cs), inhabit(i), c(chan)
+ {
+ if (!ChanServ || !c)
+ return;
+ inhabit.Set(c, true);
+ if (!c->ci || !c->ci->GetBot())
+ ChanServ->Join(c);
+ else if (!c->FindUser(c->ci->GetBot()))
+ c->ci->GetBot()->Join(c);
+
+ /* Set +ntsi to prevent rejoin */
+ c->SetMode(NULL, "NOEXTERNAL");
+ c->SetMode(NULL, "TOPIC");
+ c->SetMode(NULL, "SECRET");
+ c->SetMode(NULL, "INVITE");
+ }
+
+ /** Called when the delay is up
+ * @param The current time
+ */
+ void Tick(time_t) override
+ {
+ if (!c)
+ return;
+
+ inhabit.Unset(c);
+
+ /* In the event we don't part */
+ c->RemoveMode(NULL, "SECRET");
+ c->RemoveMode(NULL, "INVITE");
+
+ if (!c->ci || !c->ci->GetBot())
+ {
+ if (ChanServ)
+ ChanServ->Part(c);
+ }
+ /* If someone has rejoined this channel in the meantime, don't part the bot */
+ else if (c->users.size() <= 1)
+ c->ci->GetBot()->Part(c);
+ }
+ };
+
+ if (inhabit.HasExt(c))
+ return;
+
+ new ChanServTimer(ChanServ, inhabit, this, c);
+ }
+
+ void AddPrivilege(ChanServ::Privilege p) override
+ {
+ unsigned i;
+ for (i = 0; i < Privileges.size(); ++i)
+ {
+ ChanServ::Privilege &priv = Privileges[i];
+
+ if (priv.rank > p.rank)
+ break;
+ }
+
+ Privileges.insert(Privileges.begin() + i, p);
+ }
+
+ void RemovePrivilege(ChanServ::Privilege &p) override
+ {
+ std::vector<ChanServ::Privilege>::iterator it = std::find(Privileges.begin(), Privileges.end(), p);
+ if (it != Privileges.end())
+ Privileges.erase(it);
+
+ for (auto& cit : GetChannels())
+ {
+ ChanServ::Channel *ci = cit.second;
+ ci->RemoveLevel(p.name);
+ }
+ }
+
+ ChanServ::Privilege *FindPrivilege(const Anope::string &name) override
+ {
+ for (unsigned i = Privileges.size(); i > 0; --i)
+ if (Privileges[i - 1].name.equals_ci(name))
+ return &Privileges[i - 1];
+ return NULL;
+ }
+
+ std::vector<ChanServ::Privilege> &GetPrivileges() override
+ {
+ return Privileges;
+ }
+
+ void ClearPrivileges() override
+ {
+ Privileges.clear();
+ }
+
+ void OnReload(Configuration::Conf *conf) override
+ {
+ const Anope::string &channick = conf->GetModule(this)->Get<Anope::string>("client");
+
+ if (channick.empty())
+ throw ConfigException(Module::name + ": <client> must be defined");
+
+ ServiceBot *bi = ServiceBot::Find(channick, true);
+ if (!bi)
+ throw ConfigException(Module::name + ": no bot named " + channick);
+
+ ChanServ = bi;
+
+ ClearPrivileges();
+ for (int i = 0; i < conf->CountBlock("privilege"); ++i)
+ {
+ Configuration::Block *privilege = conf->GetBlock("privilege", i);
+
+ const Anope::string &nname = privilege->Get<Anope::string>("name"),
+ &desc = privilege->Get<Anope::string>("desc");
+ int rank = privilege->Get<int>("rank");
+ Anope::string value = privilege->Get<Anope::string>("level");
+ int level;
+ if (value.equals_ci("founder"))
+ level = ChanServ::ACCESS_FOUNDER;
+ else if (value.equals_ci("disabled"))
+ level = ChanServ::ACCESS_INVALID;
+ else
+ level = privilege->Get<int>("level");
+
+ AddPrivilege(ChanServ::Privilege(nname, desc, rank, level));
+ }
+
+ spacesepstream(conf->GetModule(this)->Get<Anope::string>("defaults", "greet fantasy")).GetTokens(defaults);
+ if (defaults.empty())
+ defaults = { "keeptopic", "secure", "securefounder", "signkick" };
+ else if (defaults[0].equals_ci("none"))
+ defaults.clear();
+
+ always_lower = conf->GetModule(this)->Get<bool>("always_lower_ts");
+ }
+
+ void OnChannelCreate(Channel *c) override
+ {
+ c->ci = Find(c->name);
+ if (c->ci)
+ c->ci->c = c;
+ }
+
+ void OnBotDelete(ServiceBot *bi) override
+ {
+ if (bi == ChanServ)
+ ChanServ = NULL;
+ }
+
+ EventReturn OnBotPrivmsg(User *u, ServiceBot *bi, Anope::string &message) override
+ {
+ if (bi == ChanServ && Config->GetModule(this)->Get<bool>("opersonly") && !u->HasMode("OPER"))
+ {
+ u->SendMessage(bi, _("Access denied."));
+ return EVENT_STOP;
+ }
+
+ return EVENT_CONTINUE;
+ }
+
+ void OnDelCore(NickServ::Account *nc) override
+ {
+ unsigned int max_reg = Config->GetModule(this)->Get<unsigned int>("maxregistered");
+ for (ChanServ::Channel *ci : nc->GetRefs<ChanServ::Channel *>())
+ {
+ if (ci->GetFounder() == nc)
+ {
+ NickServ::Account *newowner = NULL;
+ if (ci->GetSuccessor() && ci->GetSuccessor() != nc && (ci->GetSuccessor()->GetOper() || !max_reg || ci->GetSuccessor()->GetChannelCount() < max_reg))
+ newowner = ci->GetSuccessor();
+ else
+ {
+ ChanServ::ChanAccess *highest = NULL;
+ for (unsigned j = 0; j < ci->GetAccessCount(); ++j)
+ {
+ ChanServ::ChanAccess *ca = ci->GetAccess(j);
+ NickServ::Account *anc = ca->GetAccount();
+
+ if (!anc || (!anc->GetOper() && max_reg && anc->GetChannelCount() >= max_reg) || (anc == nc))
+ continue;
+ if (!highest || *ca > *highest)
+ highest = ca;
+ }
+ if (highest)
+ newowner = highest->GetAccount();
+ }
+
+ if (newowner)
+ {
+ ChanServ->logger.Category("chanserv/drop").Log(_("Transferring foundership of {0} from deleted account {1} to {2}"),
+ ci->GetName(), nc->GetDisplay(), newowner->GetDisplay());
+
+ ci->SetFounder(newowner);
+
+ // Can't be both founder and successor
+ if (ci->GetSuccessor() == newowner)
+ ci->SetSuccessor(nullptr);
+ }
+ else
+ {
+ ChanServ->logger.Category("chanserv/drop").Log(_("Deleting channel {0} owned by deleted account {1}"),
+ ci->GetName(), nc->GetDisplay());
+
+ ci->Delete();
+ continue;
+ }
+ }
+ }
+ }
+
+ void OnDelChan(ChanServ::Channel *ci) override
+ {
+ if (ci->c)
+ {
+ ci->c->RemoveMode(ci->WhoSends(), "REGISTERED", "", false);
+
+ const Anope::string &require = Config->GetModule(this)->Get<Anope::string>("require");
+ if (!require.empty())
+ ci->c->SetModes(ci->WhoSends(), false, "-%s", require.c_str());
+ }
+ }
+
+ EventReturn OnPreHelp(CommandSource &source, const std::vector<Anope::string> &params) override
+ {
+ if (!params.empty() || source.c || source.service != *ChanServ)
+ return EVENT_CONTINUE;
+ source.Reply(_("\002{0}\002 allows you to register and control various\n"
+ "aspects of channels. {1} can often prevent\n"
+ "malicious users from \"taking over\" channels by limiting\n"
+ "who is allowed channel operator privileges. Available\n"
+ "commands are listed below; to use them, type\n"
+ "\002{2}{3} \037command\037\002. For more information on a\n"
+ "specific command, type \002{4}{5} HELP \037command\037\002.\n"),
+ ChanServ->nick, ChanServ->nick, Config->StrictPrivmsg, ChanServ->nick, Config->StrictPrivmsg, ChanServ->nick, ChanServ->nick, source.GetCommand());
+ return EVENT_CONTINUE;
+ }
+
+ void OnPostHelp(CommandSource &source, const std::vector<Anope::string> &params) override
+ {
+ if (!params.empty() || source.c || source.service != *ChanServ)
+ return;
+ time_t chanserv_expire = Config->GetModule(this)->Get<time_t>("expire", "14d");
+ if (chanserv_expire >= 86400)
+ source.Reply(_(" \n"
+ "Note that any channel which is not used for %d days\n"
+ "(i.e. which no user on the channel's access list enters\n"
+ "for that period of time) will be automatically dropped."), chanserv_expire / 86400);
+ if (source.IsServicesOper())
+ source.Reply(_(" \n"
+ "Services Operators can also, depending on their access drop\n"
+ "any channel, view (and modify) the access, levels and akick\n"
+ "lists and settings for any channel."));
+ }
+
+ void OnCheckModes(Reference<Channel> &c) override
+ {
+ if (!c)
+ return;
+
+ if (c->ci)
+ c->SetMode(nullptr, "REGISTERED", "", false);
+ else
+ c->RemoveMode(nullptr, "REGISTERED", "", false);
+
+ const Anope::string &require = Config->GetModule(this)->Get<Anope::string>("require");
+ if (!require.empty())
+ {
+ if (c->ci)
+ c->SetModes(nullptr, false, "+%s", require.c_str());
+ else
+ c->SetModes(nullptr, false, "-%s", require.c_str());
+ }
+ }
+
+ EventReturn OnCanSet(User *u, const ChannelMode *cm) override
+ {
+ if (Config->GetModule(this)->Get<Anope::string>("nomlock").find(cm->mchar) != Anope::string::npos
+ || Config->GetModule(this)->Get<Anope::string>("require").find(cm->mchar) != Anope::string::npos)
+ return EVENT_STOP;
+ return EVENT_CONTINUE;
+ }
+
+ void OnChannelSync(Channel *c) override
+ {
+ bool perm = c->HasMode("PERM") || (c->ci && c->ci->IsPersist());
+ if (!perm && !c->botchannel && (c->users.empty() || (c->users.size() == 1 && c->users.begin()->second->user->server == Me)))
+ {
+ this->Hold(c);
+ }
+ }
+
+ void OnLog(Logger *l) override
+ {
+#warning ""
+#if 0
+ if (l->type == LogType::CHANNEL)
+ l->bi = ChanServ;
+#endif
+ }
+
+ void OnExpireTick() override
+ {
+ time_t chanserv_expire = Config->GetModule(this)->Get<time_t>("expire", "14d");
+
+ if (!chanserv_expire || Anope::NoExpire || Anope::ReadOnly)
+ return;
+
+ for (ChanServ::Channel *ci : Serialize::GetObjects<ChanServ::Channel *>())
+ {
+ bool expire = false;
+
+ if (Anope::CurTime - ci->GetLastUsed() >= chanserv_expire)
+ {
+ if (ci->c)
+ {
+ time_t last_used = ci->GetLastUsed();
+ for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end && last_used == ci->GetLastUsed(); ++cit)
+ ci->AccessFor(cit->second->user);
+ expire = last_used == ci->GetLastUsed();
+ }
+ else
+ expire = true;
+ }
+
+ EventManager::Get()->Dispatch(&ChanServ::Event::PreChanExpire::OnPreChanExpire, ci, expire);
+
+ if (expire)
+ {
+ ChanServ->logger.Category("chanserv/expire").Log(_("Expiring channel {0} (founder: {1})"),
+ ci->GetName(), ci->GetFounder() ? ci->GetFounder()->GetDisplay() : "(none)");
+
+ EventManager::Get()->Dispatch(&ChanServ::Event::ChanExpire::OnChanExpire, ci);
+ ci->Delete();
+ }
+ }
+ }
+
+ EventReturn OnCheckDelete(Channel *c) override
+ {
+ /* Do not delete this channel if ChanServ/a BotServ bot is inhabiting it */
+ if (inhabit.HasExt(c))
+ return EVENT_STOP;
+
+ return EVENT_CONTINUE;
+ }
+
+ void OnPreUplinkSync(Server *serv) override
+ {
+ /* Find all persistent channels and create them, as we are about to finish burst to our uplink */
+ for (ChanServ::Channel *ci : Serialize::GetObjects<ChanServ::Channel *>())
+ {
+ if (ci->IsPersist())
+ {
+ bool c;
+ ci->c = Channel::FindOrCreate(ci->GetName(), c, ci->GetTimeRegistered());
+
+ if (ModeManager::FindChannelModeByName("PERM") != NULL)
+ {
+ if (c)
+ IRCD->Send<messages::MessageChannel>(ci->c);
+ ci->c->SetMode(NULL, "PERM");
+ }
+ else
+ {
+ if (!ci->GetBot())
+ {
+ ServiceBot *bi = ci->WhoSends();
+ if (bi != nullptr)
+ bi->Assign(nullptr, ci);
+ }
+
+ if (ci->GetBot() != nullptr && ci->c->FindUser(ci->GetBot()) == nullptr)
+ {
+ Anope::string botmodes = Config->GetModule("botserv/main")->Get<Anope::string>("botmodes",
+ Config->GetModule("chanserv/main")->Get<Anope::string>("botmodes"));
+ ChannelStatus status(botmodes);
+ ci->GetBot()->Join(ci->c, &status);
+ }
+ }
+ }
+ }
+
+ }
+
+ void OnChanRegistered(ChanServ::Channel *ci) override
+ {
+ /* Set default chan flags */
+ for (unsigned i = 0; i < defaults.size(); ++i)
+ ci->SetS<bool>(defaults[i].upper(), true);
+
+ if (!ci->c)
+ return;
+ /* Mark the channel as persistent */
+ if (ci->c->HasMode("PERM"))
+ ci->SetPersist(true);
+ /* Persist may be in def cflags, set it here */
+ else if (ci->IsPersist())
+ ci->c->SetMode(NULL, "PERM");
+ }
+
+ void OnJoinChannel(User *u, Channel *c) override
+ {
+ if (always_lower && c->ci && c->creation_time > c->ci->GetTimeRegistered())
+ {
+ logger.Debug("Changing TS of {0} from {1} to {2}", c->name, c->creation_time, c->ci->GetTimeRegistered());
+ c->creation_time = c->ci->GetTimeRegistered();
+ IRCD->Send<messages::MessageChannel>(c);
+ c->Reset();
+ }
+ }
+
+ EventReturn OnChannelModeSet(Channel *c, const MessageSource &setter, ChannelMode *mode, const Anope::string &param) override
+ {
+ if (!always_lower && Anope::CurTime == c->creation_time && c->ci && setter.GetUser() && !setter.GetUser()->server->IsULined())
+ {
+ ChanUserContainer *cu = c->FindUser(setter.GetUser());
+ ChannelMode *cm = ModeManager::FindChannelModeByName("OP");
+ if (cu && cm && !cu->status.HasMode(cm->mchar))
+ {
+ /* Our -o and their mode change crossing, bounce their mode */
+ c->RemoveMode(nullptr, mode, param);
+ /* We don't set mlocks until after the join has finished processing, it will stack with this change,
+ * so there isn't much for the user to remove except -nt etc which is likely locked anyway.
+ */
+ }
+ }
+
+ return EVENT_CONTINUE;
+ }
+
+ void OnChanInfo(CommandSource &source, ChanServ::Channel *ci, InfoFormatter &info, bool show_all) override
+ {
+ if (!show_all)
+ return;
+
+ time_t chanserv_expire = Config->GetModule(this)->Get<time_t>("expire", "14d");
+ if (!ci->IsNoExpire() && chanserv_expire && !Anope::NoExpire && ci->GetLastUsed() != Anope::CurTime)
+ info[_("Expires")] = Anope::strftime(ci->GetLastUsed() + chanserv_expire, source.GetAccount());
+ }
+
+ void OnSetCorrectModes(User *user, Channel *chan, ChanServ::AccessGroup &access, bool &give_modes, bool &take_modes) override
+ {
+ if (always_lower)
+ // Since we always lower the TS, the other side will remove the modes if the channel ts lowers, so we don't
+ // have to worry about it
+ take_modes = false;
+ else if (ModeManager::FindChannelModeByName("REGISTERED"))
+ // Otherwise if the registered channel mode exists, we should remove modes if the channel is not +r
+ take_modes = !chan->HasMode("REGISTERED");
+ }
+};
+
+MODULE_INIT(ChanServCore)
+