/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2013, 2017-2026 Sadie Powell * Copyright (C) 2013 Shawn Smith * Copyright (C) 2012-2013 Attila Molnar * Copyright (C) 2012 Robby * Copyright (C) 2009-2010 Daniel De Graaf * Copyright (C) 2009 Uli Schlachter * Copyright (C) 2008 Robin Burchell * Copyright (C) 2007 Dennis Friis * Copyright (C) 2006-2007 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/callerid.h" #include "modules/ctctags.h" #include "modules/extban.h" #include "modules/tls.h" #include "numerichelper.h" enum { // From UnrealIRCd. ERR_SECUREONLYCHAN = 489, // InspIRCd-specific. ERR_ALLMUSTSSL = 490 }; class FingerprintExtBan final : public ExtBan::MatchingBase { private: TLS::API& tlsapi; public: bool operonly; FingerprintExtBan(const WeakModulePtr& Creator, TLS::API& api) : ExtBan::MatchingBase(Creator, "fingerprint", 'z') , tlsapi(api) { } bool IsMatch(ListModeBase* lm, User* user, Channel* channel, const std::string& text, const ExtBan::MatchConfig& config) override { if (!tlsapi) return false; for (const auto& fingerprint : tlsapi->GetFingerprints(user)) { if (InspIRCd::Match(fingerprint, text)) return true; } return false; } bool Validate(ListModeBase* lm, LocalUser* user, Channel* channel, std::string& text) override { if (operonly && !user->IsOper()) { // XXX: this has to match the check in sslinfo. user->WriteNumeric(Numerics::NoPrivileges("you are not a server operator")); return false; } return true; } }; /** Handle channel mode +z */ class SSLOnlyChannel final : public ModeHandler { private: TLS::API& tlsapi; public: SSLOnlyChannel(const WeakModulePtr& Creator, TLS::API& api) : ModeHandler(Creator, "sslonly", 'z', PARAM_NONE, MODETYPE_CHANNEL) , tlsapi(api) { } bool OnModeChange(User* source, User* dest, Channel* channel, Modes::Change& change) override { if (change.adding == channel->IsModeSet(this)) return false; if (change.adding && source->IsLocal()) { if (!tlsapi) { source->WriteNumeric(ERR_ALLMUSTSSL, channel->name, "Unable to determine whether all members of the channel are connected using TLS"); return false; } size_t nonssl = 0; for (const auto& [u, _] : channel->GetUsers()) { if (!tlsapi->IsSecure(u) && !u->server->IsService()) nonssl++; } if (nonssl) { source->WriteNumeric(ERR_ALLMUSTSSL, channel->name, FMT::format("All members of the channel must be connected using TLS ({}/{} are non-TLS)", nonssl, channel->GetUsers().size())); return false; } } channel->SetMode(this, change.adding); return true; } }; /** Handle user mode +z */ class SSLOnlyUser final : public ModeHandler { private: TLS::API& tlsapi; public: SSLOnlyUser(const WeakModulePtr& Creator, TLS::API& api) : ModeHandler(Creator, "sslonly", 'z', PARAM_NONE, MODETYPE_USER) , tlsapi(api) { this->oldname = "sslqueries"; } bool OnModeChange(User* user, User* dest, Channel* channel, Modes::Change& change) override { if (change.adding == dest->IsModeSet(this)) return false; if (change.adding && user->IsLocal()) { if (!tlsapi) { user->WriteNumeric(ERR_ALLMUSTSSL, dest->nick, FMT::format("Unable to determine whether {} is connected with TLS", dest->nick)); return false; } if (!tlsapi->IsSecure(dest)) { user->WriteNumeric(ERR_ALLMUSTSSL, dest->nick, FMT::format("{} is not connected using TLS", dest->nick)); return false; } } dest->SetMode(this, change.adding); return true; } }; class ModuleSSLModes final : public Module , public CTCTags::EventListener { private: TLS::API tlsapi; CallerID::API calleridapi; SSLOnlyChannel sslonlychan; SSLOnlyUser sslonlyuser; FingerprintExtBan extban; public: ModuleSSLModes() : Module(VF_VENDOR, "Adds channel mode z (sslonly) which prevents users who are not connecting using TLS from joining the channel and user mode z (sslqueries) to prevent messages from non-TLS users.") , CTCTags::EventListener(weak_from_this()) , tlsapi(weak_from_this()) , calleridapi(weak_from_this()) , sslonlychan(weak_from_this(), tlsapi) , sslonlyuser(weak_from_this(), tlsapi) , extban(weak_from_this(), tlsapi) { } void ReadConfig(ConfigStatus& status) override { const auto fpvisible = ServerInstance->Config->ConfValue("sslinfo")->getBool("operonly"); const auto& tag = ServerInstance->Config->ConfValue("sslmodes"); extban.operonly = tag->getBool("extbanoperonly", fpvisible); } ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, PrefixMode::Set& privs, const std::string& keygiven, bool override) override { if (override) return MOD_RES_PASSTHRU; // Allow override joins. if (!chan || !chan->IsModeSet(sslonlychan)) return MOD_RES_PASSTHRU; // New channel or mode not set. if (!tlsapi) { user->WriteNumeric(ERR_SECUREONLYCHAN, cname, FMT::format("Cannot join channel; unable to determine if you are a TLS user (+{} is set)", sslonlychan.GetModeChar())); return MOD_RES_DENY; } if (!tlsapi->IsSecure(user)) { user->WriteNumeric(ERR_SECUREONLYCHAN, cname, FMT::format("Cannot join channel; TLS users only (+{} is set)", sslonlychan.GetModeChar())); return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } ModResult HandleMessage(User* user, const MessageTarget& msgtarget) { if (msgtarget.type != MessageTarget::TYPE_USER) return MOD_RES_PASSTHRU; auto* target = msgtarget.Get(); /* If one or more of the parties involved is a services user, we won't stop it. */ if (user->server->IsService() || target->server->IsService()) return MOD_RES_PASSTHRU; /* If the target is +z */ if (target->IsModeSet(sslonlyuser)) { const bool is_secure = tlsapi && tlsapi->IsSecure(user); const bool is_accepted = calleridapi && calleridapi->IsOnAcceptList(user, target); if (!is_secure && !is_accepted) { /* The sending user is not on an TLS connection */ user->WriteNumeric(Numerics::CannotSendTo(target, "messages", &sslonlyuser)); return MOD_RES_DENY; } } /* If the user is +z */ else if (user->IsModeSet(sslonlyuser)) { if (!tlsapi || !tlsapi->IsSecure(target)) { user->WriteNumeric(Numerics::CannotSendTo(target, "messages", &sslonlyuser, true)); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } ModResult OnUserPreMessage(User* user, MessageTarget& target, MessageDetails& details) override { return HandleMessage(user, target); } ModResult OnUserPreTagMessage(User* user, MessageTarget& target, CTCTags::TagMessageDetails& details) override { return HandleMessage(user, target); } }; MODULE_INIT(ModuleSSLModes)