ejabberd integration with XMLRPC API



This page describes mod_xmlrpc, which is useful only in old ejabberd 2 releases. If you are using newer ejabberd, you can use ejabberd_xmlrpc which is included in ejabberd, and call any command that is provided by ejabberd. You can find the list of commands and their arguments running "ejabberdctl help" in the shell. Or you can refer to ejabberd documentation: Managing ejabberd.



Description

mod_xmlrpc is a module for ejabberd, a XMPP/Jabber server written in Erlang. It starts a XML-RPC server and waits for external requests. Implemented calls include statistics and user administration. 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

Here is a document (in French) showing example calls in XML (raw XMLRPC) instead of Erlang formalism: PDF|doc/download/attachments/636/french_document_xmlrpc_xml_examples.pdf?version=1&modificationDate=1244121737000\ MSWord|doc/download/attachments/636/french_document_xmlrpc_xml_examples.doc?version=1&modificationDate=1244121737000\

It should be merged with this document.
This document should also include example Java calls. Example Java calls code should also be commited in SVN with mod_xmlrpc: https://svn.process-one.net/customers/process-one/mod_xmlrpc/trunk/

Table of contents

Installation

mod_xmlrpc has been integrated with ejabberd in the latest installer version. This means that the XMLRPC server is already installed and preconfigured.

Configuration

To activate the XMLRPC module, simply uncomment the following line from the 'module' section of _ejabberd.cfg_ file:

{mod_xmlrpc,[{port, 4560},{timeout,5000}]},

With this line the XMLRPC server will be listening on port 4560. Simply change the value here to match your requirements. The timeout value is optional and is expressed in milliseconds. Default timeout value is 5 seconds (5000 milliseconds). If you want the server to keep the connection open, you can set the timeout value to infinity.

Note if you are updating your mod_xmlrpc calls from ejabberd 2.0.x or older to ejabberd 2.1.0 or newer: Since ejabberd 2.1.0, the code in mod_xmlrpc has been reorganized: mod_xmlrpc provides only a few test XML-RPC calls and access to ejabberd commands. All the calls that were implemented in the old mod_xmlrpc has been moved to a new module: mod_admin_p1. So, in ejabberd 2.1.0 and newer it is required to enable mod_admin_p1 which provides all the calls. Also note that most calls have changed the arguments and/or result. So please update the XML-RPC calls according to this updated documentation.

To uncomment a line in ejabberd config file, simply remove the leading percent '%' characters.

Note that it's not yet possible to configure the IP address to listen to, so the XML-RPC server listens on ALL of them.
Since there are no security measures right now, anybody that knows the IP, port, calls and their format can use the module.
You are strongly advised to block the port (default is 4560) on external interfaces.

Usage

Call target

All XMLRPC are send to the following URL: http://host:4560/.

host is the hostname of your ejabberd server. 4560 should match the XMLRPC server port you have defined in your ejabberd.cfg file.

Here is the list of currently implemented methods:

Available methods

Debug methods

echothis

Simple echo implementation, using the old argument and result formatting.

String echothis(String echostring)
echothisnew

Simple echo implementation, using the new argument and result formatting.

Struct(String repeated) echothisnew(Struct(String sentence))
multhis

Send the result of the multiplication operation, using the old argument and result formatting.

int multhis(int FirstNumber, int SecondNumber)
multhisnew

Send the result of the multiplication operation, using the new argument and result formatting.

Struct(int mu) multhisnew(Struct(int FirstNumber, int SecondNumber))

Users administration

create_account

Create an ejabberd user account.

Struct(Integer res) create_account(Struct(String user, String server, String password))
delete_account

Remove an account from the server.

Struct(Integer res) delete_account(Struct(String user, String server))
rename_account

Change an account name.

Struct(Integer res) rename_account(Struct(String user, String server, String newuser, String newserver))

This function creates a new account, and reproduce the roster of the old one.
Offline messages and private storage are lost.

change_password

Change the password on behalf of the given user.

