/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2020 Matt Schatz * Copyright (C) 2013, 2017-2021 Sadie Powell * Copyright (C) 2013 Shawn Smith * Copyright (C) 2012-2014 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/ctctags.h" #include "modules/extban.h" #include "modules/ssl.h" #include "numerichelper.h" enum { // From UnrealIRCd. ERR_SECUREONLYCHAN = 489, // InspIRCd-specific. ERR_ALLMUSTSSL = 490 }; class FingerprintExtBan final : public ExtBan::MatchingBase { private: UserCertificateAPI& sslapi; public: FingerprintExtBan(Module* Creator, UserCertificateAPI& api) : ExtBan::MatchingBase(Creator, "fingerprint", 'z') , sslapi(api) { } bool IsMatch(User* user, Channel* channel, const std::string& text) override { if (!sslapi) return false; for (const auto& fingerprint : sslapi->GetFingerprints(user)) { if (InspIRCd::Match(fingerprint, text)) return true; } return false; } }; /** Handle channel mode +z */ class SSLMode final : public ModeHandler { private: UserCertificateAPI& API; public: SSLMode(Module* Creator, UserCertificateAPI& api) : ModeHandler(Creator, "sslonly", 'z', PARAM_NONE, MODETYPE_CHANNEL) , API(api) { } bool OnModeChange(User* source, User* dest, Channel* channel, Modes::Change& change) override { if (change.adding) { if (!channel->IsModeSet(this)) { if (IS_LOCAL(source)) { if (!API) { source->WriteNumeric(ERR_ALLMUSTSSL, channel->name, "Unable to determine whether all members of the channel are connected via TLS"); return false; } size_t nonssl = 0; for (const auto& [u, _] : channel->GetUsers()) { if (!API->IsSecure(u) && !u->server->IsService()) nonssl++; } if (nonssl) { source->WriteNumeric(ERR_ALLMUSTSSL, channel->name, INSP_FORMAT("All members of the channel must be connected via TLS ({}/{} are non-TLS)", nonssl, channel->GetUsers().size())); return false; } } channel->SetMode(this, true); return true; } else { return false; } } else { if (channel->IsModeSet(this)) { channel->SetMode(this, false); return true; } return false; } } }; /** Handle user mode +z */ class SSLModeUser final : public ModeHandler { private: UserCertificateAPI& API; public: SSLModeUser(Module* Creator, UserCertificateAPI& api) : ModeHandler(Creator, "sslqueries", 'z', PARAM_NONE, MODETYPE_USER) , API(api) { } bool OnModeChange(User* user, User* dest, Channel* channel, Modes::Change& change) override { if (change.adding == dest->IsModeSet(this)) return false; if (change.adding && IS_LOCAL(user) && (!API || !API->IsSecure(user))) return false; dest->SetMode(this, change.adding); return true; } }; class ModuleSSLModes final : public Module , public CTCTags::EventListener { private: UserCertificateAPI api; SSLMode sslm; SSLModeUser sslquery; 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(this) , api(this) , sslm(this, api) , sslquery(this, api) , extban(this, api) { } ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven, bool override) override { if (!override && chan && chan->IsModeSet(sslm)) { if (!api) { user->WriteNumeric(ERR_SECUREONLYCHAN, cname, INSP_FORMAT("Cannot join channel; unable to determine if you are a TLS user (+{} is set)", sslm.GetModeChar())); return MOD_RES_DENY; } if (!api->IsSecure(user)) { user->WriteNumeric(ERR_SECUREONLYCHAN, cname, INSP_FORMAT("Cannot join channel; TLS users only (+{} is set)", sslm.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; User* 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(sslquery)) { if (!api || !api->IsSecure(user)) { /* The sending user is not on an TLS connection */ user->WriteNumeric(Numerics::CannotSendTo(target, "messages", &sslquery)); return MOD_RES_DENY; } } /* If the user is +z */ else if (user->IsModeSet(sslquery)) { if (!api || !api->IsSecure(target)) { user->WriteNumeric(Numerics::CannotSendTo(target, "messages", &sslquery, true)); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) override { return HandleMessage(user, target); } ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) override { return HandleMessage(user, target); } }; MODULE_INIT(ModuleSSLModes)