diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 323e734ea..690c509de 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -515,12 +515,53 @@ #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # Cloak module: Adds user mode x (cloak) which allows user hostnames to # be hidden. This module does not provide any cloak methods by itself. -# You should also load another module like cloak_md5 or cloak_sha256. +# You should also load another module like cloak_account or cloak_sha256. # # In order to have users automatically cloaked on connect you should # load the conn_umodes module and add "x" to . # +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# Account cloak module: Adds the "account" (services account name) and +# "account-id" (services account identifier) cloak methods. +# +# +#-#-#-#-#-#-#-#-#-#- ACCOUNT CLOAK CONFIGURATION -#-#-#-#-#-#-#-#-#-#-# +# To use the cloak_account module you must define a tag. This # +# tag can have the following fields. # +# # +# class - If non-empty then a comma-delimited list of connect # +# class names that a user has to be in to get the cloak # +# from this tag. # +# # +# prefix - A freeform value to prefix cloaks with. This must not # +# contain spaces. # +# # +# suffix - A freeform value to suffix IPv4/IPv6 cloaks with. This # +# must not contain spaces. # +# # +# sanitize - If enabled then any characters in the account name/id # +# that are not valid in a hostname will be removed rather # +# than skipping the cloak method. Defaults to yes. # +# # +# IMPORTANT: Changing these details will break all of your existing # +# bans. If you do not want this to happen you can define multiple # +# cloak tags. The first will be used for hostnames and the rest will # +# be used for checking if a user is banned in a channel. # +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# +# +# +# + #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # MD5 cloak module: Adds the "half" and "full" cloak methods. These # methods are obsolete and should only be used on a network which is @@ -545,8 +586,8 @@ # prefix - A freeform value to prefix cloaks with. This must not # # contain spaces. # # # -# suffix - A freeform value to suffix IPv4/IPv6 cloaks with. This # -# must not contain spaces. # +# suffix - A freeform value to suffix cloaks with. This must not # +# contain spaces. # # # # domainparts - The maximum number of hostname labels that should be # # visible on the end of a host. Defaults to 3. # diff --git a/src/modules/m_cloak_account.cpp b/src/modules/m_cloak_account.cpp new file mode 100644 index 000000000..76180fb3f --- /dev/null +++ b/src/modules/m_cloak_account.cpp @@ -0,0 +1,191 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2023 Sadie Powell + * + * 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/cloak.h" + +class AccountMethod + : public Cloak::Method +{ +protected: + // Dynamic reference to the account api. + Account::API& accountapi; + + // The characters which are valid in a hostname. + const CharState& hostmap; + + // The prefix for cloaks (e.g. users.). + const std::string prefix; + + // Whether to strip non-host characters from the cloak. + const bool sanitize; + + // The suffix for IP cloaks (e.g. .example.org). + const std::string suffix; + + // Retrieves the middle segment of the cloak. + virtual std::string GetMiddle(LocalUser* user) + { + const std::string* accountname = accountapi ? accountapi->GetAccountName(user) : nullptr; + return accountname ? *accountname : ""; + } + +public: + AccountMethod(const Cloak::Engine* engine, const std::shared_ptr& tag, const CharState& hm, Account::API& api) ATTR_NOT_NULL(2) + : Cloak::Method(engine, tag) + , accountapi(api) + , hostmap(hm) + , prefix(tag->getString("prefix")) + , sanitize(tag->getBool("sanitize", true)) + , suffix(tag->getString("suffix")) + { + } + + std::string Generate(LocalUser* user) override ATTR_NOT_NULL(2) + { + if (!MatchesUser(user)) + return {}; // We shouldn't cloak this user. + + const std::string middle = GetMiddle(user); + if (middle.empty()) + return {}; // No middle cloak. + + std::string safemiddle; + safemiddle.reserve(middle.length()); + for (const auto chr : middle) + { + if (!hostmap.test(static_cast(chr))) + { + if (!sanitize) + return {}; // Contains invalid characters. + + continue; + } + + safemiddle.push_back(chr); + } + + if (safemiddle.empty()) + return {}; // No cloak. + + return prefix + safemiddle + suffix; + } + + std::string Generate(const std::string& hostip) override + { + // We can't generate account cloaks without a user. + return {}; + } + + void GetLinkData(Module::LinkData& data, std::string& compatdata) override + { + data["prefix"] = prefix; + data["sanitize"] = sanitize ? "yes" : "no"; + data["suffix"] = suffix; + } +}; + +class AccountIdMethod final + : public AccountMethod +{ +protected: + // Retrieves the middle segment of the cloak. + std::string GetMiddle(LocalUser* user) override + { + const std::string* accountid = accountapi ? accountapi->GetAccountId(user) : nullptr; + return accountid ? *accountid : ""; + } + +public: + AccountIdMethod(const Cloak::Engine* engine, const std::shared_ptr& tag, const CharState& hm, Account::API& api) ATTR_NOT_NULL(2) + : AccountMethod(engine, tag, hm, api) + { + } +}; + +template +class AccountEngine final + : public Cloak::Engine +{ +private: + // Dynamic reference to the account api. + Account::API& accountapi; + + // The characters which are valid in a hostname. + const CharState& hostmap; + +public: + AccountEngine(Module* Creator, const std::string& Name, const CharState& hm, Account::API& api) + : Cloak::Engine(Creator, Name) + , accountapi(api) + , hostmap(hm) + { + } + + Cloak::MethodPtr Create(const std::shared_ptr& tag, bool primary) override + { + return std::make_shared(this, tag, hostmap, accountapi); + } +}; + +class ModuleCloakAccount final + : public Module + , public Account::EventListener +{ +private: + Account::API accountapi; + AccountEngine accountcloak; + AccountEngine accountidcloak; + Cloak::API cloakapi; + CharState hostmap; + +public: + ModuleCloakAccount() + : Module(VF_VENDOR, "Adds the account and account-id cloaking methods for use with the cloak module.") + , Account::EventListener(this) + , accountapi(this) + , accountcloak(this, "account", hostmap, accountapi) + , accountidcloak(this, "account-id", hostmap, accountapi) + , cloakapi(this) + { + } + + void ReadConfig(ConfigStatus& status) override + { + CharState newhostmap; + const auto& tag = ServerInstance->Config->ConfValue("hostname"); + for (const auto chr : tag->getString("charmap", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_/0123456789", 1)) + { + // A hostname can not contain NUL, LF, CR, or SPACE. + if (chr == 0x00 || chr == 0x0A || chr == 0x0D || chr == 0x20) + throw ModuleException(this, INSP_FORMAT(" can not contain character 0x{:02X} ({})", chr, chr)); + newhostmap.set(static_cast(chr)); + } + std::swap(newhostmap, hostmap); + } + + void OnAccountChange(User* user, const std::string& newaccount) override + { + LocalUser* luser = IS_LOCAL(user); + if (luser && cloakapi) + cloakapi->ResetCloaks(luser, true); + } +}; + +MODULE_INIT(ModuleCloakAccount)