// Anope IRC Services // // Copyright (C) 2003-2026 Anope Contributors // // Anope is free software. You can use, modify, and/or distribute it under the // terms of version 2 of the GNU General Public License. See docs/LICENSE.txt // for the complete terms of this license and docs/AUTHORS.txt for a list of // contributors. // // Based on the original code of Epona by Lara // Based on the original code of Services by Andy Church // // SPDX-License-Identifier: GPL-2.0-only #include "services.h" #include "modules.h" #include "users.h" #include "regchannel.h" #include "config.h" #include #include #ifndef _WIN32 #include #include #endif #include namespace fs = std::filesystem; std::list ModuleManager::Modules; std::vector ModuleManager::EventHandlers[I_SIZE]; #ifdef _WIN32 void ModuleManager::CleanupRuntimeDirectory() { Anope::string dirbuf = Anope::ExpandData("runtime"); Log(LOG_DEBUG) << "Cleaning out Module run time directory (" << dirbuf << ") - this may take a moment, please wait"; try { for (const auto &entry : fs::directory_iterator(dirbuf.str())) { if (entry.is_regular_file()) fs::remove(entry); } } catch (const fs::filesystem_error &err) { Log(LOG_DEBUG) << "Cannot open directory (" << dirbuf << "): " << err.what(); } } #endif /* This code was found online at https://web.archive.org/web/20180318184211/https://www.linuxjournal.com/article/3687#comment-26593 * * This function will take a pointer from either dlsym or GetProcAddress and cast it in * a way that won't cause C++ warnings/errors to come up. */ template static TYPE function_cast(void *symbol) { union { void *symbol; TYPE function; } cast; cast.symbol = symbol; return cast.function; } ModuleReturn ModuleManager::LoadModule(const Anope::string &modname, User *u) { if (modname.empty()) return MOD_ERR_PARAMS; if (FindModule(modname)) return MOD_ERR_EXISTS; Log(LOG_DEBUG) << "Trying to load module: " << modname; const auto modpath = Anope::ExpandModule(modname + DLL_EXT); std::error_code ec; if (!fs::exists(modpath.str()) || ec) { Log(LOG_TERMINAL) << "Error while loading " << modname << ": file does not exist"; return MOD_ERR_NOEXIST; } #ifdef _WIN32 /* Generate the filename for the temporary copy of the module */ auto pbuf = Anope::ExpandData("runtime/" + modname + DLL_EXT ".XXXXXX"); if (!fs::copy_file(modpath.str(), pbuf.str(), fs::copy_options::overwrite_existing, ec) || ec) { Log(LOG_TERMINAL) << "Error while loading " << modname << ": " << ec.message(); return MOD_ERR_FILE_IO; } #else const auto pbuf = modpath; #endif dlerror(); void *handle = dlopen(pbuf.c_str(), RTLD_NOW); const char *err = dlerror(); if (!handle) { if (err && *err) Log() << err; return MOD_ERR_NOLOAD; } try { ModuleVersion v = GetVersion(handle); if (v.GetMajor() < Anope::VersionMajor() || (v.GetMajor() == Anope::VersionMajor() && v.GetMinor() < Anope::VersionMinor())) { Log() << "Module " << modname << " is compiled against an older version of Anope " << v.GetMajor() << "." << v.GetMinor() << ", this is " << Anope::VersionShort(); dlclose(handle); return MOD_ERR_VERSION; } else if (v.GetMajor() > Anope::VersionMajor() || (v.GetMajor() == Anope::VersionMajor() && v.GetMinor() > Anope::VersionMinor())) { Log() << "Module " << modname << " is compiled against a newer version of Anope " << v.GetMajor() << "." << v.GetMinor() << ", this is " << Anope::VersionShort(); dlclose(handle); return MOD_ERR_VERSION; } else if (v.GetPatch() < Anope::VersionPatch()) { Log() << "Module " << modname << " is compiled against an older version of Anope, " << v.GetMajor() << "." << v.GetMinor() << "." << v.GetPatch() << ", this is " << Anope::VersionShort(); dlclose(handle); return MOD_ERR_VERSION; } else if (v.GetPatch() > Anope::VersionPatch()) { Log() << "Module " << modname << " is compiled against a newer version of Anope, " << v.GetMajor() << "." << v.GetMinor() << "." << v.GetPatch() << ", this is " << Anope::VersionShort(); dlclose(handle); return MOD_ERR_VERSION; } else { Log(LOG_DEBUG_2) << "Module " << modname << " is compiled against current version of Anope " << Anope::VersionShort(); } } catch (const ModuleException &ex) { /* this error has already been logged */ dlclose(handle); return MOD_ERR_NOLOAD; } dlerror(); Module *(*func)(const Anope::string &, const Anope::string &) = function_cast(dlsym(handle, "AnopeInit")); err = dlerror(); if (!func) { Log() << "No init function found, not an Anope module"; if (err && *err) Log(LOG_DEBUG) << err; dlclose(handle); return MOD_ERR_NOLOAD; } /* Create module. */ Anope::string nick; if (u) nick = u->nick; Module *m; ModuleReturn moderr = MOD_ERR_OK; try { m = func(modname, nick); } catch (const ModuleException &ex) { Log() << "Error while loading " << modname << ": " << ex.GetReason(); moderr = MOD_ERR_EXCEPTION; } if (moderr != MOD_ERR_OK) { if (dlclose(handle)) Log() << dlerror(); return moderr; } m->filename = pbuf; m->handle = handle; if (m->type & DEPRECATED) Log(LOG_TERMINAL) << "Warning: " << modname << " is deprecated and will be removed in a future release"; /* Initialize config */ try { m->OnReload(*Config); } catch (const ModuleException &ex) { Log() << "Module " << modname << " couldn't load:" << ex.GetReason(); moderr = MOD_ERR_EXCEPTION; } catch (const ConfigException &ex) { Log() << "Module " << modname << " couldn't load due to configuration problems: " << ex.GetReason(); moderr = MOD_ERR_EXCEPTION; } catch (const NotImplementedException &ex) { } if (moderr != MOD_ERR_OK) { DeleteModule(m); return moderr; } Log(LOG_DEBUG) << "Module " << modname << " loaded."; /* Attach module to all events */ for (auto &mods : EventHandlers) mods.push_back(m); Serialize::CreateTypes(); m->Prioritize(); FOREACH_MOD(OnModuleLoad, (u, m)); return MOD_ERR_OK; } ModuleVersion ModuleManager::GetVersion(void *handle) { dlerror(); ModuleVersionC (*func)() = function_cast(dlsym(handle, "AnopeVersion"));; if (!func) { Log() << "No version function found, not an Anope module"; const char *err = dlerror(); if (err && *err) Log(LOG_DEBUG) << err; throw ModuleException("No version"); } return func(); } ModuleReturn ModuleManager::UnloadModule(Module *m, User *u) { if (!m) return MOD_ERR_PARAMS; FOREACH_MOD(OnModuleUnload, (u, m)); return DeleteModule(m); } Module *ModuleManager::FindModule(const Anope::string &name) { for (auto *m : Modules) { if (m->name.equals_ci(name)) return m; } return NULL; } Module *ModuleManager::FindFirstOf(ModType type, bool ignoredeprecated) { for (auto *m : Modules) { if (ignoredeprecated && (m->type & DEPRECATED)) continue; if (m->type & type) return m; } return NULL; } void ModuleManager::RequireVersion(unsigned major, unsigned minor, unsigned patch) { if (Anope::VersionMajor() > major) return; else if (Anope::VersionMajor() == major) { if (Anope::VersionMinor() > minor) return; else if (Anope::VersionMinor() == minor) { if (Anope::VersionPatch() > patch) return; else if (Anope::VersionPatch() == patch) return; } } throw ModuleException("This module requires version " + Anope::ToString(major) + "." + Anope::ToString(minor) + "." + Anope::ToString(patch) + " - this is " + Anope::VersionShort()); } ModuleReturn ModuleManager::DeleteModule(Module *m) { if (!m || !m->handle) return MOD_ERR_PARAMS; void *handle = m->handle; Anope::string filename = m->filename; Log(LOG_DEBUG) << "Unloading module " << m->name; dlerror(); void (*destroy_func)(Module *m) = function_cast(dlsym(m->handle, "AnopeFini")); const char *err = dlerror(); if (!destroy_func || (err && *err)) { Log() << "No destroy function found for " << m->name << ", chancing delete..."; delete m; /* we just have to chance they haven't overwrote the delete operator then... */ } else destroy_func(m); /* Let the module delete it self, just in case */ if (dlclose(handle)) Log() << dlerror(); #ifdef _WIN32 if (!filename.empty()) unlink(filename.c_str()); #endif return MOD_ERR_OK; } void ModuleManager::DetachAll(Module *mod) { for (auto &mods : EventHandlers) { std::vector::iterator it2 = std::find(mods.begin(), mods.end(), mod); if (it2 != mods.end()) mods.erase(it2); } } bool ModuleManager::SetPriority(Module *mod, Priority s) { for (unsigned i = 0; i < I_SIZE; ++i) SetPriority(mod, static_cast(i), s); return true; } bool ModuleManager::SetPriority(Module *mod, Implementation i, Priority s, Module **modules, size_t sz) { /** To change the priority of a module, we first find its position in the vector, * then we find the position of the other modules in the vector that this module * wants to be before/after. We pick off either the first or last of these depending * on which they want, and we make sure our module is *at least* before or after * the first or last of this subset, depending again on the type of priority. */ /* Locate our module. This is O(n) but it only occurs on module load so we're * not too bothered about it */ size_t source = 0; bool found = false; for (size_t x = 0, end = EventHandlers[i].size(); x != end; ++x) if (EventHandlers[i][x] == mod) { source = x; found = true; break; } /* Eh? this module doesn't exist, probably trying to set priority on an event * they're not attached to. */ if (!found) return false; size_t swap_pos = 0; bool swap = true; switch (s) { /* Dummy value */ case PRIORITY_DONTCARE: { swap = false; break; } /* Module wants to be first, sod everything else */ case PRIORITY_FIRST: { swap_pos = 0; break; } /* Module is submissive and wants to be last... awww. */ case PRIORITY_LAST: { if (EventHandlers[i].empty()) swap_pos = 0; else swap_pos = EventHandlers[i].size() - 1; break; } /* Place this module after a set of other modules */ case PRIORITY_AFTER: { /* Find the latest possible position */ swap_pos = 0; swap = false; for (size_t x = 0, end = EventHandlers[i].size(); x != end; ++x) { for (size_t n = 0; n < sz; ++n) { if (modules[n] && EventHandlers[i][x] == modules[n] && x >= swap_pos && source <= swap_pos) { swap_pos = x; swap = true; } } } break; } /* Place this module before a set of other modules */ case PRIORITY_BEFORE: { swap_pos = EventHandlers[i].size() - 1; swap = false; for (size_t x = 0, end = EventHandlers[i].size(); x != end; ++x) { for (size_t n = 0; n < sz; ++n) { if (modules[n] && EventHandlers[i][x] == modules[n] && x <= swap_pos && source >= swap_pos) { swap = true; swap_pos = x; } } } break; } } /* Do we need to swap? */ if (swap && swap_pos != source) { /* Suggestion from Phoenix, "shuffle" the modules to better retain call order */ int increment = 1; if (source > swap_pos) increment = -1; for (unsigned j = source; j != swap_pos; j += increment) { if (j + increment > EventHandlers[i].size() - 1 || (!j && increment == -1)) continue; std::swap(EventHandlers[i][j], EventHandlers[i][j + increment]); } } return true; } void ModuleManager::UnloadAll() { std::vector modules; for (size_t i = 1, j = 0; i != MT_END; j |= i, i <<= 1) { for (auto *m : Modules) { if ((m->type & j) == m->type) modules.push_back(m->name); } } for (auto &module : modules) { Module *m = FindModule(module); if (m != NULL) UnloadModule(m, NULL); } }