diff options
author | Sadie Powell <sadie@witchery.services> | 2025-02-18 16:15:58 +0000 |
---|---|---|
committer | Sadie Powell <sadie@witchery.services> | 2025-02-18 16:15:58 +0000 |
commit | 98320d130f6c74793d416526e892b11ca5619b91 (patch) | |
tree | 4e0ecde92ac8a7d4a1bf09f3dff82389fb48a666 | |
parent | 37f21a2e1eae792aa30a397f64e93a624c39631e (diff) |
Rewrite the xmlrpc module using libxmlrpc-c.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | data/modules.example.conf | 2 | ||||
-rw-r--r-- | modules/extra/xmlrpc.cpp | 214 | ||||
-rw-r--r-- | modules/rpc/xmlrpc.cpp | 275 |
4 files changed, 216 insertions, 276 deletions
diff --git a/.gitignore b/.gitignore index bfe5f3746..9e048eb5a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ modules/sqlite.cpp modules/ssl_gnutls.cpp modules/ssl_openssl.cpp modules/stats +modules/xmlrpc.cpp run/ *.mo *.pot diff --git a/data/modules.example.conf b/data/modules.example.conf index bfc1643ac..e3fbb4294 100644 --- a/data/modules.example.conf +++ b/data/modules.example.conf @@ -802,7 +802,7 @@ module { name = "sasl" } } /* - * xmlrpc + * [EXTRA] xmlrpc * * Allows remote applications (websites) to execute queries in real time to retrieve data from Anope. * By itself this module does nothing, but allows other modules (rpc_main) to receive and send XMLRPC queries. diff --git a/modules/extra/xmlrpc.cpp b/modules/extra/xmlrpc.cpp new file mode 100644 index 000000000..582a5ab25 --- /dev/null +++ b/modules/extra/xmlrpc.cpp @@ -0,0 +1,214 @@ +/* + * + * (C) 2010-2025 Anope Team + * Contact us at team@anope.org + * + * Please read COPYING and README for further details. + */ + + +/* RequiredLibraries: xmlrpc */ + +#include <xmlrpc-c/base.h> + +#include "module.h" +#include "modules/rpc.h" +#include "modules/httpd.h" + +class MyXMLRPCServiceInterface final + : public RPCServiceInterface + , public HTTPPage +{ +private: + std::deque<RPCEvent *> events; + + void SendError(HTTPReply &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); + } + +public: + MyXMLRPCServiceInterface(Module *creator, const Anope::string &sname) + : RPCServiceInterface(creator, sname) + , HTTPPage("/xmlrpc", "text/xml") + { + } + + void Register(RPCEvent *event) override + { + this->events.push_back(event); + } + + void Unregister(RPCEvent *event) override + { + std::deque<RPCEvent *>::iterator it = std::find(this->events.begin(), this->events.end(), event); + + if (it != this->events.end()) + this->events.erase(it); + } + + bool OnRequest(HTTPProvider *provider, const Anope::string &page_name, HTTPClient *client, HTTPMessage &message, HTTPReply &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; + } + + RPCRequest request(reply); + + request.name = method; + delete method; + + 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) + { + // TODO: error; + xmlrpc_env_set_fault(&env, 0, "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); + + for (auto *e : this->events) + { + if (!e->Run(this, client, request)) + return false; + + if (request.GetError()) + { + xmlrpc_env_set_fault(&env, request.GetError()->first, request.GetError()->second.c_str()); + SendError(reply, env); + return true; + } + + if (!request.GetReplies().empty()) + { + this->Reply(request); + return true; + } + } + + // If we reached this point nobody handled the event. + xmlrpc_env_set_fault(&env, -32601, "Method not found"); + SendError(reply, env); + return true; + } + + void Reply(RPCRequest &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; + } + + auto *value = xmlrpc_struct_new(&env); + for (const auto &[k, v] : request.GetReplies()) + { + auto *str = xmlrpc_string_new_lp(&env, v.length(), v.c_str()); + xmlrpc_struct_set_value_n(&env, value, k.c_str(), k.length(), str); + } + + 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); + } +}; + +class ModuleXMLRPC final + : public Module +{ +private: + ServiceReference<HTTPProvider> httpref; + MyXMLRPCServiceInterface xmlrpcinterface; + +public: + ModuleXMLRPC(const Anope::string &modname, const Anope::string &creator) + : Module(modname, creator, EXTRA | VENDOR) + , xmlrpcinterface(this, "rpc") + { + xmlrpc_env env; + xmlrpc_env_init(&env); + xmlrpc_init(&env); + if (!env.fault_occurred) + return ; + + Anope::string fault(env.fault_string); + xmlrpc_env_clean(&env); + throw ModuleException("Failed to initialise libxmlrpc: " + fault); + } + + ~ModuleXMLRPC() override + { + if (httpref) + httpref->UnregisterPage(&xmlrpcinterface); + + xmlrpc_term(); + } + + void OnReload(Configuration::Conf *conf) override + { + if (httpref) + httpref->UnregisterPage(&xmlrpcinterface); + + this->httpref = ServiceReference<HTTPProvider>("HTTPProvider", conf->GetModule(this)->Get<const Anope::string>("server", "httpd/main")); + if (!httpref) + throw ConfigException("Unable to find http reference, is httpd loaded?"); + + httpref->RegisterPage(&xmlrpcinterface); + } +}; + +MODULE_INIT(ModuleXMLRPC) diff --git a/modules/rpc/xmlrpc.cpp b/modules/rpc/xmlrpc.cpp deleted file mode 100644 index 2ecf00410..000000000 --- a/modules/rpc/xmlrpc.cpp +++ /dev/null @@ -1,275 +0,0 @@ -/* - * - * (C) 2010-2025 Anope Team - * Contact us at team@anope.org - * - * Please read COPYING and README for further details. - */ - -#include "module.h" -#include "modules/rpc.h" -#include "modules/httpd.h" - -static struct special_chars final -{ - Anope::string character; - Anope::string replace; - - special_chars(const Anope::string &c, const Anope::string &r) : character(c), replace(r) { } -} -special[] = { - special_chars("&", "&"), - special_chars("\"", """), - special_chars("<", "<"), - special_chars(">", "&qt;"), - special_chars("'", "'"), - special_chars("\n", "
"), - special_chars("\002", ""), // bold - special_chars("\003", ""), // color - special_chars("\035", ""), // italics - special_chars("\037", ""), // underline - special_chars("\026", ""), // reverses - special_chars("", "") -}; - -class MyXMLRPCServiceInterface final - : public RPCServiceInterface - , public HTTPPage -{ - std::deque<RPCEvent *> events; - -public: - MyXMLRPCServiceInterface(Module *creator, const Anope::string &sname) : RPCServiceInterface(creator, sname), HTTPPage("/xmlrpc", "text/xml") { } - - void Register(RPCEvent *event) override - { - this->events.push_back(event); - } - - void Unregister(RPCEvent *event) override - { - std::deque<RPCEvent *>::iterator it = std::find(this->events.begin(), this->events.end(), event); - - if (it != this->events.end()) - this->events.erase(it); - } - - static Anope::string Sanitize(const Anope::string &string) - { - Anope::string ret = string; - for (int i = 0; !special[i].character.empty(); ++i) - ret = ret.replace_all_cs(special[i].character, special[i].replace); - return ret; - } - - static Anope::string Unescape(const Anope::string &string) - { - Anope::string ret = string; - for (int i = 0; !special[i].character.empty(); ++i) - if (!special[i].replace.empty()) - ret = ret.replace_all_cs(special[i].replace, special[i].character); - - for (size_t i, last = 0; (i = string.find("&#", last)) != Anope::string::npos;) - { - last = i + 1; - - size_t end = string.find(';', i); - if (end == Anope::string::npos) - break; - - Anope::string ch = string.substr(i + 2, end - (i + 2)); - - if (ch.empty()) - continue; - - long l; - if (!ch.empty() && ch[0] == 'x') - l = strtol(ch.substr(1).c_str(), NULL, 16); - else - l = strtol(ch.c_str(), NULL, 10); - - if (l > 0 && l < 256) - ret = ret.replace_all_cs("&#" + ch + ";", Anope::string(l)); - } - - return ret; - } - -private: - static bool GetData(Anope::string &content, Anope::string &tag, Anope::string &data) - { - if (content.empty()) - return false; - - Anope::string prev, cur; - bool istag; - - do - { - prev = cur; - cur.clear(); - - size_t len = 0; - istag = false; - - if (content[0] == '<') - { - len = content.find_first_of('>'); - istag = true; - } - else if (content[0] != '>') - { - len = content.find_first_of('<'); - } - - // len must advance - if (len == Anope::string::npos || len == 0) - break; - - if (istag) - { - cur = content.substr(1, len - 1); - content.erase(0, len + 1); - while (!content.empty() && content[0] == ' ') - content.erase(content.begin()); - } - else - { - cur = content.substr(0, len); - content.erase(0, len); - } - } - while (istag && !content.empty()); - - tag = Unescape(prev); - data = Unescape(cur); - return !istag && !data.empty(); - } - -public: - bool OnRequest(HTTPProvider *provider, const Anope::string &page_name, HTTPClient *client, HTTPMessage &message, HTTPReply &reply) override - { - Anope::string content = message.content, tname, data; - RPCRequest request(reply); - - while (GetData(content, tname, data)) - { - Log(LOG_DEBUG) << "xmlrpc: Tag name: " << tname << ", data: " << data; - if (tname == "methodName") - request.name = data; - else if (tname == "name" && data == "id") - { - GetData(content, tname, data); - request.id = data; - } - else if (tname == "string") - request.data.push_back(data); - } - - for (auto *e : this->events) - { - if (!e->Run(this, client, request)) - return false; - else if (!request.GetReplies().empty()) - { - this->Reply(request); - return true; - } - } - - reply.error = HTTP_PAGE_NOT_FOUND; - reply.Write("Unrecognized query"); - return true; - } - - void Reply(RPCRequest &request) override - { - if (!request.id.empty()) - request.Reply("id", request.id); - - Anope::string xml = - "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" - "<methodResponse>\n"; - - if (request.GetError()) - { - xml += - "<fault>\n" - " <value>\n" - " <struct>\n" - " <member>\n" - " <name>faultCode</name>\n" - " <value>\n" - " <int>" + Anope::ToString(request.GetError()->first) + "</int>\n" - " </value>\n" - " </member>\n" - " <member>\n" - " <name>faultString</name>\n" - " <value>\n" - " <string>" + this->Sanitize(request.GetError()->second) + "</string>\n" - " </value>\n" - " </member>\n" - " </struct>\n" - " </value>\n" - "</fault>\n"; - } - else - { - xml += - "<params>\n" - " <param>\n" - " <value>\n" - " <struct>\n"; - for (const auto &[name, value] : request.GetReplies()) - { - xml += - "<member>\n" - " <name>" + this->Sanitize(name) + "</name>\n" - " <value>\n" - " <string>" + this->Sanitize(value) + "</string>\n" - " </value>\n" - "</member>\n"; - } - xml += - " </struct>\n" - " </value>\n" - " </param>\n" - "</params>\n"; - } - xml += "</methodResponse>"; - - request.reply.Write(xml); - } -}; - -class ModuleXMLRPC final - : public Module -{ - ServiceReference<HTTPProvider> httpref; -public: - MyXMLRPCServiceInterface xmlrpcinterface; - - ModuleXMLRPC(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, EXTRA | VENDOR), - xmlrpcinterface(this, "rpc") - { - - } - - ~ModuleXMLRPC() override - { - if (httpref) - httpref->UnregisterPage(&xmlrpcinterface); - } - - void OnReload(Configuration::Conf *conf) override - { - if (httpref) - httpref->UnregisterPage(&xmlrpcinterface); - this->httpref = ServiceReference<HTTPProvider>("HTTPProvider", conf->GetModule(this)->Get<const Anope::string>("server", "httpd/main")); - if (!httpref) - throw ConfigException("Unable to find http reference, is httpd loaded?"); - httpref->RegisterPage(&xmlrpcinterface); - } -}; - -MODULE_INIT(ModuleXMLRPC) |