IMPORTANT: due to a drive failure, as of 13-Mar-2021, the Mercurial repository had to be re-mirrored, which changed every commit SHA. The old SHAs and trees are backed up in the vault branches. Please migrate to the new branches as soon as you can.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
prosody/plugins/mod_xmlrpc.lua

124 lines
4.6 KiB

-- Prosody IM v0.4
-- Copyright (C) 2008-2009 Matthew Wild
-- Copyright (C) 2008-2009 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module.host = "*" -- Global module
local httpserver = require "net.httpserver";
local st = require "util.stanza";
local pcall = pcall;
local unpack = unpack;
local tostring = tostring;
local is_admin = require "core.usermanager".is_admin;
local jid_split = require "util.jid".split;
local b64_decode = require "util.encodings".base64.decode;
local get_method = require "core.objectmanager".get_object;
local validate_credentials = require "core.usermanager".validate_credentials;
local translate_request = require "util.xmlrpc".translate_request;
local create_response = require "util.xmlrpc".create_response;
local create_error_response = require "util.xmlrpc".create_error_response;
local entity_map = setmetatable({
["amp"] = "&";
["gt"] = ">";
["lt"] = "<";
["apos"] = "'";
["quot"] = "\"";
}, {__index = function(_, s)
if s:sub(1,1) == "#" then
if s:sub(2,2) == "x" then
return string.char(tonumber(s:sub(3), 16));
else
return string.char(tonumber(s:sub(2)));
end
end
end
});
local function xml_unescape(str)
return (str:gsub("&(.-);", entity_map));
end
local function parse_xml(xml)
local stanza = st.stanza("root");
local regexp = "<([^>]*)>([^<]*)";
for elem, text in xml:gmatch(regexp) do
--print("[<"..elem..">|"..text.."]");
if elem:sub(1,1) == "!" or elem:sub(1,1) == "?" then -- neglect comments and processing-instructions
elseif elem:sub(1,1) == "/" then -- end tag
elem = elem:sub(2);
stanza:up(); -- TODO check for start-end tag name match
elseif elem:sub(-1,-1) == "/" then -- empty tag
elem = elem:sub(1,-2);
stanza:tag(elem):up();
else -- start tag
stanza:tag(elem);
end
if #text ~= 0 then -- text
stanza:text(xml_unescape(text));
end
end
return stanza.tags[1];
end
local function handle_xmlrpc_request(method, args)
method = get_method(method);
if not method then return create_error_response(404, "method not found"); end
args = args or {};
local success, result = pcall(method, unpack(args));
if success then
success, result = pcall(create_response, result or "nil");
if success then
return result;
end
return create_error_response(500, "Error in creating response: "..result);
end
return create_error_response(0, result or "nil");
end
local function handle_xmpp_request(origin, stanza)
local query = stanza.tags[1];
if query.name == "query" then
if #query.tags == 1 then
if is_admin(stanza.attr.from) then
local success, method, args = pcall(translate_request, query.tags[1]);
if success then
local result = handle_xmlrpc_request(method, args);
origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:rpc'}):add_child(result));
else
origin.send(st.error_reply(stanza, "modify", "bad-request", method));
end
else origin.send(st.error_reply(stanza, "auth", "forbidden", "No content in XML-RPC request")); end
else origin.send(st.error_reply(stanza, "modify", "bad-request", "No content in XML-RPC request")); end
else origin.send(st.error_reply(stanza, "cancel", "service-unavailable")); end
end
module:add_iq_handler({"c2s", "s2sin"}, "jabber:iq:rpc", handle_xmpp_request);
module:add_feature("jabber:iq:rpc");
-- TODO add <identity category='automation' type='rpc'/> to disco replies
local default_headers = { ['Content-Type'] = 'text/xml' };
local unauthorized_response = { status = '401 UNAUTHORIZED', headers = {['Content-Type']='text/html', ['WWW-Authenticate']='Basic realm="WallyWorld"'}; body = "<html><body>Authentication required</body></html>"; };
local function handle_http_request(method, body, request)
-- authenticate user
local username, password = b64_decode(request['authorization'] or ''):gmatch('([^:]*):(.*)')(); -- TODO digest auth
local node, host = jid_split(username);
if not validate_credentials(host, node, password) and is_admin(username) then
return unauthorized_response;
end
-- parse request
local stanza = body and parse_xml(body);
if (not stanza) or request.method ~= "POST" then
return "<html><body>You really don't look like an XML-RPC client to me... what do you want?</body></html>";
end
-- execute request
local success, method, args = pcall(translate_request, stanza);
if success then
return { headers = default_headers; body = tostring(handle_xmlrpc_request(method, args)) };
end
return "<html><body>Error parsing XML-RPC request: "..tostring(method).."</body></html>";
end
httpserver.new{ port = 9000, base = "xmlrpc", handler = handle_http_request }