Struct(int res) change_password(Struct(String user, String server, String newpass))


set_nickname

Define user nickname.

Struct(int res) set_nickname(Struct(String user, String server, String nick))

The nickname is set/updated in the user Vcard. Other informations are unchanged.

set_rosternick

Define user nick in all its roster entries.

Struct(int res) set_nickname(Struct(String user, String server, String nick))

The nick is set/updated in the roster, and all roster changes are pushed to user's contacts. Other informations are unchanged.

add_rosteritem

Add an entry in a user's roster.

Struct(Integer res) add_rosteritem(Struct(String user, String server, String jid, String group, String nick, String subs))

jid is the JabberID of the user you would like to add in user roster on the server.
subs is the state of the roster item subscription. It can be either both, to, from or none. nonemeans that presence packets are not send between parties. both means that presence packets are send in both direction. to means that the user see the presence of the given JID. from means that the JID specified sees the user presence.

Do not forget that roster items should be kept symmetric: when adding a roster item for a user, you have to do the symmetric roster item addition.

The group feature is not yet supported, group must be set as empty list.

delete_rosteritem

Remove an entry for a user roster.

Struct(Integer res) delete_rosteritem(Struct(String user, String server, String jid))

Do not forget that roster items should be kept symmetric: when removing a roster item for a user, you have to do the symmetric roster item removal.

This mechanism bypass the standard roster approval addition mechanism and should only be used for server administration or server integration purpose.

Add a symmetrical entry in two users roster.

Struct(Integer res) link_contacts(Struct(String jid1, String nick1, String jid1, String nick2))

jid1 is the JabberID of the user1 you would like to add in user2 roster on the server.
nick1 is the nick of user1
jid2 is the JabberID of the user2 you would like to add in user1 roster on the server.
nick2 is the nick of user2

This mechanism bypass the standard roster approval addition mechanism and should only be userd for server administration or server integration purpose.

Remove a symmetrical entry in two users roster.

Struct(Integer res) unlink_contacts(Struct(String jid1, Stringjid2))

jid1 is the JabberID of the user1
jid2 is the JabberID of the user2

This mechanism bypass the standard roster approval addition mechanism and should only be userd for server administration or server integration purpose.

add_contacts

Call add_rosteritem with subscription "both" for a given list of contacts

