From 0e758a2ac23dc4a001e8e126cec14588da9a9769 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 28 Jul 2016 21:29:35 -0400 Subject: Allow serializable fields to use storage in the respective objects. Split service management code nito a proper servicemanager. Make service references managed instead of lazy lookup. Also made events and serializable use service manager instead of their respective systems for management --- modules/CMakeLists.txt | 57 +- modules/botserv/assign.cpp | 4 +- modules/botserv/autoassign.cpp | 1 + modules/botserv/badwords.cpp | 33 +- modules/botserv/bot.cpp | 13 +- modules/botserv/botlist.cpp | 2 +- modules/botserv/info.cpp | 12 +- modules/botserv/kick.cpp | 159 +++-- modules/botserv/main/botserv.cpp | 29 +- modules/botserv/set.cpp | 3 +- modules/chanserv/CMakeLists.txt | 2 + modules/chanserv/access.cpp | 912 ++++++++++++++++++++++++++ modules/chanserv/akick.cpp | 43 +- modules/chanserv/clone.cpp | 11 +- modules/chanserv/cs_access.old | 941 --------------------------- modules/chanserv/cs_flags.old | 528 --------------- modules/chanserv/cs_xop.old | 659 ------------------- modules/chanserv/drop.cpp | 10 +- modules/chanserv/entrymsg.cpp | 31 +- modules/chanserv/flags.cpp | 531 +++++++++++++++ modules/chanserv/info.cpp | 10 +- modules/chanserv/list.cpp | 9 +- modules/chanserv/log.cpp | 48 +- modules/chanserv/main/chanaccess.cpp | 58 +- modules/chanserv/main/chanaccess.h | 30 - modules/chanserv/main/chanaccesstype.h | 19 +- modules/chanserv/main/channel.cpp | 68 +- modules/chanserv/main/channel.h | 17 +- modules/chanserv/main/channeltype.cpp | 32 +- modules/chanserv/main/channeltype.h | 28 +- modules/chanserv/main/chanserv.cpp | 70 +- modules/chanserv/main/level.h | 6 + modules/chanserv/main/leveltype.h | 8 +- modules/chanserv/main/mode.h | 5 + modules/chanserv/main/modetype.h | 8 +- modules/chanserv/mode.cpp | 66 +- modules/chanserv/register.cpp | 15 +- modules/chanserv/seen.cpp | 1 + modules/chanserv/set.cpp | 91 +-- modules/chanserv/set_misc.cpp | 22 +- modules/chanserv/statusupdate.cpp | 8 +- modules/chanserv/suspend.cpp | 57 +- modules/chanserv/topic.cpp | 13 +- modules/chanserv/unban.cpp | 2 +- modules/chanserv/xop.cpp | 658 +++++++++++++++++++ modules/database/CMakeLists.txt | 1 + modules/database/flatfile.cpp | 7 +- modules/database/old.cpp | 140 ++-- modules/database/redis.cpp | 28 +- modules/database/sql.cpp | 100 ++- modules/dns.cpp | 3 +- modules/dnsbl.cpp | 17 +- modules/encryption/bcrypt.cpp | 2 + modules/encryption/md5.cpp | 2 + modules/encryption/none.cpp | 3 + modules/encryption/old.cpp | 13 +- modules/encryption/sha1.cpp | 2 + modules/encryption/sha256.cpp | 2 + modules/extra/ldap_authentication.cpp | 2 +- modules/extra/mysql.cpp | 5 +- modules/extra/sasl_dh-aes.cpp | 2 +- modules/extra/sasl_dh-blowfish.cpp | 2 +- modules/extra/sql_authentication.cpp | 2 +- modules/extra/stats/chanstats.cpp | 4 +- modules/fantasy.cpp | 21 +- modules/global/global.cpp | 6 +- modules/global/main/global.cpp | 4 + modules/greet.cpp | 9 +- modules/help.cpp | 16 +- modules/helpchan.cpp | 1 + modules/hostserv/del.cpp | 20 +- modules/hostserv/group.cpp | 6 +- modules/hostserv/list.cpp | 2 +- modules/hostserv/main/hostserv.cpp | 5 + modules/hostserv/request.cpp | 99 +-- modules/hostserv/set.cpp | 6 +- modules/httpd.cpp | 8 +- modules/memoserv/cancel.cpp | 5 +- modules/memoserv/del.cpp | 9 +- modules/memoserv/ignore.cpp | 4 +- modules/memoserv/main/ignore.h | 5 + modules/memoserv/main/ignoretype.cpp | 6 +- modules/memoserv/main/memo.h | 9 +- modules/memoserv/main/memoinfo.cpp | 4 +- modules/memoserv/main/memoinfo.h | 5 + modules/memoserv/main/memoinfotype.cpp | 6 +- modules/memoserv/main/memoserv.cpp | 49 +- modules/memoserv/main/memotype.cpp | 14 +- modules/memoserv/rsend.cpp | 3 +- modules/memoserv/set.cpp | 10 +- modules/nickserv/access.cpp | 31 +- modules/nickserv/ajoin.cpp | 28 +- modules/nickserv/alist.cpp | 2 +- modules/nickserv/cert.cpp | 33 +- modules/nickserv/drop.cpp | 10 +- modules/nickserv/group.cpp | 44 +- modules/nickserv/identify.cpp | 6 +- modules/nickserv/info.cpp | 23 +- modules/nickserv/list.cpp | 7 +- modules/nickserv/logout.cpp | 3 +- modules/nickserv/main/account.cpp | 10 +- modules/nickserv/main/account.h | 4 + modules/nickserv/main/accounttype.cpp | 13 +- modules/nickserv/main/identifyrequest.cpp | 11 + modules/nickserv/main/identifyrequest.h | 1 + modules/nickserv/main/mode.h | 5 + modules/nickserv/main/modetype.h | 6 +- modules/nickserv/main/nick.cpp | 8 +- modules/nickserv/main/nick.h | 8 + modules/nickserv/main/nickserv.cpp | 67 +- modules/nickserv/main/nicktype.cpp | 28 +- modules/nickserv/maxemail.cpp | 7 +- modules/nickserv/recover.cpp | 8 +- modules/nickserv/register.cpp | 45 +- modules/nickserv/resetpass.cpp | 3 +- modules/nickserv/set.cpp | 71 +- modules/nickserv/set_misc.cpp | 22 +- modules/nickserv/suspend.cpp | 57 +- modules/nickserv/update.cpp | 10 +- modules/operserv/akill.cpp | 25 +- modules/operserv/chankill.cpp | 13 +- modules/operserv/defcon.cpp | 59 +- modules/operserv/dns.cpp | 187 +++--- modules/operserv/forbid.cpp | 49 +- modules/operserv/ignore.cpp | 28 +- modules/operserv/info.cpp | 28 +- modules/operserv/login.cpp | 3 +- modules/operserv/logsearch.cpp | 2 +- modules/operserv/main/operserv.cpp | 46 +- modules/operserv/modinfo.cpp | 15 +- modules/operserv/module.cpp | 2 +- modules/operserv/news.cpp | 34 +- modules/operserv/noop.cpp | 3 +- modules/operserv/oper.cpp | 2 +- modules/operserv/reload.cpp | 2 +- modules/operserv/session.cpp | 67 +- modules/operserv/set.cpp | 2 +- modules/operserv/stats.cpp | 108 ++- modules/operserv/sxline.cpp | 30 +- modules/protocol/bahamut.cpp | 9 +- modules/protocol/charybdis.cpp | 2 +- modules/protocol/hybrid.cpp | 23 +- modules/protocol/inspircd20.cpp | 34 +- modules/protocol/ngircd.cpp | 3 +- modules/protocol/plexus.cpp | 11 +- modules/protocol/ratbox.cpp | 8 +- modules/protocol/unreal.cpp | 35 +- modules/proxyscan.cpp | 380 ----------- modules/redis.cpp | 7 +- modules/sasl.cpp | 85 ++- modules/webcpanel/pages/chanserv/access.cpp | 6 +- modules/webcpanel/pages/chanserv/drop.cpp | 2 +- modules/webcpanel/pages/chanserv/utils.cpp | 2 +- modules/webcpanel/pages/hostserv/request.cpp | 2 +- modules/webcpanel/pages/index.cpp | 2 +- modules/webcpanel/pages/logout.cpp | 4 +- modules/webcpanel/pages/nickserv/access.cpp | 2 +- modules/webcpanel/pages/nickserv/alist.cpp | 2 +- modules/webcpanel/pages/nickserv/cert.cpp | 5 +- modules/webcpanel/pages/nickserv/info.cpp | 2 +- modules/webcpanel/pages/operserv/akill.cpp | 5 +- modules/webcpanel/pages/operserv/akill.h | 2 + modules/webcpanel/webcpanel.cpp | 6 +- modules/webcpanel/webcpanel.h | 6 +- modules/xmlrpc.cpp | 6 +- modules/xmlrpc_main.cpp | 9 +- 166 files changed, 4014 insertions(+), 3921 deletions(-) create mode 100644 modules/chanserv/access.cpp delete mode 100644 modules/chanserv/cs_access.old delete mode 100644 modules/chanserv/cs_flags.old delete mode 100644 modules/chanserv/cs_xop.old create mode 100644 modules/chanserv/flags.cpp delete mode 100644 modules/chanserv/main/chanaccess.h create mode 100644 modules/chanserv/xop.cpp delete mode 100644 modules/proxyscan.cpp (limited to 'modules') diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 1851f47b9..7a31c1ccb 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -22,9 +22,11 @@ macro(build_modules SRC) file(RELATIVE_PATH DEST "${Anope_SOURCE_DIR}/modules" ${SRC}) if(DEST) - set(TARG "module_${DEST}.${SO}") + set(TARG "anope_${DEST}.${SO}") + set(OUT_NAME "${DEST}_${SO}") else() - set(TARG "module_${SO}") + set(TARG "anope_${SO}") + set(OUT_NAME "${SO}") endif() # Reset linker flags @@ -40,14 +42,14 @@ macro(build_modules SRC) set(WIN32_MEMORY) endif() # Generate the module and set its linker flags, also set it to depend on the main Anope executable to be built beforehand - add_library(${TARG} MODULE ${MODULE_SRC}) + add_library(${TARG} SHARED ${MODULE_SRC}) # Windows requires this because it's weird if(WIN32) set(WIN32_NO_LIBS "/nodefaultlib:\"libcmt.lib\" /OPT:NOREF") else() set(WIN32_NO_LIBS) endif() - set_target_properties(${TARG} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" LINK_FLAGS "${TEMP_LDFLAGS} ${WIN32_NO_LIBS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON OUTPUT_NAME "${SO}") + set_target_properties(${TARG} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" OUTPUT_NAME "${OUT_NAME}" LINK_FLAGS "${TEMP_LDFLAGS} ${WIN32_NO_LIBS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON) add_dependencies(${TARG} ${PROGRAM_NAME}) if(GETTEXT_FOUND) add_dependencies(${TARG} module_language) @@ -60,15 +62,41 @@ macro(build_modules SRC) elseif(APPLE) target_link_libraries(${TARG} ${PROGRAM_NAME}) endif() - calculate_dependencies(${MODULE_SRC}) # Set the module to be installed to the module directory under the data directory - install(TARGETS ${TARG} DESTINATION ${LIB_DIR}/modules/${DEST}) + install(TARGETS ${TARG} DESTINATION ${LIB_DIR}/modules) endif() endif() endforeach() endif() endmacro() +macro(build_modules_dependencies SRC) + file(GLOB MODULES_SRCS "${SRC}/*") + foreach(MODULE_SRC ${MODULES_SRCS}) + if(NOT IS_DIRECTORY "${MODULE_SRC}") + string(REGEX MATCH "\\.cpp$" CPP ${MODULE_SRC}) + if(CPP) + file(RELATIVE_PATH FNAME ${SRC} ${MODULE_SRC}) + # Convert the real source file extension to have a .so extension + string(REGEX REPLACE "\\.cpp$" "" SO ${FNAME}) + + file(RELATIVE_PATH DEST "${Anope_SOURCE_DIR}/modules" ${SRC}) + if(DEST) + set(TARG "anope_${DEST}.${SO}") + else() + set(TARG "anope_${SO}") + endif() + + set(DEPS) + calculate_dependencies(${MODULE_SRC} DEPS) + foreach(DEP ${DEPS}) + target_link_libraries(${TARG} ${DEP}) + endforeach() + endif() + endif() + endforeach() +endmacro() + macro(build_subdir) file(GLOB_RECURSE MODULES_SUBDIR_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cpp") list(SORT MODULES_SUBDIR_SRCS) @@ -77,14 +105,15 @@ macro(build_subdir) get_filename_component(DIR_NAME ${DEST} DIRECTORY) get_filename_component(FOLDER_NAME ${DEST} NAME) if(DIR_NAME) - set(SO "${DIR_NAME}.${FOLDER_NAME}") + set(SO "anope_${DIR_NAME}.${FOLDER_NAME}") else() - set(SO "${FOLDER_NAME}") + set(SO "anope_${FOLDER_NAME}") endif() # Set all the files to use C++ as well as set their compile flags (use the module-specific compile flags, though) set_source_files_properties(${MODULES_SUBDIR_SRCS} PROPERTIES LANGUAGE CXX COMPILE_FLAGS "${CXXFLAGS}") + set(DEPS) # Iterate through the source files in the subdirectory foreach(SRC ${MODULES_SUBDIR_SRCS}) # Reset linker flags @@ -93,7 +122,7 @@ macro(build_subdir) set(TEMP_DEPENDENCIES) # Calculate the library dependencies for the given source file calculate_libraries(${SRC} SKIP_LIBRARIES MODULE TEMP_LDFLAGS TEMP_DEPENDENCIES) - calculate_dependencies(${SRC}) + calculate_dependencies(${SRC} DEPS) # Append this source file's linker flags to the subdirectoy's linker flags, if there are any to append if(TEMP_DEPENDENCIES) @@ -119,13 +148,16 @@ macro(build_subdir) endif() # Generate the module and set its linker flags, also set it to depend on the main Anope executable to be built beforehand - add_library(${SO} MODULE ${MODULES_SUBDIR_SRCS}) - set_target_properties(${SO} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" LINK_FLAGS "${SUBDIR_LDFLAGS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON OUTPUT_NAME "${FOLDER_NAME}") + add_library(${SO} SHARED ${MODULES_SUBDIR_SRCS}) + set_target_properties(${SO} PROPERTIES LINKER_LANGUAGE CXX PREFIX "" LINK_FLAGS "${SUBDIR_LDFLAGS}" INSTALL_RPATH_USE_LINK_PATH ON BUILD_WITH_INSTALL_RPATH ON OUTPUT_NAME "${DIR_NAME}_${FOLDER_NAME}") add_dependencies(${SO} ${PROGRAM_NAME}) if(GETTEXT_FOUND) add_dependencies(${SO} module_language) endif() target_link_libraries(${SO} ${SUBDIR_EXTRA_DEPENDS}) + foreach(DEP ${DEPS}) + target_link_libraries(${SO} ${DEP}) + endforeach() # For Windows only, have the module link to the export library of Anope as well as wsock32 and Ws2_32 libraries (most of the modules probably don't need this, but this is to be on the safe side), also set its version if(WIN32) target_link_libraries(${SO} ${PROGRAM_NAME} wsock32 Ws2_32 ${WIN32_MEMORY}) @@ -134,8 +166,9 @@ macro(build_subdir) target_link_libraries(${SO} ${PROGRAM_NAME}) endif() - install(TARGETS ${SO} DESTINATION ${LIB_DIR}/modules/${DIR_NAME}) + install(TARGETS ${SO} DESTINATION ${LIB_DIR}/modules) endmacro() build_modules(${CMAKE_CURRENT_SOURCE_DIR}) +build_modules_dependencies(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/botserv/assign.cpp b/modules/botserv/assign.cpp index 1d2755ad5..96e8b0821 100644 --- a/modules/botserv/assign.cpp +++ b/modules/botserv/assign.cpp @@ -227,8 +227,10 @@ class BSAssign : public Module public: BSAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , EventHook(this) + , EventHook(this) - , nobot(this, botinfo, "BS_NOBOT") + , nobot(this, "BS_NOBOT") , commandbsassign(this) , commandbsunassign(this) diff --git a/modules/botserv/autoassign.cpp b/modules/botserv/autoassign.cpp index 8978499ed..4b5074abe 100644 --- a/modules/botserv/autoassign.cpp +++ b/modules/botserv/autoassign.cpp @@ -14,6 +14,7 @@ class BSAutoAssign : public Module { public: BSAutoAssign(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , EventHook(this) { } diff --git a/modules/botserv/badwords.cpp b/modules/botserv/badwords.cpp index afdc2e09f..94cf8803a 100644 --- a/modules/botserv/badwords.cpp +++ b/modules/botserv/badwords.cpp @@ -12,24 +12,25 @@ #include "module.h" #include "modules/botserv/badwords.h" -static EventHandlers *bwevents; - class BadWordImpl : public BadWord { + friend class BadWordsType; + + ChanServ::Channel *channel = nullptr; + Anope::string word; + BadWordType type; + public: BadWordImpl(Serialize::TypeBase *type) : BadWord(type) { } BadWordImpl(Serialize::TypeBase *type, Serialize::ID id) : BadWord(type, id) { } ChanServ::Channel *GetChannel() override; - void SetChannel(ChanServ::Channel *c) override; Anope::string GetWord() override; - void SetWord(const Anope::string &w) override; BadWordType GetType() override; - void SetType(const BadWordType &t) override; }; @@ -40,10 +41,10 @@ class BadWordsType : public Serialize::Type Serialize::Field word; Serialize::Field type; - BadWordsType(Module *me) : Serialize::Type(me, "BadWord") - , channel(this, "ci", true) - , word(this, "word") - , type(this, "type") + BadWordsType(Module *me) : Serialize::Type(me) + , channel(this, "ci", &BadWordImpl::channel, true) + , word(this, "word", &BadWordImpl::word) + , type(this, "type", &BadWordImpl::type) { } }; @@ -84,19 +85,19 @@ struct BadWordsImpl : BadWords BadWord* AddBadWord(ChanServ::Channel *ci, const Anope::string &word, BadWordType type) override { - BadWord *bw = badword.Create(); + BadWord *bw = Serialize::New(); bw->SetChannel(ci); bw->SetWord(word); bw->SetType(type); - (*bwevents)(&Event::BadWordEvents::OnBadWordAdd, ci, bw); + EventManager::Get()->Dispatch(&Event::BadWordEvents::OnBadWordAdd, ci, bw); return bw; } std::vector GetBadWords(ChanServ::Channel *ci) override { - return ci->GetRefs(badword); + return ci->GetRefs(); } BadWord* GetBadWord(ChanServ::Channel *ci, unsigned index) override @@ -117,7 +118,7 @@ struct BadWordsImpl : BadWords return; BadWord *bw = bws[index]; - (*bwevents)(&Event::BadWordEvents::OnBadWordDel, ci, bw); + EventManager::Get()->Dispatch(&Event::BadWordEvents::OnBadWordDel, ci, bw); delete bw; } @@ -131,7 +132,8 @@ struct BadWordsImpl : BadWords class CommandBSBadwords : public Command { - private: + ServiceReference badwords; + void DoList(CommandSource &source, ChanServ::Channel *ci, const Anope::string &word) { bool override = !source.AccessFor(ci).HasPriv("BADWORDS"); @@ -448,17 +450,14 @@ class BSBadwords : public Module { CommandBSBadwords commandbsbadwords; BadWordsImpl badwords; - EventHandlers events; BadWordsType bwtype; public: BSBadwords(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) , commandbsbadwords(this) , badwords(this) - , events(this) , bwtype(this) { - bwevents = &events; } }; diff --git a/modules/botserv/bot.cpp b/modules/botserv/bot.cpp index b2630bfcb..027df87fa 100644 --- a/modules/botserv/bot.cpp +++ b/modules/botserv/bot.cpp @@ -14,10 +14,6 @@ class CommandBSBot : public Command { - EventHandlers OnBotCreate; - EventHandlers OnBotChange; - EventHandlers OnBotDelete; - void DoAdd(CommandSource &source, const std::vector ¶ms) { const Anope::string &nick = params[1]; @@ -93,7 +89,7 @@ class CommandBSBot : public Command source.Reply(_("\002{0}!{1}@{2}\002 (\002{3}\002) added to the bot list."), bi->nick, bi->GetIdent(), bi->host, bi->realname); - this->OnBotCreate(&Event::BotCreate::OnBotCreate, bi); + EventManager::Get()->Dispatch(&Event::BotCreate::OnBotCreate, bi); } void DoChange(CommandSource &source, const std::vector ¶ms) @@ -247,7 +243,7 @@ class CommandBSBot : public Command source.Reply(_("Bot \002{0}\002 has been changed to \002{1}!{2}@{3}\002 (\002{4}\002)."), oldnick, bi->nick, bi->GetIdent(), bi->host, bi->realname); Log(LOG_ADMIN, source, this) << "CHANGE " << oldnick << " to " << bi->GetMask() << " " << bi->realname; - this->OnBotChange(&Event::BotChange::OnBotChange, bi); + EventManager::Get()->Dispatch(&Event::BotChange::OnBotChange, bi); } void DoDel(CommandSource &source, const std::vector ¶ms) @@ -273,7 +269,7 @@ class CommandBSBot : public Command return; } - this->OnBotDelete(&Event::BotDelete::OnBotDelete, bi); + EventManager::Get()->Dispatch(&Event::BotDelete::OnBotDelete, bi); Log(LOG_ADMIN, source, this) << "DEL " << bi->nick; @@ -283,9 +279,6 @@ class CommandBSBot : public Command public: CommandBSBot(Module *creator) : Command(creator, "botserv/bot", 1, 6) - , OnBotCreate(creator) - , OnBotChange(creator) - , OnBotDelete(creator) { this->SetDesc(_("Maintains network bot list")); this->SetSyntax(_("\002ADD \037nicknae\037 \037username\037 \037hostname\037 \037realname\037\002")); diff --git a/modules/botserv/botlist.cpp b/modules/botserv/botlist.cpp index ce899719e..26b23a9fc 100644 --- a/modules/botserv/botlist.cpp +++ b/modules/botserv/botlist.cpp @@ -26,7 +26,7 @@ class CommandBSBotList : public Command list.AddColumn(_("Nick")).AddColumn(_("Mask")); - for (BotInfo *bi : Serialize::GetObjects(botinfo)) + for (BotInfo *bi : Serialize::GetObjects()) { if (source.HasPriv("botserv/administration") || !bi->GetOperOnly()) { diff --git a/modules/botserv/info.cpp b/modules/botserv/info.cpp index 19fe0a775..85100a4ec 100644 --- a/modules/botserv/info.cpp +++ b/modules/botserv/info.cpp @@ -16,10 +16,8 @@ class CommandBSInfo : public Command { - EventHandlers &onbotinfo; - public: - CommandBSInfo(Module *creator, EventHandlers &event) : Command(creator, "botserv/info", 1, 1), onbotinfo(event) + CommandBSInfo(Module *creator) : Command(creator, "botserv/info", 1, 1) { this->SetSyntax(_("{\037channel\037 | \037nickname\037}")); } @@ -41,7 +39,7 @@ class CommandBSInfo : public Command info[_("Options")] = bi->bi->GetOperOnly() ? _("Private") : _("None"); info[_("Used on")] = stringify(bi->GetChannelCount()) + " channel(s)"; - this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); + EventManager::Get()->Dispatch(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); std::vector replies; info.Process(replies); @@ -72,7 +70,7 @@ class CommandBSInfo : public Command Anope::string enabled = Language::Translate(source.nc, _("Enabled")); Anope::string disabled = Language::Translate(source.nc, _("Disabled")); - this->onbotinfo(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); + EventManager::Get()->Dispatch(&Event::ServiceBotEvent::OnServiceBot, source, bi, ci, info); std::vector replies; info.Process(replies); @@ -102,12 +100,10 @@ class CommandBSInfo : public Command class BSInfo : public Module { CommandBSInfo commandbsinfo; - EventHandlers onbotinfo; public: BSInfo(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) - , commandbsinfo(this, onbotinfo) - , onbotinfo(this) + , commandbsinfo(this) { } diff --git a/modules/botserv/kick.cpp b/modules/botserv/kick.cpp index f61c7eb94..00fd36b88 100644 --- a/modules/botserv/kick.cpp +++ b/modules/botserv/kick.cpp @@ -1,14 +1,9 @@ /* BotServ core functions * - * (C) 2003-2014 Anope Team + * (C) 2003-2016 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" @@ -35,6 +30,41 @@ enum TTBType class KickerDataImpl : public KickerData { + friend class KickerDataType; + + ChanServ::Channel *channel = nullptr; + + bool amsgs = false, + badwords = false, + bolds = false, + caps = false, + colors = false, + flood = false, + italics = false, + repeat = false, + reverses = false, + underlines = false; + + int16_t ttb_bolds = 0, + ttb_colors = 0, + ttb_reverses = 0, + ttb_underlines = 0, + ttb_badwords = 0, + ttb_caps = 0, + ttb_flood = 0, + ttb_repeat = 0, + ttb_italics = 0, + ttb_amsgs = 0; + + int16_t capsmin = 0, + capspercent = 0, + floodlines = 0, + floodsecs = 0, + repeattimes = 0; + + bool dontkickops = false, + dontkickvoices = false; + public: KickerDataImpl(Serialize::TypeBase *type) : KickerData(type) { } KickerDataImpl(Serialize::TypeBase *type, Serialize::ID id) : KickerData(type, id) { } @@ -128,42 +158,66 @@ class KickerDataType : public Serialize::Type { public: Serialize::ObjectField channel; - Serialize::Field amsgs, badwords, bolds, caps, colors, flood, italics, repeat, reverses, underlines; - Serialize::Field ttb_bolds, ttb_colors, ttb_reverses, ttb_underlines, ttb_badwords, ttb_caps, ttb_flood, ttb_repeat, ttb_italics, ttb_amsgs, - capsmin, capspercent, - floodlines, floodsecs, - repeattimes; - Serialize::Field dontkickops, dontkickvoices; - - KickerDataType(Module *owner) : Serialize::Type(owner, "KickerData") - , channel(this, "channel", true) - , amsgs(this, "amsgs") - , badwords(this, "badwords") - , bolds(this, "bolds") - , caps(this, "caps") - , colors(this, "colors") - , flood(this, "flood") - , italics(this, "italics") - , repeat(this, "repeat") - , reverses(this, "reverses") - , underlines(this, "underlines") - , ttb_bolds(this, "ttb_bolds") - , ttb_colors(this, "ttb_colors") - , ttb_reverses(this, "ttb_reverses") - , ttb_underlines(this, "ttb_underlines") - , ttb_badwords(this, "ttb_badwords") - , ttb_caps(this, "ttb_caps") - , ttb_flood(this, "ttb_flood") - , ttb_repeat(this, "ttb_repeat") - , ttb_italics(this, "ttb_italics") - , ttb_amsgs(this, "ttb_amsgs") - , capsmin(this, "capsmin") - , capspercent(this, "capspercent") - , floodlines(this, "floodlines") - , floodsecs(this, "floodsecs") - , repeattimes(this, "repeattimes") - , dontkickops(this, "dontkickops") - , dontkickvoices(this, "dontkickvoices") + + Serialize::Field amsgs, + badwords, + bolds, + caps, + colors, + flood, + italics, + repeat, + reverses, + underlines; + + Serialize::Field ttb_bolds, + ttb_colors, + ttb_reverses, + ttb_underlines, + ttb_badwords, + ttb_caps, + ttb_flood, + ttb_repeat, + ttb_italics, + ttb_amsgs, + capsmin, + capspercent, + floodlines, + floodsecs, + repeattimes; + + Serialize::Field dontkickops, + dontkickvoices; + + KickerDataType(Module *owner) : Serialize::Type(owner) + , channel(this, "channel", &KickerDataImpl::channel, true) + , amsgs(this, "amsgs", &KickerDataImpl::amsgs) + , badwords(this, "badwords", &KickerDataImpl::badwords) + , bolds(this, "bolds", &KickerDataImpl::bolds) + , caps(this, "caps", &KickerDataImpl::caps) + , colors(this, "colors", &KickerDataImpl::colors) + , flood(this, "flood", &KickerDataImpl::flood) + , italics(this, "italics", &KickerDataImpl::italics) + , repeat(this, "repeat", &KickerDataImpl::repeat) + , reverses(this, "reverses", &KickerDataImpl::reverses) + , underlines(this, "underlines", &KickerDataImpl::underlines) + , ttb_bolds(this, "ttb_bolds", &KickerDataImpl::ttb_bolds) + , ttb_colors(this, "ttb_colors", &KickerDataImpl::ttb_colors) + , ttb_reverses(this, "ttb_reverses", &KickerDataImpl::ttb_reverses) + , ttb_underlines(this, "ttb_underlines", &KickerDataImpl::ttb_underlines) + , ttb_badwords(this, "ttb_badwords", &KickerDataImpl::ttb_badwords) + , ttb_caps(this, "ttb_caps", &KickerDataImpl::ttb_caps) + , ttb_flood(this, "ttb_flood", &KickerDataImpl::ttb_flood) + , ttb_repeat(this, "ttb_repeat", &KickerDataImpl::ttb_repeat) + , ttb_italics(this, "ttb_italics", &KickerDataImpl::ttb_italics) + , ttb_amsgs(this, "ttb_amsgs", &KickerDataImpl::ttb_amsgs) + , capsmin(this, "capsmin", &KickerDataImpl::capsmin) + , capspercent(this, "capspercent", &KickerDataImpl::capspercent) + , floodlines(this, "floodlines", &KickerDataImpl::floodlines) + , floodsecs(this, "floodsecs", &KickerDataImpl::floodsecs) + , repeattimes(this, "repeattimes", &KickerDataImpl::repeattimes) + , dontkickops(this, "dontkickops", &KickerDataImpl::dontkickops) + , dontkickvoices(this, "dontkickvoices", &KickerDataImpl::dontkickvoices) { } }; @@ -477,7 +531,7 @@ class CommandBSKick : public Command if (c_name.find_ci(this_name + " ") == 0) { - ServiceReference command("Command", info.name); + ServiceReference command(info.name); if (command) { source.command = c_name; @@ -556,7 +610,7 @@ class CommandBSKickBase : public Command void Process(CommandSource &source, ChanServ::Channel *ci, const Anope::string ¶m, const Anope::string &ttb, void (KickerData::*setter)(const bool &), void (KickerData::*ttbsetter)(const int16_t &), const Anope::string &optname) { - KickerData *kd = ci->GetRef(kickerdata); + KickerData *kd = ci->GetRef(); if (param.equals_ci("ON")) { @@ -1195,7 +1249,7 @@ class BanDataPurger : public Timer { bd->purge(); if (bd->empty()) - c->ShrinkOK("bandata"); + c->Shrink("bandata"); } } } @@ -1226,8 +1280,8 @@ class BSKick : public Module CommandBSSetDontKickVoices commandbssetdontkickvoices; BanDataPurger purger; - - EventHandlers onbotban; + + ServiceReference badwords; BanData::Data &GetBanData(User *u, Channel *c) { @@ -1260,7 +1314,7 @@ class BSKick : public Module Anope::string mask = ci->GetIdealBan(u); ci->c->SetMode(NULL, "BAN", mask); - this->onbotban(&Event::BotBan::OnBotBan, u, ci, mask); + EventManager::Get()->Dispatch(&Event::BotBan::OnBotBan, u, ci, mask); } if (!ci->c->FindUser(u)) @@ -1279,6 +1333,9 @@ class BSKick : public Module public: BSKick(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , EventHook(this) + , EventHook(this) + , bandata(this, "bandata") , userdata(this, "userdata") @@ -1300,8 +1357,6 @@ class BSKick : public Module , commandbssetdontkickvoices(this) , purger(this) - - , onbotban(this) { me = this; } @@ -1313,7 +1368,7 @@ class BSKick : public Module Anope::string enabled = Language::Translate(source.nc, _("Enabled")); Anope::string disabled = Language::Translate(source.nc, _("Disabled")); - KickerData *kd = ci->GetRef(&kdtype); + KickerData *kd = ci->GetRef(); if (kd && kd->GetBadwords()) { @@ -1434,7 +1489,7 @@ class BSKick : public Module ChanServ::Channel *ci = c->ci; if (ci == NULL) return; - KickerData *kd = c->ci->GetRef(&kdtype); + KickerData *kd = c->ci->GetRef(); if (kd == NULL) return; diff --git a/modules/botserv/main/botserv.cpp b/modules/botserv/main/botserv.cpp index 0b51691fc..a297db153 100644 --- a/modules/botserv/main/botserv.cpp +++ b/modules/botserv/main/botserv.cpp @@ -20,17 +20,25 @@ class BotServCore : public Module, public BotServ::BotServService , public EventHook , public EventHook , public EventHook - , public EventHook + , public EventHook , public EventHook , public EventHook { Reference BotServ; - ExtensibleRef persist, inhabit;//XXX? + ExtensibleRef inhabit; public: BotServCore(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, PSEUDOCLIENT | VENDOR) , BotServ::BotServService(this) - , persist("PERSIST") + , EventHook(this) + , EventHook(this) + , EventHook(this) + , EventHook(this) + , EventHook(this) + , EventHook(this) + , EventHook(this) + , EventHook(this) + , EventHook(this) , inhabit("inhabit") { } @@ -119,7 +127,7 @@ class BotServCore : public Module, public BotServ::BotServService void OnLeaveChannel(User *u, Channel *c) override { /* Channel is persistent, it shouldn't be deleted and the service bot should stay */ - if (c->ci && persist && persist->HasExt(c->ci)) + if (c->ci && c->ci->HasFieldS("PERSIST")) return; /* Channel is syncing from a netburst, don't destroy it as more users are probably wanting to join immediately @@ -132,9 +140,14 @@ class BotServCore : public Module, public BotServ::BotServService if (inhabit && inhabit->HasExt(c)) return; - /* This is called prior to removing the user from the channnel, so c->users.size() - 1 should be safe */ - if (c->ci && c->ci->GetBot() && u != c->ci->GetBot() && c->users.size() - 1 <= Config->GetModule(this)->Get("minusers") && c->FindUser(c->ci->GetBot())) - c->ci->GetBot()->Part(c->ci->c); + if (c->ci) + { + ServiceBot *bot = c->ci->GetBot(); + + /* This is called prior to removing the user from the channnel, so c->users.size() - 1 should be safe */ + if (bot && u != bot && c->users.size() - 1 <= Config->GetModule(this)->Get("minusers") && c->FindUser(bot)) + bot->Part(c); + } } EventReturn OnPreHelp(CommandSource &source, const std::vector ¶ms) override @@ -201,7 +214,7 @@ class BotServCore : public Module, public BotServ::BotServService return EVENT_CONTINUE; } - void OnCreateChan(ChanServ::Channel *ci) override + void OnChanRegistered(ChanServ::Channel *ci) override { /* Set default bot flags */ spacesepstream sep(Config->GetModule(this)->Get("defaults", "greet fantasy")); diff --git a/modules/botserv/set.cpp b/modules/botserv/set.cpp index b480108af..ee8426e65 100644 --- a/modules/botserv/set.cpp +++ b/modules/botserv/set.cpp @@ -40,7 +40,7 @@ class CommandBSSet : public Command const CommandInfo &info = it->second; if (c_name.find_ci(this_name + " ") == 0) { - ServiceReference command("Command", info.name); + ServiceReference command(info.name); if (command) { // XXX dup @@ -203,6 +203,7 @@ class BSSet : public Module public: BSSet(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR) + , EventHook(this) , commandbsset(this) , commandbssetbanexpire(this) , commandbssetprivate(this) diff --git a/modules/chanserv/CMakeLists.txt b/modules/chanserv/CMakeLists.txt index cd225a94d..afefd9d26 100644 --- a/modules/chanserv/CMakeLists.txt +++ b/modules/chanserv/CMakeLists.txt @@ -1 +1,3 @@ +#add_subdirs(${CMAKE_CURRENT_SOURCE_DIR}) build_modules(${CMAKE_CURRENT_SOURCE_DIR}) +build_modules_dependencies(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/modules/chanserv/access.cpp b/modules/chanserv/access.cpp new file mode 100644 index 000000000..c7fc9bace --- /dev/null +++ b/modules/chanserv/access.cpp @@ -0,0 +1,912 @@ +/* ChanServ core functions + * + * (C) 2003-2016 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + * + */ + +/* Dependencies: anope_chanserv.main */ + +#include "module.h" +#include "modules/chanserv.h" +#include "modules/chanserv/access.h" +#include "modules/chanserv/main/chanaccess.h" +#include "main/chanaccesstype.h" + +class AccessChanAccessImpl : public AccessChanAccess +{ + friend class AccessChanAccessType; + + int level = 0; + + public: + static constexpr const char *NAME = "accesschanaccess"; + + using AccessChanAccess::AccessChanAccess; + + int GetLevel(); + void SetLevel(const int &); + + bool HasPriv(const Anope::string &name) override + { + return this->GetChannel()->GetLevel(name) != ChanServ::ACCESS_INVALID && this->GetLevel() >= this->GetChannel()->GetLevel(name); + } + + Anope::string AccessSerialize() override + { + return stringify(this->GetLevel()); + } + + void AccessUnserialize(const Anope::string &data) override + { + try + { + this->SetLevel(convertTo(data)); + } + catch (const ConvertException &) + { + } + } + + bool operator>(ChanServ::ChanAccess &other) override + { + if (this->GetSerializableType() != other.GetSerializableType()) + return ChanServ::ChanAccess::operator>(other); + else + return this->GetLevel() > anope_dynamic_static_cast(&other)->GetLevel(); + } + + bool operator<(ChanServ::ChanAccess &other) override + { + if (this->GetSerializableType() != other.GetSerializableType()) + return ChanAccess::operator<(other); + else + return this->GetLevel() < anope_dynamic_static_cast(&other)->GetLevel(); + } +}; + +class AccessChanAccessType : public ChanAccessType +{ + public: + Serialize::Field level; + + AccessChanAccessType(Module *me) : ChanAccessType(me) + , level(this, "level", &AccessChanAccessImpl::level) + { + Serialize::SetParent(AccessChanAccess::NAME, ChanServ::ChanAccess::NAME); + } +}; + +int AccessChanAccess::GetLevel() +{ + return Get(&AccessChanAccessType::level); +} + +void AccessChanAccess::SetLevel(const int &i) +{ + Object::Set(&AccessChanAccessType::level, i); +} + +class CommandCSAccess : public Command +{ + void DoAdd(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + Anope::string mask = params[2]; + ChanServ::Privilege *p = NULL; + int level = ChanServ::ACCESS_INVALID; + + try + { + level = convertTo(params[3]); + } + catch (const ConvertException &) + { + p = ChanServ::service ? ChanServ::service->FindPrivilege(params[3]) : nullptr; + if (p != NULL && p->level) + level = p->level; + } + + if (!level) + { + source.Reply(_("Access level must be non-zero.")); + return; + } + + if (level <= ChanServ::ACCESS_INVALID || level >= ChanServ::ACCESS_FOUNDER) + { + source.Reply(_("Access level must be between \002{0}\002 and \002{1}\002 inclusive."), ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1); + return; + } + + ChanServ::AccessGroup u_access = source.AccessFor(ci); + ChanServ::ChanAccess *highest = u_access.Highest(); + + AccessChanAccess *access = Serialize::New(); + access->SetChannel(ci); + access->SetLevel(level); + + bool override = false; + + if ((!highest || *highest <= *access) && !u_access.founder) + { + if (source.HasPriv("chanserv/access/modify")) + { + override = true; + } + else + { + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to add someone at level \002{1}\002."), ci->GetName(), level); + access->Delete(); + return; + } + } + + access->Delete(); + + NickServ::Nick *na = NickServ::FindNick(mask); + + if (!na && Config->GetModule("chanserv")->Get("disallow_hostmask_access")) + { + source.Reply(_("Masks and unregistered users may not be on access lists.")); + return; + } + + if (mask.find_first_of("!*@") == Anope::string::npos && !na) + { + User *targ = User::Find(mask, true); + if (targ != NULL) + mask = "*!*@" + targ->GetDisplayedHost(); + else + { + source.Reply(_("\002{0}\002 isn't registered."), mask); + return; + } + } + + if (na) + mask = na->GetNick(); + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->Mask())) + { + /* Don't allow lowering from a level >= u_level */ + if ((!highest || *access >= *highest) && !u_access.founder && !source.HasPriv("chanserv/access/modify")) + { + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to lower the access of \002{1}\002."), ci->GetName(), access->Mask()); + return; + } + delete access; + break; + } + } + + unsigned access_max = Config->GetModule("chanserv")->Get("accessmax", "1024"); + if (access_max && ci->GetAccessCount() >= access_max) + { + source.Reply(_("Sorry, you can only have %d access entries on a channel, including access entries from other channels."), access_max); + return; + } + + access = Serialize::New(); + if (na) + access->SetObj(na->GetAccount()); + access->SetChannel(ci); + access->SetMask(mask); + access->SetCreator(source.GetNick()); + access->SetLevel(level); + access->SetLastSeen(0); + access->SetCreated(Anope::CurTime); + + EventManager::Get()->Dispatch(&Event::AccessAdd::OnAccessAdd, ci, source, access); + + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to add " << mask << " with level " << level; + if (p != NULL) + source.Reply(_("\002{0}\002 added to the access list of \002{1}\002 with privilege \002{2}\002 (level \002{3}\002)."), access->Mask(), ci->GetName(), p->name, level); + else + source.Reply(_("\002{0}\002 added to the access list of \002{1}\002 at level \002{2}\002."), access->Mask(), ci->GetName(), level); + } + + void DoDel(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + Anope::string mask = params[2]; + + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + if (!isdigit(mask[0]) && mask.find_first_of("#!*@") == Anope::string::npos && !NickServ::FindNick(mask)) + { + User *targ = User::Find(mask, true); + if (targ != NULL) + mask = "*!*@" + targ->GetDisplayedHost(); + else + { + source.Reply(_("\002{0}\002 isn't registered."), mask); + return; + } + } + + if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) + { + bool override = !source.AccessFor(ci).HasPriv("ACCESS_CHANGE") && source.HasPriv("chanserv/access/modify"); + Anope::string nicks; + bool denied = false; + unsigned int deleted = 0; + + NumberList(mask, true, + [&](unsigned int num) + { + if (!num || num > ci->GetAccessCount()) + return; + + ChanServ::ChanAccess *access = ci->GetAccess(num - 1); + + ChanServ::AccessGroup ag = source.AccessFor(ci); + ChanServ::ChanAccess *u_highest = ag.Highest(); + + if ((!u_highest || *u_highest <= *access) && !ag.founder && !override && access->GetObj() != source.nc) + { + denied = true; + return; + } + + ++deleted; + if (!nicks.empty()) + nicks += ", " + access->Mask(); + else + nicks = access->Mask(); + + EventManager::Get()->Dispatch(&Event::AccessDel::OnAccessDel, ci, source, access); + delete access; + }, + [&]() + { + if (denied && !deleted) + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to remove any access entries matching \002{1}\002.")); + else if (!deleted) + source.Reply(_("There are no entries matching \002{0}\002 on the access list of \002{1}\002."), mask, ci->GetName()); + else + { + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << nicks; + + if (deleted == 1) + source.Reply(_("Deleted \0021\002 entry from the access list of \002{0}\002."), ci->GetName()); + else + source.Reply(_("Deleted \002{0}\002 entries from the access list of \002{1}\002."), deleted, ci->GetName()); + } + }); + } + else + { + ChanServ::AccessGroup u_access = source.AccessFor(ci); + ChanServ::ChanAccess *highest = u_access.Highest(); + + for (unsigned i = ci->GetAccessCount(); i > 0; --i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i - 1); + if (mask.equals_ci(access->Mask())) + { + if (access->GetObj() != source.nc && !u_access.founder && (!highest || *highest <= *access) && !source.HasPriv("chanserv/access/modify")) + source.Reply(_("Access denied. You do not have enough privileges on \002{0}\002 to remove the access of \002{1}\002."), ci->GetName(), access->Mask()); + else + { + source.Reply(_("\002{0}\002 deleted from the access list of \002{1}\002."), access->Mask(), ci->GetName()); + bool override = !u_access.founder && !u_access.HasPriv("ACCESS_CHANGE") && !access->Mask().equals_ci(source.nc->GetDisplay()); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to delete " << access->Mask(); + + EventManager::Get()->Dispatch(&Event::AccessDel::OnAccessDel, ci, source, access); + delete access; + } + return; + } + } + + source.Reply(_("\002{0}\002 was not found on the access list of \002{1}\002."), mask, ci->GetName()); + } + } + + void ProcessList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms, ListFormatter &list) + { + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + if (!nick.empty() && nick.find_first_not_of("1234567890,-") == Anope::string::npos) + { + NumberList(nick, false, + [&](unsigned int number) + { + if (!number || number > ci->GetAccessCount()) + return; + + ChanServ::ChanAccess *access = ci->GetAccess(number - 1); + + Anope::string timebuf; + if (ci->c) + for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) + { + if (access->Matches(cit->second->user, cit->second->user->Account())) + timebuf = "Now"; + } + if (timebuf.empty()) + { + if (access->GetLastSeen() == 0) + timebuf = "Never"; + else + timebuf = Anope::strftime(access->GetLastSeen(), NULL, true); + } + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(number); + entry["Level"] = access->AccessSerialize(); + entry["Mask"] = access->Mask(); + entry["By"] = access->GetCreator(); + entry["Last seen"] = timebuf; + list.AddEntry(entry); + }, + [&](){}); + } + else + { + for (unsigned i = 0, end = ci->GetAccessCount(); i < end; ++i) + { + ChanServ::ChanAccess *access = ci->GetAccess(i); + + if (!nick.empty() && !Anope::Match(access->Mask(), nick)) + continue; + + Anope::string timebuf; + if (ci->c) + for (Channel::ChanUserList::const_iterator cit = ci->c->users.begin(), cit_end = ci->c->users.end(); cit != cit_end; ++cit) + { + if (access->Matches(cit->second->user, cit->second->user->Account())) + timebuf = "Now"; + } + if (timebuf.empty()) + { + if (access->GetLastSeen() == 0) + timebuf = "Never"; + else + timebuf = Anope::strftime(access->GetLastSeen(), NULL, true); + } + + ListFormatter::ListEntry entry; + entry["Number"] = stringify(i + 1); + entry["Level"] = access->AccessSerialize(); + entry["Mask"] = access->Mask(); + entry["By"] = access->GetCreator(); + entry["Last seen"] = timebuf; + list.AddEntry(entry); + } + } + + if (list.IsEmpty()) + { + source.Reply(_("No matching entries on the access list of \002{0}\002."), ci->GetName()); + return; + } + + std::vector replies; + list.Process(replies); + + source.Reply(_("Access list for \002{0}\002:"), ci->GetName()); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + + source.Reply(_("End of access list.")); + } + + void DoList(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask")); + this->ProcessList(source, ci, params, list); + } + + void DoView(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + if (!ci->GetAccessCount()) + { + source.Reply(_("The access list for \002{0}\002 is empty."), ci->GetName()); + return; + } + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Number")).AddColumn(_("Level")).AddColumn(_("Mask")).AddColumn(_("By")).AddColumn(_("Last seen")); + this->ProcessList(source, ci, params, list); + } + + void DoClear(CommandSource &source, ChanServ::Channel *ci) + { + if (!source.IsFounder(ci) && !source.HasPriv("chanserv/access/modify")) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), "FOUNDER", ci->GetName()); + return; + } + + EventManager::Get()->Dispatch(&Event::AccessClear::OnAccessClear, ci, source); + + ci->ClearAccess(); + + source.Reply(_("The access list of \002{0}\002 has been cleared."), ci->GetName()); + + bool override = !source.IsFounder(ci); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to clear the access list"; + } + + public: + CommandCSAccess(Module *creator) : Command(creator, "chanserv/access", 2, 4) + { + this->SetDesc(_("Modify the list of privileged users")); + this->SetSyntax(_("\037channel\037 ADD \037mask\037 \037level\037")); + this->SetSyntax(_("\037channel\037 DEL {\037mask\037 | \037entry-num\037 | \037list\037}")); + this->SetSyntax(_("\037channel\037 LIST [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 VIEW [\037mask\037 | \037list\037]")); + this->SetSyntax(_("\037channel\037 CLEAR")); + } + + void Execute(CommandSource &source, const std::vector ¶ms) override + { + const Anope::string &chan = params[0]; + const Anope::string &cmd = params[1]; + const Anope::string &nick = params.size() > 2 ? params[2] : ""; + const Anope::string &s = params.size() > 3 ? params[3] : ""; + + ChanServ::Channel *ci = ChanServ::Find(chan); + if (ci == NULL) + { + source.Reply(_("Channel \002{0}\002 isn't registered."), chan); + return; + } + + bool is_list = cmd.equals_ci("LIST") || cmd.equals_ci("VIEW"); + bool is_clear = cmd.equals_ci("CLEAR"); + bool is_del = cmd.equals_ci("DEL"); + + ChanServ::AccessGroup access = source.AccessFor(ci); + + bool has_access = false; + if (source.HasPriv("chanserv/access/modify")) + has_access = true; + else if (is_list && source.HasPriv("chanserv/access/list")) + has_access = true; + else if (is_list && access.HasPriv("ACCESS_LIST")) + has_access = true; + else if (access.HasPriv("ACCESS_CHANGE")) + has_access = true; + else if (is_del) + { + NickServ::Nick *na = NickServ::FindNick(nick); + if (na && na->GetAccount() == source.GetAccount()) + has_access = true; + } + + /* If LIST, we don't *require* any parameters, but we can take any. + * If DEL, we require a nick and no level. + * Else (ADD), we require a level (which implies a nick). */ + if (is_list || is_clear ? 0 : (cmd.equals_ci("DEL") ? (nick.empty() || !s.empty()) : s.empty())) + { + this->OnSyntaxError(source, cmd); + return; + } + + if (!has_access) + { + source.Reply(_("Access denied. You do not have privilege \002{0}\002 on \002{1}\002."), is_list ? "ACCESS_LIST" : "ACCESS_CHANGE", ci->GetName()); + return; + } + + if (Anope::ReadOnly && !is_list) + { + source.Reply(_("Sorry, channel access list modification is temporarily disabled.")); + return; + } + + if (cmd.equals_ci("ADD")) + this->DoAdd(source, ci, params); + else if (cmd.equals_ci("DEL")) + this->DoDel(source, ci, params); + else if (cmd.equals_ci("LIST")) + this->DoList(source, ci, params); + else if (cmd.equals_ci("VIEW")) + this->DoView(source, ci, params); + else if (cmd.equals_ci("CLEAR")) + this->DoClear(source, ci); + else + this->OnSyntaxError(source, ""); + } + + bool OnHelp(CommandSource &source, const Anope::string &subcommand) override + { + if (subcommand.equals_ci("ADD")) + { + source.Reply(_("The \002{0} ADD\002 adds \037mask\037 to the access list of \037channel\037 at level \037level\037." + " If \037mask\037 is already present on the access list, the access level for it is changed to \037level\037." + " The \037level\037 may be a numerical level between \002{1}\002 and \002{2}\002 or the name of a privilege (eg. \002{3}\002)." + " The privilege set granted to a given user is the union of the privileges of access entries that match the user." + " Use of this command requires the \002{4}\002 privilege on \037channel\037."), + source.command, ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1, "AUTOOP", "ACCESS_CHANGE"); + + if (!Config->GetModule("chanserv")->Get("disallow_channel_access")) + source.Reply(_("The given \037mask\037 may also be a channel, which will use the access list from the other channel up to the given \037level\037.")); + + //XXX show def levels + + source.Reply(_("\n" + "Examples:\n" + " {command} #anope ADD Adam 9001\n" + " Adds \"Adam\" to the access list of \"#anope\" at level \"9001\".\n" + "\n" + " {command} #anope ADD *!*@anope.org AUTOOP\n" + " Adds the host mask \"*!*@anope.org\" to the access list of \"#anope\" with the privilege \"AUTOOP\".")); + } + else if (subcommand.equals_ci("DEL")) + source.Reply(_("The \002{0} DEL\002 command removes \037mask\037 from the access list of \037channel\037." + " If a list of entry numbers is given, those entries are deleted." + " You may remove yourself from an access list, even if you do not have access to modify that list otherwise." + " Use of this command requires the \002{1}\002 privilege on \037channel\037.\n" + "\n" + "Example:\n" + " {command} #anope del DukePyrolator\n" + " Removes the access of \"DukePyrolator\" from \"#anope\"."), + source.command, "ACCESS_CHANGE"); + else if (subcommand.equals_ci("LIST") || subcommand.equals_ci("VIEW")) + source.Reply(_("The \002{0} LIST\002 and \002{0} VIEW\002 command displays the access list of \037channel\037." + " If a wildcard mask is given, only those entries matching the mask are displayed." + " If a list of entry numbers is given, only those entries are shown." + " \002VIEW\002 is similar to \002LIST\002 but also shows who created the access entry, and when the access entry was last used." + " Use of these commands requires the \002{1}\002 privilege on \037channel\037.\n" + "\n" + "Example:\n" + " {0} #anope LIST 2-5,7-9\n" + " Lists access entries numbered 2 through 5 and 7 through 9 on #anope."), + source.command, "ACCESS_LIST"); + else if (subcommand.equals_ci("CLEAR")) + source.Reply(_("The \002{0} CLEAR\002 command clears the access list of \037channel\037." + " Use of this command requires the \002{1}\002 privilege on \037channel\037."), + source.command, "FOUNDER"); + + else + { + source.Reply(_("Maintains the access list for \037channel\037. The access list specifies which users are granted which privileges to the channel." + " The access system uses numerical levels to represent different sets of privileges. Users who are identified but do not match any entries on" + " the access list has a level of 0. Unregistered or unidentified users who do not match any entries have a user level of 0.")); + ServiceBot *bi; + Anope::string name; + CommandInfo *help = source.service->FindCommand("generic/help"); + if (Command::FindCommandFromService("chanserv/levels", bi, name) && help) + source.Reply(_("\n" + "Access levels can be configured via the \002{levels}\002 command. See \002{msg}{service} {help} {levels}\002 for more information."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = bi->nick, "help"_kw = help->cname, "levels"_kw = name); + + if (help) + source.Reply(_("\n" + "The \002ADD\002 command adds \037mask\037 to the access list at level \037level\037.\n" + "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} ADD\002 for more information.\n" + "\n" + "The \002DEL\002 command removes \037mask\037 from the access list.\n" + "Use of this command requires the \002{change}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} DEL\002 for more information.\n" + "\n" + "The \002LIST\002 and \002VIEW\002 commands both show the access list for \037channel\037, but \002VIEW\002 also shows who created the access entry, and when the user was last seen.\n" + "Use of these commands requires the \002{list}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} [LIST | VIEW]\002 for more information.\n" + "\n" + "The \002CLEAR\002 command clears the access list." + "Use of this command requires the \002{founder}\002 privilege on \037channel\037.\n" + "\002{msg}{service} {help} {command} CLEAR\002 for more information."), + "msg"_kw = Config->StrictPrivmsg, "service"_kw = source.service->nick, "command"_kw = source.command, + "help"_kw = help->cname, "change"_kw = "ACCESS_CHANGE", "list"_kw = "ACCESS_LIST", "founder"_kw = "FOUNDER"); + } + + return true; + } +}; + +class CommandCSLevels : public Command +{ + void DoSet(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string &what = params[2]; + const Anope::string &lev = params[3]; + + int level; + + if (lev.equals_ci("FOUNDER")) + level = ChanServ::ACCESS_FOUNDER; + else + { + try + { + level = convertTo(lev); + } + catch (const ConvertException &) + { + this->OnSyntaxError(source, "SET"); + return; + } + } + + if (level <= ChanServ::ACCESS_INVALID || level > ChanServ::ACCESS_FOUNDER) + source.Reply(_("Level must be between \002{0}\002 and \002{1}\002 inclusive."), ChanServ::ACCESS_INVALID + 1, ChanServ::ACCESS_FOUNDER - 1); + else + { + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(what) : nullptr; + if (p == NULL) + { + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("There is no such privilege \002{0}\002. See \002{0}{1} {2} {3}\002 for a list of valid settings."), + what, Config->StrictPrivmsg, source.service->nick, help->cname, source.command); + } + else + { + bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to set " << p->name << " to level " << level; + + ci->SetLevel(p->name, level); + EventManager::Get()->Dispatch(&Event::LevelChange::OnLevelChange, source, ci, p->name, level); + + if (level == ChanServ::ACCESS_FOUNDER) + source.Reply(_("Level for privilege \002{0}\002 on channel \002{1}\002 changed to \002founder only\002."), p->name, ci->GetName()); + else + source.Reply(_("Level for privilege \002{0}\002 on channel \002{1}\002 changed to \002{3}\002."), p->name, ci->GetName(), level); + } + } + } + + void DoDisable(CommandSource &source, ChanServ::Channel *ci, const std::vector ¶ms) + { + const Anope::string &what = params[2]; + + /* Don't allow disabling of the founder level. It would be hard to change it back if you don't have access to use this command */ + if (what.equals_ci("FOUNDER")) + { + source.Reply(_("You can not disable the founder privilege because it would be impossible to reenable it at a later time.")); + return; + } + + ChanServ::Privilege *p = ChanServ::service ? ChanServ::service->FindPrivilege(what) : nullptr; + if (p != NULL) + { + bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to disable " << p->name; + + ci->SetLevel(p->name, ChanServ::ACCESS_INVALID); + EventManager::Get()->Dispatch(&Event::LevelChange::OnLevelChange, source, ci, p->name, ChanServ::ACCESS_INVALID); + + source.Reply(_("Privileged \002{0}\002 disabled on channel \002{1}\002."), p->name, ci->GetName()); + return; + } + + CommandInfo *help = source.service->FindCommand("generic/help"); + if (help) + source.Reply(_("There is no such privilege \002{0}\002. See \002{0}{1} {2} {3}\002 for a list of valid settings."), + what, Config->StrictPrivmsg, source.service->nick, help->cname, source.command); + } + + void DoList(CommandSource &source, ChanServ::Channel *ci) + { + if (!ChanServ::service) + return; + + source.Reply(_("Access level settings for channel \002{0}\002"), ci->GetName()); + + ListFormatter list(source.GetAccount()); + list.AddColumn(_("Name")).AddColumn(_("Level")); + + const std::vector &privs = ChanServ::service->GetPrivileges(); + + for (unsigned i = 0; i < privs.size(); ++i) + { + const ChanServ::Privilege &p = privs[i]; + int16_t j = ci->GetLevel(p.name); + + ListFormatter::ListEntry entry; + entry["Name"] = p.name; + + if (j == ChanServ::ACCESS_INVALID) + entry["Level"] = Language::Translate(source.GetAccount(), _("(disabled)")); + else if (j == ChanServ::ACCESS_FOUNDER) + entry["Level"] = Language::Translate(source.GetAccount(), _("(founder only)")); + else + entry["Level"] = stringify(j); + + list.AddEntry(entry); + } + + std::vector replies; + list.Process(replies); + + for (unsigned i = 0; i < replies.size(); ++i) + source.Reply(replies[i]); + } + + void DoReset(CommandSource &source, ChanServ::Channel *ci) + { + bool override = !source.AccessFor(ci).HasPriv("FOUNDER"); + Log(override ? LOG_OVERRIDE : LOG_COMMAND, source, this, ci) << "to reset all levels"; + + ci->ClearLevels(); + EventManager::Get()->Dispatch(&Event::LevelC