summaryrefslogtreecommitdiff
path: root/modules/pseudoclients/chanserv
diff options
context:
space:
mode:
authorAdam <Adam@anope.org>2014-05-28 12:07:29 -0400
committerAdam <Adam@anope.org>2014-05-28 12:07:54 -0400
commitf29e1cf383529a1a29f02b0669d973f5ee0b7a66 (patch)
tree3c33db276dc9328235bbd572641521ed44458176 /modules/pseudoclients/chanserv
parent1253c70e0682fa1490c99c3d2869049a0e3fa2e0 (diff)
Move most of the core pseudoclient logic to modules
Diffstat (limited to 'modules/pseudoclients/chanserv')
-rw-r--r--modules/pseudoclients/chanserv/CMakeLists.txt1
-rw-r--r--modules/pseudoclients/chanserv/channel.cpp605
-rw-r--r--modules/pseudoclients/chanserv/channel.h41
-rw-r--r--modules/pseudoclients/chanserv/chanserv.cpp736
4 files changed, 1383 insertions, 0 deletions
diff --git a/modules/pseudoclients/chanserv/CMakeLists.txt b/modules/pseudoclients/chanserv/CMakeLists.txt
new file mode 100644
index 000000000..781f0ef1f
--- /dev/null
+++ b/modules/pseudoclients/chanserv/CMakeLists.txt
@@ -0,0 +1 @@
+build_subdir(${CMAKE_CURRENT_SOURCE_DIR})
diff --git a/modules/pseudoclients/chanserv/channel.cpp b/modules/pseudoclients/chanserv/channel.cpp
new file mode 100644
index 000000000..5364075ae
--- /dev/null
+++ b/modules/pseudoclients/chanserv/channel.cpp
@@ -0,0 +1,605 @@
+/* Registered channel functions
+ *
+ * (C) 2003-2014 Anope Team
+ * Contact us at team@anope.org
+ *
+ * Please read COPYING and README for further details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ *
+ */
+
+#include "module.h"
+#include "channel.h"
+#include "modules/cs_akick.h"
+
+ChannelImpl::ChannelImpl(const Anope::string &chname)
+{
+ if (chname.empty())
+ throw CoreException("Empty channel passed to ChanServ::Channel constructor");
+
+ this->founder = NULL;
+ this->successor = NULL;
+ this->c = ::Channel::Find(chname);
+ if (this->c)
+ this->c->ci = this;
+ this->banexpire = 0;
+ this->bi = NULL;
+ this->last_topic_time = 0;
+
+ this->name = chname;
+
+ this->bantype = 2;
+ this->last_used = this->time_registered = Anope::CurTime;
+
+ ChanServ::registered_channel_map& map = ChanServ::service->GetChannels();
+ Channel* &ci = map[chname];
+ if (ci)
+ Log(LOG_DEBUG) << "Duplicate channel " << this->name << " in registered channel table?";
+ ci = this;
+
+ Event::OnCreateChan(&Event::CreateChan::OnCreateChan, this);
+}
+
+ChannelImpl::ChannelImpl(const ChanServ::Channel &ci)
+{
+ *this = ci;
+
+ if (this->founder)
+ --this->founder->channelcount;
+
+ this->access->clear();
+ this->akick->clear();
+
+ for (unsigned i = 0; i < ci.GetAccessCount(); ++i)
+ {
+ const ChanServ::ChanAccess *taccess = ci.GetAccess(i);
+ ChanServ::AccessProvider *provider = taccess->provider;
+
+ ChanServ::ChanAccess *newaccess = provider->Create();
+ newaccess->ci = this;
+ newaccess->mask = taccess->mask;
+ newaccess->creator = taccess->creator;
+ newaccess->last_seen = taccess->last_seen;
+ newaccess->created = taccess->created;
+ newaccess->AccessUnserialize(taccess->AccessSerialize());
+
+ this->AddAccess(newaccess);
+ }
+
+ for (unsigned i = 0; i < ci.GetAkickCount(); ++i)
+ {
+ const AutoKick *takick = ci.GetAkick(i);
+ if (takick->nc)
+ this->AddAkick(takick->creator, takick->nc, takick->reason, takick->addtime, takick->last_used);
+ else
+ this->AddAkick(takick->creator, takick->mask, takick->reason, takick->addtime, takick->last_used);
+ }
+
+ Event::OnCreateChan(&Event::CreateChan::OnCreateChan, this);
+}
+
+ChannelImpl::~ChannelImpl()
+{
+ Event::OnDelChan(&Event::DelChan::OnDelChan, this);
+
+ Log(LOG_DEBUG) << "Deleting channel " << this->name;
+
+ if (this->c)
+ {
+ if (this->bi && this->c->FindUser(this->bi))
+ this->bi->Part(this->c);
+
+ /* Parting the service bot can cause the channel to go away */
+
+ if (this->c)
+ {
+ if (this->c && this->c->CheckDelete())
+ delete this->c;
+
+ this->c = NULL;
+ }
+ }
+
+ ChanServ::registered_channel_map& map = ChanServ::service->GetChannels();
+ map.erase(this->name);
+
+ this->SetFounder(NULL);
+ this->SetSuccessor(NULL);
+
+ this->ClearAccess();
+ this->ClearAkick();
+
+ if (this->memos)
+ {
+ for (unsigned i = 0, end = this->memos->memos->size(); i < end; ++i)
+ delete this->memos->GetMemo(i);
+ this->memos->memos->clear();
+ delete memos;
+ }
+
+ if (this->founder)
+ --this->founder->channelcount;
+}
+
+void ChannelImpl::Serialize(Serialize::Data &data) const
+{
+ data["name"] << this->name;
+ if (this->founder)
+ data["founder"] << this->founder->display;
+ if (this->successor)
+ data["successor"] << this->successor->display;
+ data["description"] << this->desc;
+ data.SetType("time_registered", Serialize::Data::DT_INT); data["time_registered"] << this->time_registered;
+ data.SetType("last_used", Serialize::Data::DT_INT); data["last_used"] << this->last_used;
+ data["last_topic"] << this->last_topic;
+ data["last_topic_setter"] << this->last_topic_setter;
+ data.SetType("last_topic_time", Serialize::Data::DT_INT); data["last_topic_time"] << this->last_topic_time;
+ data.SetType("bantype", Serialize::Data::DT_INT); data["bantype"] << this->bantype;
+ {
+ Anope::string levels_buffer;
+ for (Anope::map<int16_t>::const_iterator it = this->levels.begin(), it_end = this->levels.end(); it != it_end; ++it)
+ levels_buffer += it->first + " " + stringify(it->second) + " ";
+ data["levels"] << levels_buffer;
+ }
+ if (this->bi)
+ data["bi"] << this->bi->nick;
+ data.SetType("banexpire", Serialize::Data::DT_INT); data["banexpire"] << this->banexpire;
+ if (memos)
+ {
+ data["memomax"] << this->memos->memomax;
+ for (unsigned i = 0; i < this->memos->ignores.size(); ++i)
+ data["memoignores"] << this->memos->ignores[i] << " ";
+ }
+
+ Extensible::ExtensibleSerialize(this, this, data);
+}
+
+Serializable* ChannelImpl::Unserialize(Serializable *obj, Serialize::Data &data)
+{
+ Anope::string sname, sfounder, ssuccessor, slevels, sbi;
+
+ data["name"] >> sname;
+ data["founder"] >> sfounder;
+ data["successor"] >> ssuccessor;
+ data["levels"] >> slevels;
+ data["bi"] >> sbi;
+
+ ChannelImpl *ci;
+ if (obj)
+ ci = anope_dynamic_static_cast<ChannelImpl *>(obj);
+ else
+ ci = new ChannelImpl(sname);
+
+ ci->SetFounder(NickServ::FindAccount(sfounder));
+ ci->SetSuccessor(NickServ::FindAccount(ssuccessor));
+
+ data["description"] >> ci->desc;
+ data["time_registered"] >> ci->time_registered;
+ data["last_used"] >> ci->last_used;
+ data["last_topic"] >> ci->last_topic;
+ data["last_topic_setter"] >> ci->last_topic_setter;
+ data["last_topic_time"] >> ci->last_topic_time;
+ data["bantype"] >> ci->bantype;
+ {
+ std::vector<Anope::string> v;
+ spacesepstream(slevels).GetTokens(v);
+ for (unsigned i = 0; i + 1 < v.size(); i += 2)
+ try
+ {
+ ci->levels[v[i]] = convertTo<int16_t>(v[i + 1]);
+ }
+ catch (const ConvertException &) { }
+ }
+ BotInfo *bi = BotInfo::Find(sbi);
+ if (*ci->bi != bi)
+ {
+ if (bi)
+ bi->Assign(NULL, ci);
+ else if (ci->bi)
+ ci->bi->UnAssign(NULL, ci);
+ }
+ data["banexpire"] >> ci->banexpire;
+ if (ci->memos)
+ {
+ data["memomax"] >> ci->memos->memomax;
+ {
+ Anope::string buf;
+ data["memoignores"] >> buf;
+ spacesepstream sep(buf);
+ ci->memos->ignores.clear();
+ while (sep.GetToken(buf))
+ ci->memos->ignores.push_back(buf);
+ }
+ }
+
+ Extensible::ExtensibleUnserialize(ci, ci, data);
+
+ /* compat */
+ bool b;
+ b = false;
+ data["extensible:SECURE"] >> b;
+ if (b)
+ ci->Extend<bool>("CS_SECURE");
+ b = false;
+ data["extensible:PRIVATE"] >> b;
+ if (b)
+ ci->Extend<bool>("CS_PRIVATE");
+ b = false;
+ data["extensible:NO_EXPIRE"] >> b;
+ if (b)
+ ci->Extend<bool>("CS_NO_EXPIRE");
+ b = false;
+ data["extensible:FANTASY"] >> b;
+ if (b)
+ ci->Extend<bool>("BS_FANTASY");
+ b = false;
+ data["extensible:GREET"] >> b;
+ if (b)
+ ci->Extend<bool>("BS_GREET");
+ b = false;
+ data["extensible:PEACE"] >> b;
+ if (b)
+ ci->Extend<bool>("PEACE");
+ b = false;
+ data["extensible:SECUREFOUNDER"] >> b;
+ if (b)
+ ci->Extend<bool>("SECUREFOUNDER");
+ b = false;
+ data["extensible:RESTRICTED"] >> b;
+ if (b)
+ ci->Extend<bool>("RESTRICTED");
+ b = false;
+ data["extensible:KEEPTOPIC"] >> b;
+ if (b)
+ ci->Extend<bool>("KEEPTOPIC");
+ b = false;
+ data["extensible:SIGNKICK"] >> b;
+ if (b)
+ ci->Extend<bool>("SIGNKICK");
+ b = false;
+ data["extensible:SIGNKICK_LEVEL"] >> b;
+ if (b)
+ ci->Extend<bool>("SIGNKICK_LEVEL");
+ /* end compat */
+
+ return ci;
+}
+
+
+void ChannelImpl::SetFounder(NickServ::Account *nc)
+{
+ if (this->founder)
+ {
+ --this->founder->channelcount;
+ this->founder->RemoveChannelReference(this);
+ }
+
+ this->founder = nc;
+
+ if (this->founder)
+ {
+ ++this->founder->channelcount;
+ this->founder->AddChannelReference(this);
+ }
+}
+
+NickServ::Account *ChannelImpl::GetFounder() const
+{
+ return this->founder;
+}
+
+void ChannelImpl::SetSuccessor(NickServ::Account *nc)
+{
+ if (this->successor)
+ this->successor->RemoveChannelReference(this);
+ this->successor = nc;
+ if (this->successor)
+ this->successor->AddChannelReference(this);
+}
+
+NickServ::Account *ChannelImpl::GetSuccessor() const
+{
+ return this->successor;
+}
+
+BotInfo *ChannelImpl::WhoSends() const
+{
+ if (this && this->bi)
+ return this->bi;
+
+ BotInfo *ChanServ = Config->GetClient("ChanServ");
+ if (ChanServ)
+ return ChanServ;
+
+ if (!BotListByNick->empty())
+ return BotListByNick->begin()->second;
+
+ return NULL;
+}
+
+void ChannelImpl::AddAccess(ChanServ::ChanAccess *taccess)
+{
+ this->access->push_back(taccess);
+
+ const NickServ::Nick *na = NickServ::FindNick(taccess->mask);
+ if (na != NULL)
+ {
+ na->nc->AddChannelReference(this);
+ taccess->nc = na->nc;
+ }
+ else
+ {
+ ChanServ::Channel *ci = ChanServ::Find(taccess->mask);
+ if (ci != NULL)
+ ci->AddChannelReference(this->name);
+ }
+}
+
+ChanServ::ChanAccess *ChannelImpl::GetAccess(unsigned index) const
+{
+ if (this->access->empty() || index >= this->access->size())
+ return NULL;
+
+ ChanServ::ChanAccess *acc = (*this->access)[index];
+ acc->QueueUpdate();
+ return acc;
+}
+
+ChanServ::AccessGroup ChannelImpl::AccessFor(const User *u)
+{
+ ChanServ::AccessGroup group;
+
+ if (u == NULL)
+ return group;
+
+ const NickServ::Account *nc = u->Account();
+ if (nc == NULL && !this->HasExt("NS_SECURE") && u->IsRecognized())
+ {
+ const NickServ::Nick *na = NickServ::FindNick(u->nick);
+ if (na != NULL)
+ nc = na->nc;
+ }
+
+ group.super_admin = u->super_admin;
+ group.founder = IsFounder(u);
+ group.ci = this;
+ group.nc = nc;
+
+ for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i)
+ {
+ ChanServ::ChanAccess *a = this->GetAccess(i);
+ if (a->Matches(u, u->Account(), group.path))
+ group.push_back(a);
+ }
+
+ if (group.founder || !group.empty())
+ {
+ this->last_used = Anope::CurTime;
+
+ for (unsigned i = 0; i < group.size(); ++i)
+ group[i]->last_seen = Anope::CurTime;
+ }
+
+ return group;
+}
+
+ChanServ::AccessGroup ChannelImpl::AccessFor(const NickServ::Account *nc)
+{
+ ChanServ::AccessGroup group;
+
+ group.founder = (this->founder && this->founder == nc);
+ group.ci = this;
+ group.nc = nc;
+
+ for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i)
+ {
+ ChanServ::ChanAccess *a = this->GetAccess(i);
+ if (a->Matches(NULL, nc, group.path))
+ group.push_back(a);
+ }
+
+ if (group.founder || !group.empty())
+ this->last_used = Anope::CurTime;
+
+ /* don't update access last seen here, this isn't the user requesting access */
+
+ return group;
+}
+
+unsigned ChannelImpl::GetAccessCount() const
+{
+ return this->access->size();
+}
+
+unsigned ChannelImpl::GetDeepAccessCount() const
+{
+ ChanServ::ChanAccess::Path path;
+ for (unsigned i = 0, end = this->GetAccessCount(); i < end; ++i)
+ {
+ ChanServ::ChanAccess *a = this->GetAccess(i);
+ a->Matches(NULL, NULL, path);
+ }
+
+ unsigned count = this->GetAccessCount();
+ std::set<const ChanServ::Channel *> channels;
+ channels.insert(this);
+ for (ChanServ::ChanAccess::Set::iterator it = path.first.begin(); it != path.first.end(); ++it)
+ {
+ const ChanServ::Channel *ci = it->first->ci;
+ if (!channels.count(ci))
+ {
+ channels.count(ci);
+ count += ci->GetAccessCount();
+ }
+ }
+ return count;
+}
+
+ChanServ::ChanAccess *ChannelImpl::EraseAccess(unsigned index)
+{
+ if (this->access->empty() || index >= this->access->size())
+ return NULL;
+
+ ChanServ::ChanAccess *ca = this->access->at(index);
+ this->access->erase(this->access->begin() + index);
+ return ca;
+}
+
+void ChannelImpl::ClearAccess()
+{
+ for (unsigned i = this->access->size(); i > 0; --i)
+ delete this->GetAccess(i - 1);
+}
+
+AutoKick *ChannelImpl::AddAkick(const Anope::string &user, NickServ::Account *akicknc, const Anope::string &reason, time_t t, time_t lu)
+{
+ if (!::akick)
+ return nullptr;
+
+ AutoKick *autokick = ::akick->Create();
+ autokick->ci = this;
+ autokick->nc = akicknc;
+ autokick->reason = reason;
+ autokick->creator = user;
+ autokick->addtime = t;
+ autokick->last_used = lu;
+
+ this->akick->push_back(autokick);
+
+ akicknc->AddChannelReference(this);
+
+ return autokick;
+}
+
+AutoKick *ChannelImpl::AddAkick(const Anope::string &user, const Anope::string &mask, const Anope::string &reason, time_t t, time_t lu)
+{
+ if (!::akick)
+ return nullptr;
+
+ AutoKick *autokick = ::akick->Create();
+ autokick->ci = this;
+ autokick->mask = mask;
+ autokick->nc = NULL;
+ autokick->reason = reason;
+ autokick->creator = user;
+ autokick->addtime = t;
+ autokick->last_used = lu;
+
+ this->akick->push_back(autokick);
+
+ return autokick;
+}
+
+AutoKick *ChannelImpl::GetAkick(unsigned index) const
+{
+ if (this->akick->empty() || index >= this->akick->size())
+ return NULL;
+
+ AutoKick *ak = (*this->akick)[index];
+ ak->QueueUpdate();
+ return ak;
+}
+
+unsigned ChannelImpl::GetAkickCount() const
+{
+ return this->akick->size();
+}
+
+void ChannelImpl::EraseAkick(unsigned index)
+{
+ if (this->akick->empty() || index >= this->akick->size())
+ return;
+
+ delete this->GetAkick(index);
+}
+
+void ChannelImpl::ClearAkick()
+{
+ while (!this->akick->empty())
+ delete this->akick->back();
+}
+
+int16_t ChannelImpl::GetLevel(const Anope::string &priv) const
+{
+ if (!ChanServ::service || !ChanServ::service->FindPrivilege(priv))
+ {
+ Log(LOG_DEBUG) << "Unknown privilege " + priv;
+ return ChanServ::ACCESS_INVALID;
+ }
+
+ Anope::map<int16_t>::const_iterator it = this->levels.find(priv);
+ if (it == this->levels.end())
+ return 0;
+ return it->second;
+}
+
+void ChannelImpl::SetLevel(const Anope::string &priv, int16_t level)
+{
+ this->levels[priv] = level;
+}
+
+void ChannelImpl::RemoveLevel(const Anope::string &priv)
+{
+ this->levels.erase(priv);
+}
+
+void ChannelImpl::ClearLevels()
+{
+ this->levels.clear();
+}
+
+Anope::string ChannelImpl::GetIdealBan(User *u) const
+{
+ int bt = this ? this->bantype : -1;
+ switch (bt)
+ {
+ case 0:
+ return "*!" + u->GetVIdent() + "@" + u->GetDisplayedHost();
+ case 1:
+ if (u->GetVIdent()[0] == '~')
+ return "*!*" + u->GetVIdent() + "@" + u->GetDisplayedHost();
+ else
+ return "*!" + u->GetVIdent() + "@" + u->GetDisplayedHost();
+ case 3:
+ return "*!" + u->Mask();
+ case 2:
+ default:
+ return "*!*@" + u->GetDisplayedHost();
+ }
+}
+
+bool ChannelImpl::IsFounder(const User *user)
+{
+ if (!user)
+ return false;
+
+ if (user->super_admin)
+ return true;
+
+ if (user->Account() && user->Account() == this->GetFounder())
+ return true;
+
+ return false;
+}
+
+
+void ChannelImpl::AddChannelReference(const Anope::string &what)
+{
+ ++references[what];
+}
+
+void ChannelImpl::RemoveChannelReference(const Anope::string &what)
+{
+ int &i = references[what];
+ if (--i <= 0)
+ references.erase(what);
+}
+
+void ChannelImpl::GetChannelReferences(std::deque<Anope::string> &chans)
+{
+ chans.clear();
+ for (Anope::map<int>::iterator it = references.begin(); it != references.end(); ++it)
+ chans.push_back(it->first);
+}
diff --git a/modules/pseudoclients/chanserv/channel.h b/modules/pseudoclients/chanserv/channel.h
new file mode 100644
index 000000000..c9a85dba6
--- /dev/null
+++ b/modules/pseudoclients/chanserv/channel.h
@@ -0,0 +1,41 @@
+
+
+class ChannelImpl : public ChanServ::Channel
+{
+ public:
+ ChannelImpl(const Anope::string &chname);
+ ChannelImpl(const ChanServ::Channel &ci);
+ ~ChannelImpl();
+
+ void Serialize(Serialize::Data &data) const override;
+ static Serializable* Unserialize(Serializable *obj, Serialize::Data &);
+
+ bool IsFounder(const User *user) override;
+ void SetFounder(NickServ::Account *nc) override;
+ NickServ::Account *GetFounder() const override;
+ void SetSuccessor(NickServ::Account *nc) override;
+ NickServ::Account *GetSuccessor() const override;
+ BotInfo *WhoSends() const override;
+ void AddAccess(ChanServ::ChanAccess *access) override;
+ ChanServ::ChanAccess *GetAccess(unsigned index) const override;
+ ChanServ::AccessGroup AccessFor(const User *u) override;
+ ChanServ::AccessGroup AccessFor(const NickServ::Account *nc) override;
+ unsigned GetAccessCount() const override;
+ unsigned GetDeepAccessCount() const override;
+ ChanServ::ChanAccess *EraseAccess(unsigned index) override;
+ void ClearAccess() override;
+ AutoKick* AddAkick(const Anope::string &user, NickServ::Account *akicknc, const Anope::string &reason, time_t t = Anope::CurTime, time_t lu = 0) override;
+ AutoKick* AddAkick(const Anope::string &user, const Anope::string &mask, const Anope::string &reason, time_t t = Anope::CurTime, time_t lu = 0) override;
+ AutoKick* GetAkick(unsigned index) const override;
+ unsigned GetAkickCount() const override;
+ void EraseAkick(unsigned index) override;
+ void ClearAkick() override;
+ int16_t GetLevel(const Anope::string &priv) const override;
+ void SetLevel(const Anope::string &priv, int16_t level) override;
+ void RemoveLevel(const Anope::string &priv) override;
+ void ClearLevels() override;
+ Anope::string GetIdealBan(User *u) const override;
+ void AddChannelReference(const Anope::string &what) override;
+ void RemoveChannelReference(const Anope::string &what) override;
+ void GetChannelReferences(std::deque<Anope::string> &chans) override;
+};
diff --git a/modules/pseudoclients/chanserv/chanserv.cpp b/modules/pseudoclients/chanserv/chanserv.cpp
new file mode 100644
index 000000000..d4f3aa1c7
--- /dev/null
+++ b/modules/pseudoclients/chanserv/chanserv.cpp
@@ -0,0 +1,736 @@
+/* ChanServ core functions
+ *
+ * (C) 2003-2014 Anope Team
+ * Contact us at team@anope.org
+ *
+ * Please read COPYING and README for further details.
+ *
+ * Based on the original code of Epona by Lara.
+ * Based on the original code of Services by Andy Church.
+ */
+
+#include "module.h"
+#include "channel.h"
+#include "modules/cs_mode.h"
+#include "modules/help.h"
+#include "modules/bs_bot.h"
+#include "modules/chanserv.h"
+#include "modules/cs_info.h"
+#include "modules/cs_akick.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::CreateChan>
+ , 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<BotInfo> ChanServ;
+ std::vector<Anope::string> defaults;
+ ExtensibleItem<bool> inhabit;
+ ExtensibleRef<bool> persist;
+ bool always_lower;
+ EventHandlers<ChanServ::Event::PreChanExpire> OnPreChanExpire;
+ EventHandlers<ChanServ::Event::ChanExpire> OnChanExpire;
+ std::vector<ChanServ::Privilege> Privileges;
+ std::vector<ChanServ::AccessProvider *> Providers;
+ Serialize::Checker<ChanServ::registered_channel_map> registered_channels;
+ Serialize::Type channel_type, access_type;
+
+ public:
+ ChanServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR)
+ , ChanServService(this)
+ , EventHook<Event::ChannelCreate>("OnChannelCreate")
+ , EventHook<Event::BotDelete>("OnBotDelete")
+ , EventHook<Event::BotPrivmsg>("OnBotPrivmsg")
+ , EventHook<Event::DelCore>("OnDelCore")
+ , EventHook<Event::DelChan>("OnDelChan")
+ , EventHook<Event::Help>("OnHelp")
+ , EventHook<Event::CheckModes>("OnCheckModes")
+ , EventHook<Event::CreateChan>("OnCreateChan")
+ , EventHook<Event::CanSet>("OnCanSet")
+ , EventHook<Event::ChannelSync>("OnChannelSync")
+ , EventHook<Event::Log>("OnLog")
+ , EventHook<Event::ExpireTick>("OnExpireTick")
+ , EventHook<Event::CheckDelete>("OnCheckDelete")
+ , EventHook<Event::PreUplinkSync>("OnPreUplinkSync")
+ , EventHook<Event::ChanRegistered>("OnChanRegistered")
+ , EventHook<Event::JoinChannel>("OnJoinChannel")
+ , EventHook<Event::ChannelModeSet>("OnChannelModeSet")
+ , EventHook<Event::ChanInfo>("OnChanInfo")
+ , EventHook<Event::SetCorrectModes>("OnSetCorrectModes")
+ , inhabit(this, "inhabit")
+ , persist("PERSIST")
+ , always_lower(false)
+ , OnPreChanExpire(this, "OnPreChanExpire")
+ , OnChanExpire(this, "OnChanExpire")
+ , registered_channels("ChannelInfo")
+ , channel_type("ChannelInfo", ChannelImpl::Unserialize)
+ , access_type("ChanAccess", Unserialize)
+ {
+ }
+
+ ChanServ::Channel *Create(const Anope::string &name) override
+ {
+ return new ChannelImpl(name);
+ }
+
+ ChanServ::Channel *Create(const ChanServ::Channel &ci) override
+ {
+ return new ChannelImpl(ci);
+ }
+
+ ChanServ::Channel *Find(const Anope::string &name) override
+ {
+ auto it = registered_channels->find(name);
+ return it != registered_channels->end() ? it->second : nullptr;
+ }
+
+ 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<BotInfo> &ChanServ;
+ ExtensibleItem<bool> &inhabit;
+ Reference<Channel> c;
+
+ public:
+ /** Constructor
+ * @param chan The channel
+ */
+ ChanServTimer(Reference<BotInfo> &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->bi)
+ ChanServ->Join(c);
+ else if (!c->FindUser(c->ci->bi))
+ c->ci->bi->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->bi)
+ {
+ 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->bi->Part(c);
+ }
+ };
+
+ if (inhabit.HasExt(c))
+ return;
+
+ new ChanServTimer(ChanServ, inhabit, this->owner, 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->QueueUpdate();
+ 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();
+ }
+
+ std::vector<ChanServ::AccessProvider *>& GetProviders() override
+ {
+ return Providers;
+ }
+
+ void Destruct(ChanServ::ChanAccess *access) override
+ {
+ if (access->ci)
+ {
+ std::vector<ChanServ::ChanAccess *>::iterator it = std::find(access->ci->access->begin(), access->ci->access->end(), access);
+ if (it != access->ci->access->end())
+ access->ci->access->erase(it);
+
+ const NickServ::Nick *na = NickServ::FindNick(access->mask);
+ if (na != NULL)
+ na->nc->RemoveChannelReference(access->ci);
+ else
+ {
+ ChanServ::Channel *c = this->Find(access->mask);
+ if (c)
+ c->RemoveChannelReference(access->ci->name);
+ }
+ }
+ }
+
+ void Serialize(const ChanServ::ChanAccess *, Serialize::Data &data) override
+ {
+ }
+
+ bool Matches(const ChanServ::ChanAccess *access, const User *u, const NickServ::Account *acc, ChanServ::ChanAccess::Path &p) override
+ {
+ if (access->nc)
+ return access->nc == acc;
+
+ if (u)
+ {
+ bool is_mask = access->mask.find_first_of("!@?*") != Anope::string::npos;
+ if (is_mask && Anope::Match(u->nick, access->mask))
+ return true;
+ else if (Anope::Match(u->GetDisplayedMask(), access->mask))
+ return true;
+ }
+
+ if (acc)
+ {
+ for (unsigned i = 0; i < acc->aliases->size(); ++i)
+ {
+ const NickServ::Nick *na = acc->aliases->at(i);
+ if (Anope::Match(na->nick, access->mask))
+ return true;
+ }
+ }
+
+ if (IRCD->IsChannelValid(access->mask))
+ {
+ ChanServ::Channel *tci = Find(access->mask);
+ if (tci)
+ {
+ for (unsigned i = 0; i < tci->GetAccessCount(); ++i)
+ {
+ ChanServ::ChanAccess *a = tci->GetAccess(i);
+ std::pair<const ChanServ::ChanAccess *, const ChanServ::ChanAccess *> pair = std::make_pair(access, a);
+
+ std::pair<Set::iterator, Set::iterator> range = p.first.equal_range(access);
+ for (; range.first != range.second; ++range.first)
+ if (range.first->first == pair.first && range.first->second == pair.second)
+ goto cont;
+
+ p.first.insert(pair);
+ if (a->Matches(u, acc, p))
+ p.second.insert(pair);
+
+ cont:;
+ }
+
+ return p.second.count(access) > 0;
+ }
+ }
+
+ return false;
+ }
+
+ static Serializable* Unserialize(Serializable *obj, Serialize::Data &data)
+ {
+ Anope::string provider, chan;
+
+ data["provider"] >> provider;
+ data["ci"] >> chan;
+
+ ServiceReference<ChanServ::AccessProvider> aprovider("AccessProvider", provider);
+ ChanServ::Channel *ci = ChanServ::service->Find(chan);
+ if (!aprovider || !ci)
+ return NULL;
+
+ ChanServ::ChanAccess *access;
+ if (obj)
+ access = anope_dynamic_static_cast<ChanServ::ChanAccess *>(obj);
+ else
+ access = aprovider->Create();
+ access->ci = ci;
+ data["mask"] >> access->mask;
+ data["creator"] >> access->creator;
+ data["last_seen"] >> access->last_seen;
+ data["created"] >> access->created;
+
+ Anope::string adata;
+ data["data"] >> adata;
+ access->AccessUnserialize(adata);
+
+ if (!obj)
+ ci->AddAccess(access);
+ return access;
+ }
+
+ void OnReload(Configuration::Conf *conf) override
+ {
+ const Anope::string &channick = conf->GetModule(this)->Get<const Anope::string>("client");
+
+ if (channick.empty())
+ throw ConfigException(Module::name + ": <client> must be defined");
+
+ BotInfo *bi = BotInfo::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<const Anope::string>("name"),
+ &desc = privilege->Get<const Anope::string>("desc");
+ int rank = privilege->Get<int>("rank");
+
+ AddPrivilege(ChanServ::Privilege(nname, desc, rank));
+ }
+
+ spacesepstream(conf->GetModule(this)->Get<const Anope::string>("defaults", "greet fantasy")).GetTokens(defaults);
+ if (defaults.empty())
+ {
+ defaults.push_back("KEEPTOPIC");
+ defaults.push_back("CS_SECURE");
+ defaults.push_back("SECUREFOUNDER");
+ defaults.push_back("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(BotInfo *bi) override
+ {
+ if (bi == ChanServ)
+ ChanServ = NULL;
+ }
+
+ EventReturn OnBotPrivmsg(User *u, BotInfo *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
+ {
+ std::deque<ChanServ::Channel *> chans;
+ nc->GetChannelReferences(chans);
+ int max_reg = Config->GetModule(this)->Get<int>("maxregistered");
+
+ for (unsigned i = 0; i < chans.size(); ++i)
+ {
+ ChanServ::Channel *ci = chans[i];
+
+ if (ci->GetFounder() == nc)
+ {
+ NickServ::Account *newowner = NULL;
+ if (ci->GetSuccessor() && ci->GetSuccessor() != nc && (ci->GetSuccessor()->IsServicesOper() || !max_reg || ci->GetSuccessor()->channelcount < max_reg))
+ newowner = ci->GetSuccessor();
+ else
+ {
+ const ChanServ::ChanAccess *highest = NULL;
+ for (unsigned j = 0; j < ci->GetAccessCount(); ++j)
+ {
+ const ChanServ::ChanAccess *ca = ci->GetAccess(j);
+ const NickServ::Account *anc = NickServ::FindAccount(ca->mask);
+
+ if (!anc || (!anc->IsServicesOper() && max_reg && anc->channelcount >= max_reg) || (anc == nc))
+ continue;
+ if (!highest || *ca > *highest)
+ highest = ca;
+ }
+ if (highest)
+ newowner = NickServ::FindAccount(highest->mask);
+ }
+
+ if (newowner)
+ {