Struct(Integer res) add_contacts(Struct(String user, String server, Array of Struct(Array of Struct(String jid, String group, String nick contact) contacts))
remove_contacts

Call del_rosteritem for a given list of contacts

Struct(Integer res) remove_contacts(Struct(String user, Stringserver, Array of Struct(String jid) contacts))
check_users_registration

List registration status for a given list of users

Struct(Array of Struct(Array of Struct(String user, String server, Integer status) auser) users) check_users_registration(Struct(Array of Struct(Array of Struct(String user, String server) auser) users))
get_roster

Retrieve the roster for a given user.

Struct(Array of Struct(Array of Struct(String jid, String group, String nick, String subscription, String pending) contact) contacts) get_roster(Struct(String user, String server))

The function returns a list of the contacts in a user roster.

The function also returns the state of the contact subscription. Subscription can be either "none", "from", "to", "both". Pending can be "in", "out" or "none".

get_roster_with_presence

Retrieve the roster for a given user. The retrieved roster includes presence information.

Struct(Array of Struct(Array of Struct(String jid, String resource, String group, String nick, String subscription, String pending, String show, String status) contact) contacts) get_roster_with_presence(Struct(String user, String server))

The 'show' value contains the user presence flag. It can take limited values:

  • available
  • chat (Free for chat)
  • away
  • dnd (Do not disturb)
  • xa (Not available, extended away)
  • unavailable (Not connected)

The 'status' is a free text defined by the user client.

The function also returns the state of the contact subscription. Subscription can be either "none", "from", "to", "both". Pending can be "in", "out" or "none".

Note: If user is connected several times, only keep the resource with the highest non-negative priority

get_presence

Retrieve the resource with highest priority, and it's presence (show and status message) for a given user.

Struct(Array of Struct(String jid, String show, String status) presence) get_presence(Struct(String user, String server))

The 'jid' value contains the user jid with resource.
The 'show' value contains the user presence flag. It can take limited values:

  • available
  • chat (Free for chat)
  • away
  • dnd (Do not disturb)
  • xa (Not available, extended away)
  • unavailable (Not connected)

The 'status' is a free text defined by the user client.

get_resources

Retrieve all available resources for a given user.

Struct(Array of Struct(String resource) resources) get_resources(Struct(String user, String server))
send_chat

Send chat message to a given user

Struct(Integer res) send_chat(Struct(String from, String to, String body))
send_message

Send normal message to a given user

Struct(Integer res) send_message(Struct(String from, String to, String subject, String body))
send_stanza

Send stanza to a given user

Struct(Integer res) send_stanza(Struct(String from, String to, String stanza))

If Stanza contains a "from" field, then it overrides the passed from argument.
If Stanza contains a "to" field, then it overrides the passed to argument.

Note on return codes

Response code integer contains either 0 or the response code of ejabberd in case of error (Example: 409 returned when creating a user means "conflict").
Error code mapping is defined in JEP-0086

Erlang XML-RPC formalism

For developers willing to test or implement the XML-RPC call in an Erlang client, here is the Erlang XML-RPC structures to use:

Debug methods

Call

Arguments

Returns

echothis

String

String

multhis

struct[{a, Integer}, {b, Integer}]

Integer

Users administration

Call

Arguments

Returns

create_account

struct[{user, String}, {server, String}, {password, String}]

struct[{res, Integer}]

delete_account

struct[{user, String}, {server, String}]

struct[{res, Integer}]

change_password

struct[{user, String}, {server, String}, {newpass, String}]

struct[{res, Integer}]

check_users_registration

struct[{users, array[{struct, [{auser, array[{struct, [{user, String}, {server, String}]}]}]}]}]

struct[{users, array[{struct, [{auser, array[{struct, [{user, String}, {server, String}, {status, Integer}]}]}]}]}]

rename_account

struct[{user, String}, {server, String}, {newuser, String}, {newserver, String}]

struct[{res, Integer}]

set_nickname

struct[{user, String}, {server, String}, {nick, String}]

struct[{res, Integer}]

set_rosternick

struct[{user, String}, {server, String}, {nick, String}]

struct[{res, Integer}]

add_rosteritem

struct[{user, String}, {server, String}, {jid, String}, {group, String}, {nick, String}, {subs, String}]

struct[{res, Integer}]

delete_rosteritem

struct[{user, String}, {server, String}, {jid, String}]

struct[{res, Integer}]

add_contacts

struct[{user, String}, {server, String}, {contacts, {array, [{struct[{contact, {array, [{struct, [{jid, JID}, {group, Group}, {nick, Nick}]}]}}]}]}}]

struct[{res, Integer}]

remove_contacts

struct[{user, String}, {server, String}, {contacts, {array, [{struct[{jid, String}]}]}}]

struct[{res, Integer}]

link_contacts

struct[{jid1, String}, {nick1, String}, {jid2, String}, {nick2, String}]

struct[{res, Integer}]

unlink_contacts

struct[{jid1, String}, {jid2, String}]

struct[{res, Integer}]

get_roster

struct[{user, String}, {server, String}]

struct[{contacts, {array, struct[{contact, {array, [{struct [{jid, String}, {group, String}, {nick, String}, {subscription, String}, {pending, String}]}]}}]}}]

get_roster_with_presence

struct[{user, String}, {server, String}]

struct[{contacts, {array, struct[{contact, {array, [{struct [{jid, String}, {resource, String}, {group, String}, {nick,String}, {subscription, String}, {pending, String}, {show, String},{status, String}]}]}}]}}]

get_presence

struct[{user, String}, {server, String}]

struct[{jid, String}, {show, String}, {status, String}]

get_resources

struct[{user, String}, {server, String}]

struct[{resources, {array, [{struct, [{resource, String}]}]}=C2=A0 }]

send_chat

struct[{from, String}, {to, String}, {body, String}]

struct[{res, Integer}]

send_message

struct[{from, String}, {to, String}, {subject, String}, {body, String}]

struct[{res, Integer}]

send_stanza

struct[{from, String}, {to, String}, {stanza, String}]

struct[{res, Integer}]

Response code integer contains either 0 or the response code of ejabberd in case of error (Example: 409 returned when creating a user means "conflict").
Error code mapping is defined in JEP-0086

Tests

Common examples

If you are familiar with Erlang, you can easily try the XML-RPC server starting a new Erlang Virtual Machine and making calls to ejabberd's XML-RPC. Otherwise, try issuing the following calls using your favorite programming language.

1. Start Erlang with the following command:

$ bin/erl

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

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

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, echothisnew, [{struct, [{sentence, "this is a test."}]}]}).
{ok,{response,[{struct,[{repeated,"this is a test."}]}]}}

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

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

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, create_account,[{struct, [{user, "test"},  {server, "localhost"}, {password, "pass"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, create_account,[{struct, [{user, "test"},  {server, "localhost"}, {password, "pass"}]}]}).
{ok,{response,[{struct,[{res,409}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, change_password, [{struct, [{user, "test"},  {server, "localhost"}, {newpass, "newpass"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, set_nickname, [{struct, [{user, "test"},  {server, "localhost"}, {nick, "nick"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, set_nickname, [{struct, [{user, "test"},  {server, "localhost"}, {nick, "nick2"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, set_rosternick, [{struct, [{user, "test"},  {server, "localhost"}, {nick, "nick2"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, create_account,[{struct, [{user, "test2"},  {server, "localhost"}, {password, "pass"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, add_rosteritem,[{struct, [{user, "test"},  {server, "localhost"}, {jid, "test2@localhost"}, {nick, "test2nick"}, {group, "Friends"},  {subs, "both"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, add_rosteritem,[{struct, [{user, "test2"},  {server, "localhost"}, {jid, "test@localhost"}, {nick, "MyFriend"}, {group, "Coworkers"},  {subs, "both"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, create_account,[{struct, [{user, "test3"},  {server, "localhost"}, {password, "pass"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, delete_account,[{struct, [{user, "test3"},  {server, "localhost"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, delete_rosteritem,[{struct, [{user, "test2"},  {server, "localhost"}, {jid, "test@localhost"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, get_roster,[{struct, [{user, "test"},  {server, "localhost"}]}]}).
{ok,{response,[{struct,[{contacts,{array,[{struct,[{contact,{array,[{struct,[{jid,"test2@localhost"}]},
                                                                    {struct,[{group,[]}]},
                                                                    {struct,[{nick,"test2nick"}]},
                                                                    {struct,[{subscription,[...]}]},
                                                                    {struct,[{pending,...}]}]}}]}]}}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, get_roster,[{struct, [{user, "test2"}, {server, "localhost"}]}]}).
{ok,{response,[{struct,[{contacts,{array,[]}}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, get_roster_with_presence,[{struct, [{user, "test"},  {server, "localhost"}]}]}).
{ok,{response,[{struct,[{contacts,{array,[{struct,[{contact,{array,[{struct,[{jid,"test2@localhost"}]},
                                                                    {struct,[{resource,[]}]},
                                                                    {struct,[{group,[]}]},
                                                                    {struct,[{nick,[...]}]},
                                                                    {struct,[{subscription,...}]},
                                                                    {struct,[{...}]},
                                                                    {struct,[...]},
                                                                    {struct,...}]}}]}]}}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, get_roster_with_presence,[{struct, [{user, "test"},  {server, "localhost"}]}]}).
{ok,{response,[{struct,[{contacts,{array,[{struct,[{contact,{array,[{struct,[{jid,"test2@localhost"}]},
                                                                    {struct,[{resource,[]}]},
                                                                    {struct,[{group,[]}]},
                                                                    {struct,[{nick,[...]}]},
                                                                    {struct,[{subscription,...}]},
                                                                    {struct,[{...}]},
                                                                    {struct,[...]},
                                                                    {struct,...}]}}]}]}}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, get_presence,[{struct, [{user, "test"},{server, "localhost"}]}]}).
{ok,{response,[{struct,[{presence,{array,[{struct,[{jid,"test@localhost/Home"}]},
                                          {struct,[{show,"dnd"}]},
                                          {struct,[{status,"you'rewelcome to talk ! it's demo time..."}]}]}}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, send_chat,[{struct,[{from, "admin@localhost"},{to,"test@localhost"},{body,"Hello"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, send_message,[{struct, [{from, "admin@localhost"},{to, "test@localhost"},{subject,"Test"},{body,"Hello"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}
Stanza="<message from='admin@localhost' type='chat' to='test@localhost'><body>test</body></message>".

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, send_stanza,[{struct, [{from, "admin@localhost"},{to, "test@localhost"},{stanza,Stanza}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, get_resources,[{struct, [{user, "test"},{server, "localhost"}]}]}).
{ok,{response,[{struct,[{resources,{array,[{struct,[{resource,"Home"}]}]}}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, link_contacts,[{struct, [{jid1, "user1@localhost"},{nick1,"user1"},{jid2, "user2@localhost"},{nick2,"user2"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, unlink_contacts,[{struct, [{jid1, "user1@localhost"},{jid2, "user2@localhost"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, add_contacts, [{struct, [{user, "badlop"}, {server, "localhost"}, {contacts, {array, [{struct, [{contact, {array, [{struct, [{group, "Friends"}, {jid, "tom@localhost"}, {nick, "Tom"}]}]}}]}]}}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, rename_account,[{struct,[{user,"user3"},{server,"localhost"},{newuser,"user4"},{newserver,"localhost"}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, check_users_registration, [{struct, [{users, {array, [{struct, [{auser, {array, [{struct, [{user, "test"}, {server, "localhost"}]}]}}, {auser, {array, [{struct, [{user, "test99"}, {server, "localhost"}]}]}}]}]}}]}]}).
{ok,{response,[{struct,[{users,{array,[{struct,[{auser,{array,[{struct,[{user,"test"}]},
                                                               {struct,[{server,"localhost"}]},
                                                               {struct,[{status,1}]}]}}]},
                                       {struct,[{auser,{array,[{struct,[{user,"test99"}]},
                                                               {struct,[{server,"localhost"}]},
                                                               {struct,[{status,0}]}]}}]}]}}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, add_contacts, [{struct, [{user, "test"}, {server, "localhost"}, {contacts,{array, [{struct, [{contact, {array, [{struct, [{group, "Friends"}, {jid, "test2@localhost"}, {nick, "Test2"}]}]}}, {contact, {array, [{struct, [{group, "Friends"}, {jid, "test3@localhost"}, {nick, "Test3"}]}]}}]}]}}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, remove_contacts, [{struct, [{user, "test"}, {server, "localhost"}, {contacts, {array, [{struct, [{jid, "test3@localhost"}]}]}}]}]}).
{ok,{response,[{struct,[{res,0}]}]}}

Error cases

Here are some possible XML-RPC server error messages:

  • "Connection refused" can mean several different problems: wrong IP, wrong port, the server is not started, etc.:
    1> xmlrpc:call({127, 0, 0, 1}, 44444, "/", {call,echothis, [800]}).
    {error,econnrefused}
    
  • "Bad value" messages are often triggered on type error. Inthe following example, a800 is a string, so it must be put into quotes (""):
    2> xmlrpc:call({127, 0, 0, 1}, 4560, "/", {call, echothis, [a800]}).
    {error,{bad_value,a800}}
    
  • an "unknown call" is returned when you try to call a method 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]}"}}}