aboutsummaryrefslogtreecommitdiff
/*
 * InspIRCd -- Internet Relay Chat Daemon
 *
 *   Copyright (C) 2023 Sadie Powell <sadie@witchery.services>
 *   Copyright (C) 2017 Adam <Adam@anope.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/>.
 */


#pragma once

#include "stringutils.h"

namespace Cloak
{
	class API;
	class APIBase;
	class Engine;
	class Method;
	struct Info;

	/** Encapsulates a list of cloaks. */
	using List = std::vector<Info>;

	/** A shared pointer to a cloak method. */
	using MethodPtr = std::shared_ptr<Method>;

	/** Takes a hostname and retrieves the part which should be visible.
	 *
	 * This is usually the last \p hostparts segments but if not enough are
	 * present then all but the most specific segments are used. If the domain
	 * name consists of one label only then none are used.
	 *
	 * Here are some examples for how domain names will be shortened assuming
	 * \p domainparts is set to the default of 3.
	 *
	 *   "this.is.an.example.com"  =>  "an.example.com"
	 *   "an.example.com"          =>  "example.com"
	 *   "example.com"             =>  "com"
	 *   "localhost"               =>  ""
	 *
	 *   "/var/run/inspircd/client.sock"  =>  "run/inspircd/client.sock"
	 *   "/run/inspircd/client.sock"      =>  "inspircd/client.sock"
	 *   "/inspircd/client.sock"          =>  "client.sock"
	 *   "/client.sock"                   =>  ""
	 *
	 * @param host The hostname to cloak.
	 * @param hostparts The number of host labels that should be visible.
	 * @param separator The character that separates hostname segments.
	 * @return The visible segment of the hostname.
	 */
	inline std::string VisiblePart(const std::string& host, size_t hostparts, char separator);
}

/** Defines the interface for the cloak API. */
class Cloak::APIBase
	: public Service::SimpleProvider
{
public:
	APIBase(const WeakModulePtr& parent)
		: Service::SimpleProvider(parent, "cloakapi")
	{
	}

	/** Retrieves the cloak list for the specified user.
	 * @param user The user to retrieve cloaks for.
	 */
	virtual List* GetCloaks(LocalUser* user) = 0;

	/** Determines whether any cloaks of the specified type exist.
	 * @param engine The engine to check the active status of.
	 */
	virtual bool IsActiveCloak(const Cloak::Engine& engine) = 0;

	/** Reset the cloaks for the specified user.
	 * @param user The user to reset the cloaks for.
	 * @param resetdisplay Whether to reset the currently displayed cloak.
	 */
	virtual void ResetCloaks(LocalUser* user, bool resetdisplay) = 0;
};

/** Allows modules to access information regarding cloaks. */
class Cloak::API final
	: public dynamic_reference<Cloak::APIBase>
{
public:
	API(const WeakModulePtr& parent)
		: dynamic_reference<Cloak::APIBase>(parent, "cloakapi")
	{
	}
};

/** Base class for cloak engines. */
class Cloak::Engine
	: public Service::SimpleProvider
{
protected:
	Engine(const WeakModulePtr& Creator, const std::string& Name)
		: Service::SimpleProvider(Creator, "Cloak::Engine", Name)
	{
	}

public:
	/** Creates a new cloak method from the specified config.
	 * @param tag The config tag to configure the cloak method with.
	 * @param primary Whether the created cloak method is the primary method.
	 */
	virtual MethodPtr Create(const std::shared_ptr<ConfigTag>& tag, bool primary) = 0;
};

/** Base class for cloak methods. */
class Cloak::Method
{
private:
	/** The name of the engine that created this method. */
	std::string provname;

	/** If non-empty the connect classes that a user must be in one of to be cloaked by this method. */
	insp::flat_set<std::string> classes;

protected:
	Method(const Engine* engine, const std::shared_ptr<ConfigTag>& tag) ATTR_NOT_NULL(2)
		: provname(engine->service_name)
	{
		StringSplitter klassstream(tag->getString("class"), ',');
		for (std::string klass; klassstream.GetToken(klass); )
			classes.insert(klass);
	}

	bool MatchesUser(LocalUser* user) const
	{
		if (!classes.empty() && classes.find(user->GetClass()->GetName()) == classes.end())
			return false;

		// All fields matched.
		return true;
	}

public:
	virtual ~Method() = default;

	/** Generates a cloak for the specified user.
	 * @param user The user to generate a cloak for.
	 */
	virtual std::optional<Info> Cloak(LocalUser* user) ATTR_NOT_NULL(2) = 0;

	/** Generates a cloak for the specified hostname, IP address, or UNIX socket path.
	 * @param hostip The hostname, IP address, or UNIX socket path to generate a cloak for.
	 */
	virtual std::optional<Info> Cloak(const std::string& hostip) = 0;

	/** Retrieves link compatibility data for this cloak method.
	 * @param data The location to store link compatibility data.
	 */
	virtual void GetLinkData(Module::LinkData& data) = 0;

	/** Retrieves the name of this cloaking method. */
	const auto& GetName() const
	{
		return provname;
	}

	/** Determines whether when this cloak method is behind non-sensitive cloak methods
	 * if it should be treated as if it was the primary cloak method for the purposes of
	 * generating link data.
	 */
	virtual bool IsLinkSensitive() const
	{
		return false;
	}

	/** Determines whether this method is provided by the specified service provider.
	 * @param prov The service provider to check.
	 */
	bool IsProvidedBy(const Service::Provider& prov) const
	{
		return prov.service_name == provname;
	}
};

/** Encapsulates information about a cloak. */
struct Cloak::Info final
{
	/** The hostname of the cloak. */
	const std::string hostname;

	/** The username of the cloak (can be empty). */
	const std::string username;

	/** Creates a new cloak with both a username and hostname.
	 * @param u The username of the cloak.
	 * @param h The hostname of the cloak.
	 */
	Info(const std::string& u, const std::string& h)
		: hostname(h)
		, username(u)
	{
	}

	/** Creates a new cloak with just a hostname.
	 * @param h The hostname of the cloak.
	 */
	Info(const std::string& h)
		: hostname(h)
	{
	}

	/** Default comparator for cloaks. */
	auto operator<=>(const Info&) const = default;

	/** Converts a cloak from the username\@hostname form to a \p Cloak::Info.
	 * @param cloak The cloak to convert.
	 */
	static Cloak::Info FromString(const std::string& cloak)
	{
		auto sep = cloak.find('@');
		if (sep == std::string::npos)
			return Info(cloak);
		return Info(cloak.substr(0, sep), cloak.substr(sep + 1));
	}

	/** Converts a \p Cloak::Info to the username\@hostname form. */
	std::string ToString() const
	{
		std::string ret;
		if (!username.empty())
			ret.append(username).push_back('@');
		if (!hostname.empty())
			ret.append(hostname);
		return ret;
	}
};

inline std::string Cloak::VisiblePart(const std::string& host, size_t hostparts, char separator)
{
	// The position at which we found the last separator.
	std::string::const_reverse_iterator seppos;

	// The number of separators we have seen so far.
	size_t seenseps = 0;

	for (std::string::const_reverse_iterator it = host.rbegin(); it != host.rend(); ++it)
	{
		if (*it != separator)
			continue;

		// We have found a separator!
		seppos = it;
		seenseps += 1;

		// Do we have enough segments to stop?
		if (seenseps >= hostparts)
			break;
	}

	// We only returns a domain part if more than one label is
	// present. See above for a full explanation.
	if (!seenseps)
		return "";

	return std::string(seppos.base(), host.end());
}