diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 6dff9b2f4..21f9f8560 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -2245,10 +2245,23 @@ # Atheme. # # -# If your services server has support for InspIRCd v4 then you can disable -# the legacy modes that were previously used for marking a user or channel -# as being registered: -# +#-#-#-#-#-#-#-#-#-#-#-#- SERVICES CONFIGURATION -#-#-#-#-#-#-#-#-#-#-# +# # +# accountoverrideshold - Whether to allow users that are logged in # +# to an account that has a services-held nick # +# in their group to override the SVSHOLD. # +# Defaults to no. # +# # +# disablemodes - Whether channel mode `r` (registered) and # +# user mode `r` (u_registered) are disabled. # +# These modes are deprecated in InspIRCd v4 # +# but may still be needed by older services # +# software. Defaults to no. # +# # +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# +# #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # Sethost module: Adds the /SETHOST command. @@ -2539,17 +2552,6 @@ # ssl_mbedtls or ssl_openssl). # # -#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# -# SVSHold module: Implements SVSHOLD. Like Q-lines, but can only be # -# added/removed by Services. # -# -# SVSHOLD does not generate server notices by default, you can turn -# notices on by uncommenting the next line. You can also allow users -# that are authenticated to a services account to bypass SVSHOLDs for -# nicknames in their nick group (requires services support). -# -# - #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # SWHOIS module: Allows you to add arbitrary lines to user WHOIS. # This module is oper-only. diff --git a/src/configreader.cpp b/src/configreader.cpp index 09c9d070a..4e4f1089c 100644 --- a/src/configreader.cpp +++ b/src/configreader.cpp @@ -645,6 +645,8 @@ std::vector ServerConfig::GetModules() const } else if (stdalgo::string::equalsci(shortname, "servprotect")) modules.push_back("services"); + else if (stdalgo::string::equalsci(shortname, "svshold")) + modules.push_back("services"); else { // No need to rewrite this module name. diff --git a/src/modules/m_services.cpp b/src/modules/m_services.cpp index 9b429711c..19ba5b913 100644 --- a/src/modules/m_services.cpp +++ b/src/modules/m_services.cpp @@ -19,6 +19,9 @@ #include "inspircd.h" #include "modules/account.h" +#include "modules/stats.h" +#include "timeutils.h" +#include "xline.h" enum { @@ -90,23 +93,146 @@ public: } }; -class ModuleServices final - : public Module +class SVSHold final + : public XLine { private: + std::string nickname; + +public: + SVSHold(time_t settime, unsigned long period, const std::string& setter, const std::string& message, const std::string& nick) + : XLine(settime, period, setter, message, "SVSHOLD") + , nickname(nick) + { + } + + const std::string& Displayable() const override + { + return nickname; + } + + void DisplayExpiry() override + { + // SVSHOLDs do not generate any messages. + } + + bool Matches(User* user) const override + { + return irc::equals(user->nick, nickname); + } + + bool Matches(const std::string& text) const override + { + return irc::equals(nickname, text): + } +}; + +class SVSHoldFactory final + : public XLineFactory +{ +public: + SVSHoldFactory() + : XLineFactory("SVSHOLD") + { + } + + XLine* Generate(time_t settime, unsigned long duration, const std::string& source, const std::string& reason, const std::string& nick) override + { + return new SVSHold(settime, duration, source, reason, nick); + } + + bool AutoApplyToUserList(XLine* x) override + { + return false; + } +}; + +class CommandSVSHold final + : public Command +{ +public: + CommandSVSHold(Module* Creator) + : Command(Creator, "SVSHOLD", 1) + { + // No need to set any privs because they're not checked for remote users. + } + + CmdResult Handle(User* user, const Params& parameters) + { + // The command can only be executed by remote services servers. + if (!IS_LOCAL(user) || !user->server->IsService()) + return CmdResult::FAILURE; + + if (parameters.size() == 1) + { + // :36DAAAAAA SVSHOLD ChanServ + std::string reason; + return ServerInstance->XLines->DelLine(parameters[0], "SVSHOLD", reason, user) ? CmdResult::SUCCESS : CmdResult::FAILURE; + } + + if (parameters.size() == 3) + { + /// :36DAAAAAA SVSHOLD NickServ 86400 :Reserved for services + /// :36DAAAAAA SVSHOLD NickServ 1d :Reserved for services + unsigned long duration; + if (!Duration::TryFrom(parameters[1], duration)) + return CmdResult::FAILURE; + + auto* svshold = new SVSHold(ServerInstance->Time(), duration, user->nick, parameters[2], parameters[0]); + return ServerInstance->XLines->AddLine(svshold, user) ? CmdResult::SUCCESS : CmdResult::FAILURE; + } + + return CmdResult::FAILURE; + } + + RouteDescriptor GetRouting(User* user, const Params& parameters) override + { + return ROUTE_BROADCAST; + } +}; + +class ModuleServices final + : public Module + , public Stats::EventListener +{ +private: + Account::API accountapi; RegisteredChannel registeredcmode; RegisteredUser registeredumode; ServProtect servprotectmode; + SVSHoldFactory svsholdfactory; + CommandSVSHold svsholdcmd; + bool accountoverrideshold; public: ModuleServices() - : Module(VF_VENDOR, "Provides support for integrating with a services server.") + : Module(VF_COMMON | VF_VENDOR, "Provides support for integrating with a services server.") + , Stats::EventListener(this) + , accountapi(this) , registeredcmode(this) , registeredumode(this) , servprotectmode(this) + , svsholdcmd(this) { } + ~ModuleServices() override + { + ServerInstance->XLines->DelAll("SVSHOLD"); + ServerInstance->XLines->UnregisterFactory(&svsholdfactory); + } + + void init() override + { + ServerInstance->XLines->RegisterFactory(&svsholdfactory); + } + + void ReadConfig(ConfigStatus& status) override + { + const auto& tag = ServerInstance->Config->ConfValue("services"); + accountoverrideshold = tag->getBool("accountoverrideshold"); + } + ModResult OnKill(User* source, User* dest, const std::string& reason) override { if (!source) @@ -145,6 +271,15 @@ public: return MOD_RES_PASSTHRU; } + ModResult OnStats(Stats::Context& stats) override + { + if (stats.GetSymbol() != 'S') + return MOD_RES_PASSTHRU; + + ServerInstance->XLines->InvokeStats("SVSHOLD", stats); + return MOD_RES_DENY; + } + ModResult OnUserPreKick(User* source, Membership* memb, const std::string& reason) override { if (memb->user->IsModeSet(servprotectmode)) @@ -155,6 +290,27 @@ public: return MOD_RES_PASSTHRU; } + ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) override + { + auto* svshold = ServerInstance->XLines->MatchesLine("SVSHOLD", newnick); + if (!svshold) + return MOD_RES_PASSTHRU; + + if (accountoverrideshold && accountapi) + { + Account::NickList* nicks = accountapi->GetAccountNicks(user); + if (nicks && nicks->find(svshold->Displayable()) != nicks->end()) + { + std::string reason; + ServerInstance->XLines->DelLine(svshold, reason, user); + return MOD_RES_PASSTHRU; + } + } + + user->WriteNumeric(ERR_ERRONEUSNICKNAME, newnick, INSP_FORMAT("Services reserved nickname: {}", svshold->reason)); + return MOD_RES_DENY; + } + void OnUserPostNick(User* user, const std::string& oldnick) override { if (user->IsModeSet(registeredumode) && irc::equals(oldnick, user->nick)) diff --git a/src/modules/m_spanningtree/capab.cpp b/src/modules/m_spanningtree/capab.cpp index d0b974131..1db1998ac 100644 --- a/src/modules/m_spanningtree/capab.cpp +++ b/src/modules/m_spanningtree/capab.cpp @@ -63,6 +63,8 @@ namespace modname = "m_gecosban.so"; else if (stdalgo::string::equalsci(modname, "m_account.so") && ServerInstance->Modules.Find("services")) modname = "m_services_account.so"; + else if (stdalgo::string::equalsci(modname, "m_services.so")) + modname = "m_svshold.so"; } else { diff --git a/src/modules/m_svshold.cpp b/src/modules/m_svshold.cpp deleted file mode 100644 index e7699956d..000000000 --- a/src/modules/m_svshold.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2021 Herman - * Copyright (C) 2019 Matt Schatz - * Copyright (C) 2018 linuxdaemon - * Copyright (C) 2013, 2017-2018, 2020, 2022 Sadie Powell - * Copyright (C) 2012, 2019 Robby - * Copyright (C) 2012, 2014, 2016 Attila Molnar - * Copyright (C) 2009 Daniel De Graaf - * Copyright (C) 2007-2008 Dennis Friis - * Copyright (C) 2006-2008 Robin Burchell - * Copyright (C) 2006 Craig Edwards - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -#include "inspircd.h" -#include "modules/account.h" -#include "modules/stats.h" -#include "timeutils.h" -#include "xline.h" - -namespace -{ - bool silent; -} - -/** Holds a SVSHold item - */ -class SVSHold final - : public XLine -{ -public: - std::string nickname; - - SVSHold(time_t s_time, unsigned long d, const std::string& src, const std::string& re, const std::string& nick) - : XLine(s_time, d, src, re, "SVSHOLD") - , nickname(nick) - { - } - - bool Matches(User* u) const override - { - return u->nick == nickname; - } - - bool Matches(const std::string& s) const override - { - return InspIRCd::Match(s, nickname); - } - - void DisplayExpiry() override - { - if (!silent) - XLine::DisplayExpiry(); - } - - const std::string& Displayable() const override - { - return nickname; - } -}; - -/** An XLineFactory specialized to generate SVSHOLD pointers - */ -class SVSHoldFactory final - : public XLineFactory -{ -public: - SVSHoldFactory() - : XLineFactory("SVSHOLD") - { - } - - XLine* Generate(time_t set_time, unsigned long duration, const std::string& source, const std::string& reason, const std::string& xline_specific_mask) override - { - return new SVSHold(set_time, duration, source, reason, xline_specific_mask); - } - - bool AutoApplyToUserList(XLine* x) override - { - return false; - } -}; - -class CommandSvshold final - : public Command -{ -public: - CommandSvshold(Module* Creator) - : Command(Creator, "SVSHOLD", 1) - { - access_needed = CmdAccess::OPERATOR; - syntax = { " [ :]" }; - } - - CmdResult Handle(User* user, const Params& parameters) override - { - /* syntax: svshold nickname time :reason goes here */ - /* 'time' is a human-readable timestring, like 2d3h2s. */ - - if (!user->server->IsService()) - { - /* don't allow SVSHOLD from non-services */ - return CmdResult::FAILURE; - } - - if (parameters.size() == 1) - { - std::string reason; - - if (ServerInstance->XLines->DelLine(parameters[0], "SVSHOLD", reason, user)) - { - if (!silent) - ServerInstance->SNO.WriteToSnoMask('x', "{} removed SVSHOLD on {}: {}", user->nick, parameters[0], reason); - } - else - { - user->WriteNotice("*** SVSHOLD " + parameters[0] + " not found on the list."); - } - } - else - { - if (parameters.size() < 3) - return CmdResult::FAILURE; - - unsigned long duration; - if (!Duration::TryFrom(parameters[1], duration)) - { - user->WriteNotice("*** Invalid duration for SVSHOLD."); - return CmdResult::FAILURE; - } - - auto* r = new SVSHold(ServerInstance->Time(), duration, user->nick, parameters[2], parameters[0]); - if (ServerInstance->XLines->AddLine(r, user)) - { - if (silent) - return CmdResult::SUCCESS; - - if (!duration) - { - ServerInstance->SNO.WriteToSnoMask('x', "{} added a permanent SVSHOLD on {}: {}", user->nick, parameters[0], parameters[2]); - } - else - { - ServerInstance->SNO.WriteToSnoMask('x', "{} added a timed SVSHOLD on {}, expires in {} (on {}): {}", - user->nick, parameters[0], Duration::ToString(duration), - Time::ToString(ServerInstance->Time() + duration), parameters[2]); - } - } - else - { - delete r; - return CmdResult::FAILURE; - } - } - - return CmdResult::SUCCESS; - } - - RouteDescriptor GetRouting(User* user, const Params& parameters) override - { - return ROUTE_BROADCAST; - } -}; - -class ModuleSVSHold final - : public Module - , public Stats::EventListener -{ -private: - CommandSvshold cmd; - SVSHoldFactory s; - Account::API accountapi; - bool exemptregistered; - -public: - ModuleSVSHold() - : Module(VF_VENDOR | VF_COMMON, "Adds the /SVSHOLD command which allows services to reserve nicknames.") - , Stats::EventListener(this) - , cmd(this) - , accountapi(this) - { - } - - void init() override - { - ServerInstance->XLines->RegisterFactory(&s); - } - - void ReadConfig(ConfigStatus& status) override - { - const auto& tag = ServerInstance->Config->ConfValue("svshold"); - silent = tag->getBool("silent", true); - exemptregistered = tag->getBool("exemptregistered"); - } - - ModResult OnStats(Stats::Context& stats) override - { - if (stats.GetSymbol() != 'S') - return MOD_RES_PASSTHRU; - - ServerInstance->XLines->InvokeStats("SVSHOLD", stats); - return MOD_RES_DENY; - } - - ModResult OnUserPreNick(LocalUser* user, const std::string& newnick) override - { - XLine *rl = ServerInstance->XLines->MatchesLine("SVSHOLD", newnick); - if (!rl) - return MOD_RES_PASSTHRU; - - if (exemptregistered && accountapi) - { - Account::NickList* nicks = accountapi->GetAccountNicks(user); - if (nicks && nicks->find(rl->Displayable()) != nicks->end()) - { - std::string reason; - if (ServerInstance->XLines->DelLine(rl, reason, user)) - { - if (!silent) - ServerInstance->SNO.WriteToSnoMask('x', "{} overrode SVSHOLD on {}: {}", user->nick, rl->Displayable(), reason); - } - return MOD_RES_PASSTHRU; - } - } - - user->WriteNumeric(ERR_ERRONEUSNICKNAME, newnick, INSP_FORMAT("Services reserved nickname: {}", rl->reason)); - return MOD_RES_DENY; - } - - ~ModuleSVSHold() override - { - ServerInstance->XLines->DelAll("SVSHOLD"); - ServerInstance->XLines->UnregisterFactory(&s); - } -}; - -MODULE_INIT(ModuleSVSHold)