/*
* InspIRCd -- Internet Relay Chat Daemon
*
* Copyright (C) 2013, 2017-2026 Sadie Powell <sadie@witchery.services>
* Copyright (C) 2013 Shawn Smith <ShawnSmith0828@gmail.com>
* Copyright (C) 2012-2013 Attila Molnar <attilamolnar@hush.com>
* Copyright (C) 2012 Robby <robby@chatbelgie.be>
* Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
* Copyright (C) 2009 Uli Schlachter <psychon@znc.in>
* Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
* Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
* Copyright (C) 2006-2007 Craig Edwards <brain@inspircd.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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<User>();
/* 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)