ejabberd_xmlrpc - XML-RPC server

Name: ejabberd_xmlrpc
Purpose: XML-RPC server to execute ejabberd commands
Author: Badlop
Type: Module
Requirements: ejabberd 2.0.3 or newer, and XML-RPC-Erlang 1.13 with IP, Ruby and Xmerl 1.x patches, available as:
Download: ejabberd-modules

ejabberd_xmlrpc is an ejabberd listener that starts a XML-RPC server and waits for external calls.

ejabberd_xmlrpc implements some example calls that can be used to test during the development of a new XML-RPC client. But most imporntantly, ejabberd_xmlrpc is also a frontend to execute ejabberd commands. This way a XML-RPC client can execute any ejabberd command.

This allows external programs written in any language like websites or administrative tools to communicate with ejabberd to get information or to make changes without the need to know ejabberd internals. One example usage is a corporate site in PHP that creates a Jabber user every time a new user is created on the website.

Some benefits of interfacing with the Jabber server by XML-RPC instead of modifying directly the database are:

  • external programs are more simple and easy to develop and debug
  • can communicate with a server in a different machine, and even on Internet

This module was initially based in mod_xmlrpc. If you use ejabberd 2.0.x, you can't use ejabberd_xmlrpc, but you can use mod_xmlrpc.

Related Links

README.txt

CONFIGURE EJABBERD

1. You need to get and install XMLRPC-Erlang.
You can download XMLRPC-Erlang binary files from
  http://www.ejabberd.im/ejabberd_xmlrpc
or compile it yourself:
  wget http://www.ejabberd.im/files/contributions/xmlrpc-1.13-ipr2.tgz
  tar -xzvf xmlrpc-1.13-ipr2.tgz
  cd xmlrpc-1.13/src
  make
  cd ../../
Then you can copy the *.beam files to ejabberd ebin directory,
or add an option like this to the ejabberd start script:
$ erl -pa '/home/jabber/xmlrpc-1.13/ebin' ...

2. Configure ejabberd to start this listener at startup:
edit ejabberd.cfg and add on the 'listen' section:
{listen, [
   {4560, ejabberd_xmlrpc, []},
    ...
 ]}.

3. Start ejabberd.

4. Verify that ejabberd is listening in that port:
$ netstat -n -l | grep 4560
tcp        0      0 0.0.0.0:4560            0.0.0.0:*               LISTEN

5. If there is any problem, check ejabberd.log and sasl.log files

CONFIGURE

The listener allow several configurable options:

    {maxsessions, Integer}
    Number of concurrent connections allowed.
    Default: 10

    {timeout, Integer}
    Timeout of the connections, expressed in milliseconds.
    Default: 5000

    {access_commands, AccessCommands}
    This option allows to define a list of access restrictions.
    If this option is present, then XML-RPC calls must include as
    first argument a struct with a user, server and password of an
    account in ejabberd that has privileges in Access.
    If the option is not present, such struct must not be provided.
    The default value is to not define any restriction: []
    When one or several access restrictions are defined and the
    XML-RPC call provides authentication for an account, each
    restriction is verified until one matches completely:
    the account matches the Access rule,
    the command name is listed in CommandNames,
    and the provided arguments do not contradict Arguments.
    There is more information about AccessCommands in the ejabberd Guide.


Example configuration: XML-RPC calls can execute any command, with any
argument, and no authentication information must be provided:
{listen, [
  {4560, ejabberd_xmlrpc, [{maxsessions, 10}, {timeout, 5000}]},
  ...
 ]}.

In this case authentication information must be provided, but it is
enough that the account exists and the password is valid to execute
any command:
{listen, [
  {4560, ejabberd_xmlrpc, [{maxsessions, 10}, {timeout, 5000},
                           {access_commands, [{all, all, []}]}]},
  ...
 ]}.

In this example the local Jabber account xmlrpc-robot@jabber.example.org
can execute any command with no argument restriction:
{acl, xmlrpcbot, {user, "xmlrpc-robot", "jabber.example.org"}}.
{access, xmlrpcaccess, [{allow, xmlrpcbot}]}.
{listen, [
  {4560, ejabberd_xmlrpc, [{maxsessions, 10}, {timeout, 5000},
                           {access_commands, [{xmlrpcaccess, all, []}]}]},
  ...
 ]}.

Finally, in this complex example the listener only listens in port
4560 of IP address 127.0.0.1, and several access restrictions are
defined (the corresponding ACL and ACCESS are not shown):
{listen, [
  {{4560, "127.0.0.1"}, ejabberd_xmlrpc, [
    {access_commands, [
      %% This bot can execute any command:
      {xmlrpc_bot, all, []},
      %% This bot can execute any command,
      %% but if a 'host' argument is provided, it must be "example.org":
      {xmlrpc_bot_all_example, all, [{host, "example.org"}]},
      %% This bot can only execute the command 'dump'. No argument restriction:
      {xmlrpc_bot_backups, [dump], []}
      %% This bot can only execute the command 'register',
      %% and if argument 'host' is provided, it must be "example.org":
      {xmlrpc_bot_reg_example, [register], [{host, "example.org"}]},
      %% This bot can execute the commands 'register' and 'unregister',
      %% if argument host is provided, it must be "test.org":
      {xmlrpc_bot_reg_test, [register, unregister], [{host, "test.org"}]}
    ]}
  ]},
  ...
 ]}.

