summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadie Powell <sadie@witchery.services>2025-02-18 16:15:58 +0000
committerSadie Powell <sadie@witchery.services>2025-02-18 16:15:58 +0000
commit98320d130f6c74793d416526e892b11ca5619b91 (patch)
tree4e0ecde92ac8a7d4a1bf09f3dff82389fb48a666
parent37f21a2e1eae792aa30a397f64e93a624c39631e (diff)
Rewrite the xmlrpc module using libxmlrpc-c.
-rw-r--r--.gitignore1
-rw-r--r--data/modules.example.conf2
-rw-r--r--modules/extra/xmlrpc.cpp214
-rw-r--r--modules/rpc/xmlrpc.cpp275
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, &params);
+
+ 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("&", "&amp;"),
- special_chars("\"", "&quot;"),
- special_chars("<", "&lt;"),
- special_chars(">", "&qt;"),
- special_chars("'", "&#39;"),
- special_chars("\n", "&#xA;"),
- 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)