mirror of
https://github.com/anope/anope.git
synced 2026-06-12 17:04:47 +02:00
353 lines
9.1 KiB
C++
353 lines
9.1 KiB
C++
// Anope IRC Services <https://www.anope.org/>
|
|
//
|
|
// Copyright (C) 2003-2026 Anope Contributors
|
|
//
|
|
// Anope is free software. You can use, modify, and/or distribute it under the
|
|
// terms of version 2 of the GNU General Public License. See docs/LICENSE.txt
|
|
// for the complete terms of this license and docs/AUTHORS.txt for a list of
|
|
// contributors.
|
|
//
|
|
// Based on the original code of Epona by Lara
|
|
// Based on the original code of Services by Andy Church
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
/// BEGIN CMAKE
|
|
/// find_library("XMLRPC" "xmlrpc" REQUIRED)
|
|
/// message(STATUS "Found XMLRPC: ${XMLRPC}")
|
|
/// target_link_libraries(${SO} PRIVATE ${XMLRPC})
|
|
/// END CMAKE
|
|
|
|
#include <xmlrpc-c/base.h>
|
|
|
|
#include "module.h"
|
|
#include "modules/rpc.h"
|
|
#include "modules/httpd.h"
|
|
|
|
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
|
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
|
|
|
class XMLRPCServiceInterface final
|
|
: public RPC::ServiceInterface
|
|
, public HTTP::Page
|
|
{
|
|
private:
|
|
static void SendError(HTTP::Reply &reply, xmlrpc_env &env)
|
|
{
|
|
Log(LOG_DEBUG) << "XML-RPC error " << env.fault_code << ": " << env.fault_string;
|
|
|
|
xmlrpc_env fault;
|
|
xmlrpc_env_init(&fault);
|
|
|
|
auto *error = xmlrpc_mem_block_new(&fault, 0);
|
|
xmlrpc_serialize_fault(&fault, error, &env);
|
|
|
|
reply.Write((char *)xmlrpc_mem_block_contents(error), xmlrpc_mem_block_size(error));
|
|
|
|
xmlrpc_mem_block_free(error);
|
|
xmlrpc_env_clean(&fault);
|
|
xmlrpc_env_clean(&env);
|
|
}
|
|
|
|
static xmlrpc_value *SerializeElement(xmlrpc_env &env, const RPC::Value &value);
|
|
|
|
static void SerializeArray(xmlrpc_env &env, xmlrpc_value *value, const RPC::Array &array)
|
|
{
|
|
for (const auto &elem : array.GetReplies())
|
|
{
|
|
auto *obj = SerializeElement(env, elem);
|
|
xmlrpc_array_append_item(&env, value, obj);
|
|
}
|
|
}
|
|
|
|
static void SerializeMap(xmlrpc_env &env, xmlrpc_value *value, const RPC::Map &map)
|
|
{
|
|
for (const auto &[k, v] : map.GetReplies())
|
|
{
|
|
auto *obj = SerializeElement(env, v);
|
|
xmlrpc_struct_set_value_n(&env, value, k.c_str(), k.length(), obj);
|
|
}
|
|
}
|
|
|
|
public:
|
|
// Whether we should use the i8 XML-RPC extension.
|
|
static bool enable_i8;
|
|
|
|
// Whether we should use the nil XML-RPC extension.
|
|
static bool enable_nil;
|
|
|
|
XMLRPCServiceInterface(Module *creator)
|
|
: RPC::ServiceInterface(creator)
|
|
, HTTP::Page("/xmlrpc", "text/xml")
|
|
{
|
|
}
|
|
|
|
bool OnRequest(HTTP::Provider *provider, const Anope::string &page_name, HTTP::Client *client, HTTP::Message &message, HTTP::Reply &reply) override
|
|
{
|
|
xmlrpc_env env;
|
|
xmlrpc_env_init(&env);
|
|
|
|
const char *method = nullptr;
|
|
xmlrpc_value *params = nullptr;
|
|
xmlrpc_parse_call(&env, message.content.c_str(), message.content.length(), &method, ¶ms);
|
|
|
|
if (env.fault_occurred)
|
|
{
|
|
SendError(reply, env);
|
|
return true;
|
|
}
|
|
|
|
RPC::Request request(reply);
|
|
|
|
request.name = method;
|
|
delete method;
|
|
|
|
if (!tokens.empty())
|
|
{
|
|
auto it = message.headers.find("Authorization");
|
|
if (it == message.headers.end() || !CanExecute(it->second, request.name))
|
|
{
|
|
xmlrpc_env_set_fault(&env, RPC::ERR_METHOD_NOT_FOUND, "No authorization for method");
|
|
SendError(reply, env);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
ServiceReference<RPC::Event> event(RPC_EVENT, request.name);
|
|
if (!event)
|
|
{
|
|
xmlrpc_env_set_fault(&env, RPC::ERR_METHOD_NOT_FOUND, "Method not found");
|
|
SendError(reply, env);
|
|
xmlrpc_DECREF(params);
|
|
return true;
|
|
}
|
|
|
|
auto paramcount = xmlrpc_array_size(&env, params);
|
|
for (auto idx = 0; idx < paramcount; ++idx)
|
|
{
|
|
xmlrpc_value *value = nullptr;
|
|
xmlrpc_array_read_item(&env, params, idx, &value);
|
|
|
|
Anope::string param;
|
|
if (xmlrpc_value_type(value) != XMLRPC_TYPE_STRING)
|
|
{
|
|
xmlrpc_env_set_fault(&env, RPC::ERR_INVALID_REQUEST, "Anope XML-RPC only supports strings");
|
|
SendError(reply, env);
|
|
xmlrpc_DECREF(value);
|
|
xmlrpc_DECREF(params);
|
|
return true;
|
|
}
|
|
|
|
size_t length = 0;
|
|
const char *string = nullptr;
|
|
xmlrpc_read_string_lp(&env, value, &length, &string);
|
|
|
|
if (env.fault_occurred)
|
|
{
|
|
SendError(reply, env);
|
|
xmlrpc_DECREF(value);
|
|
xmlrpc_DECREF(params);
|
|
continue;
|
|
}
|
|
|
|
request.data.push_back(Anope::string(string, length));
|
|
xmlrpc_DECREF(value);
|
|
}
|
|
xmlrpc_DECREF(params);
|
|
|
|
if (request.data.size() < event->GetMinParams())
|
|
{
|
|
auto error = Anope::Format("Not enough parameters (given %zu, expected %zu)",
|
|
request.data.size(), event->GetMinParams());
|
|
xmlrpc_env_set_fault(&env, RPC::ERR_INVALID_PARAMS, error.c_str());
|
|
SendError(reply, env);
|
|
return true;
|
|
}
|
|
|
|
if (!event->Run(this, client, request))
|
|
return false;
|
|
|
|
this->Reply(request);
|
|
return true;
|
|
}
|
|
|
|
void Reply(RPC::Request &request) override
|
|
{
|
|
xmlrpc_env env;
|
|
xmlrpc_env_init(&env);
|
|
|
|
if (request.GetError())
|
|
{
|
|
xmlrpc_env_set_fault(&env, request.GetError()->first, request.GetError()->second.c_str());
|
|
SendError(request.reply, env);
|
|
return;
|
|
}
|
|
|
|
xmlrpc_value *value;
|
|
if (request.GetRoot())
|
|
value = SerializeElement(env, request.GetRoot().value());
|
|
else if (enable_nil)
|
|
value = xmlrpc_nil_new(&env);
|
|
else
|
|
value = xmlrpc_struct_new(&env);
|
|
|
|
auto *response = xmlrpc_mem_block_new(&env, 0);
|
|
xmlrpc_serialize_response(&env, response, value);
|
|
|
|
request.reply.Write((char *)xmlrpc_mem_block_contents(response), xmlrpc_mem_block_size(response));
|
|
|
|
xmlrpc_DECREF(value);
|
|
xmlrpc_mem_block_free(response);
|
|
}
|
|
};
|
|
|
|
xmlrpc_value *XMLRPCServiceInterface::SerializeElement(xmlrpc_env &env, const RPC::Value &value)
|
|
{
|
|
xmlrpc_value *elem;
|
|
std::visit(overloaded
|
|
{
|
|
[&env, &elem](const RPC::Array &a)
|
|
{
|
|
elem = xmlrpc_array_new(&env);
|
|
SerializeArray(env, elem, a);
|
|
},
|
|
[&env, &elem](const RPC::Map &m)
|
|
{
|
|
elem = xmlrpc_struct_new(&env);
|
|
SerializeMap(env, elem, m);
|
|
},
|
|
[&env, &elem](const Anope::string &s)
|
|
{
|
|
elem = xmlrpc_string_new_lp(&env, s.length(), s.c_str());
|
|
},
|
|
[&env, &elem](std::nullptr_t)
|
|
{
|
|
elem = enable_nil ? xmlrpc_nil_new(&env) : xmlrpc_struct_new(&env);
|
|
},
|
|
[&env, &elem](bool b)
|
|
{
|
|
elem = xmlrpc_bool_new(&env, b);
|
|
},
|
|
[&env, &elem](double d)
|
|
{
|
|
elem = xmlrpc_double_new(&env, d);
|
|
},
|
|
[&env, &elem](int64_t i)
|
|
{
|
|
// XML-RPC does not support 64-bit integers without the use of an
|
|
// extension.
|
|
if (!enable_i8 && (i <= INT32_MAX && i >= INT32_MIN))
|
|
{
|
|
// We can fit this into a int.
|
|
elem = xmlrpc_int_new(&env, i);
|
|
}
|
|
else if (enable_i8)
|
|
{
|
|
// We can fit this into a i8.
|
|
elem = xmlrpc_i8_new(&env, i);
|
|
}
|
|
else
|
|
{
|
|
// We need to convert this to a string.
|
|
auto s = Anope::ToString(i);
|
|
elem = xmlrpc_string_new_lp(&env, s.length(), s.c_str());
|
|
}
|
|
},
|
|
[&env, &elem](uint64_t u)
|
|
{
|
|
// XML-RPC does not support unsigned data types or 64-bit integers
|
|
// without the use of an extension.
|
|
if (!enable_i8 && u <= INT32_MAX)
|
|
{
|
|
// We can fit this into a int.
|
|
elem = xmlrpc_int_new(&env, u);
|
|
}
|
|
else if (enable_i8 && u <= INT64_MAX)
|
|
{
|
|
// We can fit this into a i8.
|
|
elem = xmlrpc_i8_new(&env, u);
|
|
}
|
|
else
|
|
{
|
|
// We need to convert this to a string.
|
|
auto s = Anope::ToString(u);
|
|
elem = xmlrpc_string_new_lp(&env, s.length(), s.c_str());
|
|
}
|
|
},
|
|
}, value.Get());
|
|
return elem;
|
|
}
|
|
|
|
bool XMLRPCServiceInterface::enable_i8 = true;
|
|
bool XMLRPCServiceInterface::enable_nil = true;
|
|
|
|
class ModuleXMLRPC final
|
|
: public Module
|
|
{
|
|
private:
|
|
ServiceReference<HTTP::Provider> httpref;
|
|
XMLRPCServiceInterface xmlrpcinterface;
|
|
|
|
public:
|
|
ModuleXMLRPC(const Anope::string &modname, const Anope::string &creator)
|
|
: Module(modname, creator, EXTRA | VENDOR)
|
|
, httpref(HTTP_PROVIDER)
|
|
, xmlrpcinterface(this)
|
|
{
|
|
xmlrpc_env env;
|
|
xmlrpc_env_init(&env);
|
|
xmlrpc_init(&env);
|
|
if (env.fault_occurred)
|
|
{
|
|
Anope::string fault(env.fault_string);
|
|
xmlrpc_env_clean(&env);
|
|
throw ModuleException("Failed to initialise libxmlrpc: " + fault);
|
|
}
|
|
|
|
unsigned int xmlrpc_major, xmlrpc_minor, xmlrpc_point;
|
|
xmlrpc_version(&xmlrpc_major, &xmlrpc_minor, &xmlrpc_point);
|
|
Log(this) << "Module is running against xmlrpc-c version " << xmlrpc_major << '.' << xmlrpc_minor << '.' << xmlrpc_point;
|
|
}
|
|
|
|
~ModuleXMLRPC() override
|
|
{
|
|
if (httpref)
|
|
httpref->UnregisterPage(&xmlrpcinterface);
|
|
|
|
xmlrpc_term();
|
|
}
|
|
|
|
void OnReload(Configuration::Conf &conf) override
|
|
{
|
|
if (httpref)
|
|
httpref->UnregisterPage(&xmlrpcinterface);
|
|
|
|
auto &modconf = conf.GetModule(this);
|
|
XMLRPCServiceInterface::enable_i8 = modconf.Get<bool>("enable_i8", "yes");
|
|
XMLRPCServiceInterface::enable_nil = modconf.Get<bool>("enable_nil", "yes");
|
|
|
|
this->httpref.SetServiceName(modconf.Get<const Anope::string>("server", "httpd/main"));
|
|
if (!httpref)
|
|
throw ConfigException("Unable to find http reference, is httpd loaded?");
|
|
|
|
xmlrpcinterface.tokens.clear();
|
|
for (int i = 0; i < modconf.CountBlock("token"); ++i)
|
|
{
|
|
const auto &block = modconf.GetBlock("token", i);
|
|
|
|
RPC::Token token;
|
|
token.token = block.Get<const Anope::string>("token");
|
|
if (!token.token.empty())
|
|
{
|
|
token.token_hash = block.Get<const Anope::string>("token_hash");
|
|
spacesepstream(block.Get<const Anope::string>("methods")).GetTokens(token.methods);
|
|
xmlrpcinterface.tokens.emplace_back(token);
|
|
}
|
|
}
|
|
|
|
httpref->RegisterPage(&xmlrpcinterface);
|
|
}
|
|
};
|
|
|
|
MODULE_INIT(ModuleXMLRPC)
|