USAGE

You can send calls to http://host:4560/
Call:           Arguments:                                             Returns:

 -- debug
echothis        String                                                   String
echothisnew     struct[{sentence, String}]           struct[{repeated, String}]
multhis         struct[{a, Integer}, {b, Integer}]                      Integer
multhisnew      struct[{a, Integer}, {b, Integer}]        struct[{mu, Integer}]

 -- statistics
tellme_title    String                                                   String
tellme_value    String                                                   String
tellme          String                 struct[{title, String}, {value. String}]
With ejabberd_xmlrpc you can execute any ejabberd command with a XML-RPC call.
1. Get a list of available ejabberd commands, for example:
$ ejabberdctl help
Available commands in this ejabberd node:
  connected_users              List all established sessions
  connected_users_number       Get the number of established sessions
  delete_expired_messages      Delete expired offline messages from database
  delete_old_messages days     Delete offline messages older than DAYS
  dump file                    Dump the database to text file
  register user host password  Register a user
  registered_users host        List all registered users in HOST
  reopen_log                   Reopen the log files
  restart                      Restart ejabberd
  restore file                 Restore the database from backup file
  status                       Get ejabberd status
  stop                         Stop ejabberd
  unregister user host         Unregister a user
  user_resources user host     List user's connected resources

2. When you found the command you want to call, get some additional
   help of the arguments and result:
$ ejabberdctl help user_resources
  Command Name: user_resources
  Arguments: user::string
             host::string
  Returns: resources::[ resource::string ]
  Tags: session
  Description: List user's connected resources

3. You can try to execute the command in the shell for the account testuser@localhost:
$ ejabberdctl user_resources testuser localhost
Home
Psi

4. Now implement the proper XML-RPC call in your XML-RPC client.
   This example will use the Erlang library:
$ erl
1> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, user_resources, [{struct, [{user, "testuser"}, {host, "localhost"}]}]}).
{ok,{response,[{struct,[{resources,{array,[{struct,[{resource,"Home"}]},
                                           {struct,[{resource,"Psi"}]}]}}]}]}}

5. Note: if ejabberd_xmlrpc has the option 'access_commands'
   configured with some access restriction (see the example
   configurations provided above), the XML-RPC must include first an
   argument providing information of a valid account. For example:
1> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, user_resources, [
   {struct, [{user, "adminuser"}, {server, "localhost"}, {password, "aeiou"}]},
   {struct, [{user, "testuser"}, {host, "localhost"}]} ]}).
Arguments in XML-RPC calls can be provided in any order; This module will sort the arguments before calling the ejabberd command. If auth is provided in the call when ejabberd_xmlrpc does not require it, the call will return the error: -112 Unknown call

EXAMPLE IN PHP

This is an XML-RPC client in PHP, thanks to Zbyszek ?ó?kiewski and Calder. It requires "allow_url_fopen = On" in your php.ini.
<?
$param=array("user"=>"testuser", "host"=>"localhost");
$request = xmlrpc_encode_request('user_resources', $param, (array('encoding' => 'utf-8')));

$context = stream_context_create(array('http' => array(
    'method' => "POST",
    'header' => "User-Agent: XMLRPC::Client mod_xmlrpc\r\n" .
                "Content-Type: text/xml\r\n" .
                "Content-Length: ".strlen($request),
    'content' => $request
)));

$file = file_get_contents("http://127.0.0.1:4560/RPC2", false, $context);

$response = xmlrpc_decode($file);

if (xmlrpc_is_fault($response)) {
    trigger_error("xmlrpc: $response[faultString] ($response[faultCode])");
} else {
    print_r($response);
}

?>
The response, following the example would be like this:
$ php5 call.php
Array
(
    [resources] => Array
        (
            [0] => Array
                (
                    [resource] => Home
                )
            [1] => Array
                (
                    [resource] => Psi
                )
        )
)
If you configured the option access_commands, you have to provide authentication information by replacing the first lime with something like this:
$param_auth=array("user"=>"analloweduser", "server"=>"localhost", "password"=>"MyPasS997");
$param_comm=array("user"=>"testuser", "host"=>"localhost");
$param=array($param_auth, $param_comm);
**** WARNING: all the remaining text was written for mod_xmlrpc and is NOT valid for ejabberd_xmlrpc ****

TEST

- You can easily try the XML-RPC server starting a new Erlang Virtual Machine and making calls to ejabberd's XML-RPC:
1. Start Erlang with this option:
$ erl -pa '/home/jabber/xmlrpc-1.13/ebin'

2. Now on the Erlang console, write commands and check the results:

1> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, echothis, [800]}).
{ok,{response,[800]}}

2> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, echothis, ["blot cloc 557.889 kg"]}).
{ok,{response,["blot cloc 557.889 kg"]}}

3> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, multhis, [{struct,[{a, 83}, {b, 689}]}]}).
{ok,{response,[57187]}}

4> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, register,
[{struct, [{user, "ggeo"}, {host, "example.com"}, {password, "gogo11"}]}]}).
{ok,{response,[0]}}

5> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, register,
[{struct, [{user, "ggeo"}, {host, "example.com"}, {password, "gogo11"}]}]}).
{ok,{response,[409]}}

6> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, muc_room_change_option,
[{struct, [{name, "test"}, {service, "conference.localhost"},
 {option, "title"}, {value, "Test Room"}]}]}).

7> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, muc_room_set_affiliation,
[{struct, [{name, "test"}, {service, "conference.example.com"},
{jid, "ex@example.com"}, {affiliation, "member"}]}]}).

8> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, muc_room_set_affiliation,
[{struct, [{name, "test"}, {service, "conference.example.com"},
{jid, "ex@example.com"}, {affiliation, "none"}]}]}).


 - Some possible XML-RPC error messages:

   + Client: connection refused: wrong IP, wrong port, the server is not started...

2> xmlrpc:call({127, 0, 0, 1}, 44444, "/", {call, echothis, [800]}).
{error,econnrefused}

   + Client: bad value: a800 is a string, so it must be put into ""

7> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, echothis, [a800]}).
{error,{bad_value,a800}}

   + Server: unknown call: you sent a call that the server does not implement

3> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, bububu, [800]}).
{ok,{response,{fault,-1,"Unknown call: {call,bububu,[800]}"}}}

EXAMPLE IN PYTHON

This is an example XML-RPC client in Python, thanks to Diddek:
import xmlrpclib

server_url = 'http://127.0.0.1:4560';
server = xmlrpclib.Server(server_url);

params = {}
params["user"] = "ggeo"
params["host"] = "localhost"
params["password"] = "gogo11"

result = server.register(params)
print result
This Python example shows how to provide authentication in the call, thanks to Muelli:
import xmlrpclib

server_url = 'http://127.0.0.1:4560'
server = xmlrpclib.ServerProxy(server_url)

EJABBERD_XMLRPC_LOGIN = {'user': 'adminuser', 'server': 'localhost', 'password': 'aeiou'}

def ejabberdctl(command, data):
    fn = getattr(server, command)
    return fn(EJABBERD_XMLRPC_LOGIN, data)

result = ejabberdctl('register', {'user':'ggeo', 'host':'localhost', 'password':'gogo11'})
print result

EXAMPLE IN RUBY

This is an example XML-RPC client in Ruby, thanks to Diddek:
require 'xmlrpc/client'

host = "172.16.29.6:4560"
timeout = 3000000
client = XMLRPC::Client.new2("http://#{host}", "#{host}", timeout)
result = client.call("echothis", "800")
puts result

EXAMPLE IN JAVA

This is an XML-RPC client in Java, thanks to Calder. It requires Apache XML-RPC available at http://ws.apache.org/xmlrpc/
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

public class Test {

	public static void main(String[] args) {
		try {
		    XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
		    config.setServerURL(new URL("http://127.0.0.1:4560/RPC2"));
		    XmlRpcClient client = new XmlRpcClient();
		    client.setConfig(config);

		    /* Command string */
		    String command = "check_password";

		    /* Parameters as struct */
		    Map struct = new HashMap();
		    struct.put("user", "test1");
		    struct.put("host", "localhost");
		    struct.put("password", "test");

		    Object[] params = new Object[]{struct};
		    Integer result = (Integer) client.execute(command, params);
		    System.out.println(result);
		} catch (Exception e) {
			System.out.println(e);
		}
	}

}

EXAMPLE IN C#

This is an XML-RPC client in C#, thanks to Mitchel Constantin.
// Copyright: 2010 Weavver, Inc. 
// Author: Mitchel Constantin 
// License: Public Domain (Limited to this file)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CookComputing.XmlRpc;

namespace Weavver.Vendors.ProcessOne
{
     public struct send_message_chat
     {
          public string from;
          public string to;
          public string body;
     }
     public struct status {}
     public class ejabberdRPC
     {
          [XmlRpcUrl("http://205.134.225.18:4560/RPC2")]
          public interface IStateName : IXmlRpcProxy
          {
               [XmlRpcMethod("send_message_chat")]
               object SendMessageChat(send_message_chat message);
               [XmlRpcMethod("status")]
               object Status(status s);
          }
          public CookComputing.XmlRpc.XmlRpcStruct SendMessageChat(send_message_chat message)
          {
               IStateName proxy = XmlRpcProxyGen.Create();
               proxy.KeepAlive = false;
               return (CookComputing.XmlRpc.XmlRpcStruct) proxy.SendMessageChat(message);
          }
          public CookComputing.XmlRpc.XmlRpcStruct Status(status status)
          {
               IStateName proxy = XmlRpcProxyGen.Create();
               proxy.KeepAlive = false;
               return (CookComputing.XmlRpc.XmlRpcStruct) proxy.Status(status);
          }
     }
}
Syndicate content