public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/ejabberd] f45: Fixed all issues, related with upgrading from the previous 2.0.5
@ 2026-09-23 19:06 Peter Lemenkov
  0 siblings, 0 replies; only message in thread
From: Peter Lemenkov @ 2026-09-23 19:06 UTC (permalink / raw)
  To: git-commits

A new commit has been pushed.

Repo   : rpms/ejabberd
Branch : f45
Commit : 838ab7b2edf507cbdef0cb803e581187b8d4a0e6
Author : Peter Lemenkov <peter@fedoraproject.org>
Date   : 2009-12-10T19:01:41+00:00
Stats  : +1681/-9 in 8 file(s)
URL    : https://src.fedoraproject.org/rpms/ejabberd/c/838ab7b2edf507cbdef0cb803e581187b8d4a0e6?branch=f45

Log:
Fixed all issues, related with upgrading from the previous 2.0.5

---
diff --git a/ejabberd-captcha-bashism.patch b/ejabberd-captcha-bashism.patch
new file mode 100644
index 0000000..85a28cc
--- /dev/null
+++ b/ejabberd-captcha-bashism.patch
@@ -0,0 +1,20 @@
+Makes the captcha script explicitly request bash as it uses
+its special variable "RANDOM" which is not defined by POSIX.
+
+The issue is tracked upstream at
+https://support.process-one.net/browse/EJAB-1105
+
+While it will be sensible to incorporate the upstream fix
+(which will make the script work in POSIX-compliant shells also),
+until bash is required in Debian, this patch is OK.
+
+diff --git a/tools/captcha.sh b/tools/captcha.sh
+index 4d40567..d68edfa 100755
+--- a/tools/captcha.sh
++++ b/tools/captcha.sh
+@@ -1,4 +1,4 @@
+-#!/bin/sh
++#!/bin/bash
+ 
+ INPUT=$1
+ 

diff --git a/ejabberd-ejabberdctl_fix.diff b/ejabberd-ejabberdctl_fix.diff
index 3bf5419..a8542aa 100644
--- a/ejabberd-ejabberdctl_fix.diff
+++ b/ejabberd-ejabberdctl_fix.diff
@@ -1,5 +1,5 @@
---- src/ejabberdctl.template.fix_ctl	2009-11-06 22:53:19.000000000 +0300
-+++ src/ejabberdctl.template	2009-11-21 10:43:17.654686409 +0300
+--- src/ejabberdctl.template	2009-11-06 22:53:19.000000000 +0300
++++ src/ejabberdctl.template	2009-12-10 21:07:28.648659178 +0300
 @@ -9,10 +9,10 @@
  
  # define default environment variables
@@ -13,6 +13,15 @@
  
  # parse command line parameters
  ARGS=
+@@ -46,7 +46,7 @@
+     LOGS_DIR=@LOCALSTATEDIR@/log/ejabberd
+ fi
+ if [ "$SPOOLDIR" = "" ] ; then
+-    SPOOLDIR=@LOCALSTATEDIR@/lib/ejabberd
++    SPOOLDIR=@LOCALSTATEDIR@/lib/ejabberd/spool
+ fi
+ if [ "$EJABBERD_DOC_PATH" = "" ] ; then
+     EJABBERD_DOC_PATH=@DOCDIR@
 @@ -60,14 +60,14 @@
  EJID=`id -g $INSTALLUSER`
  EXEC_CMD="false"

diff --git a/ejabberd-http-poll-web-page.patch b/ejabberd-http-poll-web-page.patch
new file mode 100644
index 0000000..4998146
--- /dev/null
+++ b/ejabberd-http-poll-web-page.patch
@@ -0,0 +1,89 @@
+Generates a human-readable XHTML page which is returned
+when a user tries to browse the HTTP-poll URL configured
+for the server (a common mistake).
+
+This issue is tracked in EJAB-1106.
+
+This changeset was introduced in revision 2751,
+branch http://svn.process-one.net/ejabberd/branches/ejabberd-2.1.x
+
+The first stable version containing the fix is 2.1.1.
+
+--- a/src/web/ejabberd_http_poll.erl	(revision 2750)
++++ b/src/web/ejabberd_http_poll.erl	(revision 2751)
+@@ -154,12 +154,32 @@
+ 		    end
+ 	    end;
+ 	_ ->
+-	    {200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], ""}
++	    HumanHTMLxmlel = get_human_html_xmlel(),
++	    {200, [?CT, {"Set-Cookie", "ID=-2:0; expires=-1"}], HumanHTMLxmlel}
+     end;
+ process(_, _Request) ->
+     {400, [], {xmlelement, "h1", [],
+ 	       [{xmlcdata, "400 Bad Request"}]}}.
+ 
++%% Code copied from mod_http_bind.erl and customized
++get_human_html_xmlel() ->
++    Heading = "ejabberd " ++ atom_to_list(?MODULE),
++    {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
++     [{xmlelement, "head", [],
++       [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
++      {xmlelement, "body", [],
++       [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
++        {xmlelement, "p", [],
++         [{xmlcdata, "An implementation of "},
++          {xmlelement, "a",
++	   [{"href", "http://xmpp.org/extensions/xep-0025.html"}],
++           [{xmlcdata, "Jabber HTTP Polling (XEP-0025)"}]}]},
++        {xmlelement, "p", [],
++         [{xmlcdata, "This web page is only informative. "
++	   "To use HTTP-Poll you need a Jabber/XMPP client that supports it."}
++	 ]}
++       ]}]}.
++
+ %%%----------------------------------------------------------------------
+ %%% Callback functions from gen_fsm
+ %%%----------------------------------------------------------------------
+--- a/src/web/mod_http_bind.erl	(revision 2750)
++++ b/src/web/mod_http_bind.erl	(revision 2751)
+@@ -69,7 +69,14 @@
+     ejabberd_http_bind:process_request(Data, IP);
+ process([], #request{method = 'GET',
+                      data = []}) ->
+-    Heading = "Ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ ?MOD_HTTP_BIND_VERSION,
++    get_human_html_xmlel();
++process(_Path, _Request) ->
++    ?DEBUG("Bad Request: ~p", [_Request]),
++    {400, [], {xmlelement, "h1", [],
++	       [{xmlcdata, "400 Bad Request"}]}}.
++
++get_human_html_xmlel() ->
++    Heading = "ejabberd " ++ atom_to_list(?MODULE) ++ " v" ++ ?MOD_HTTP_BIND_VERSION,
+     {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"}],
+      [{xmlelement, "head", [],
+        [{xmlelement, "title", [], [{xmlcdata, Heading}]}]},
+@@ -77,15 +84,15 @@
+        [{xmlelement, "h1", [], [{xmlcdata, Heading}]},
+         {xmlelement, "p", [],
+          [{xmlcdata, "An implementation of "},
+-          {xmlelement, "a", [{"href", "http://www.xmpp.org/extensions/xep-0206.html"}],
+-           [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]}
+-       ]}]};
+-process(_Path, _Request) ->
+-    ?DEBUG("Bad Request: ~p", [_Request]),
+-    {400, [], {xmlelement, "h1", [],
+-	       [{xmlcdata, "400 Bad Request"}]}}.
++          {xmlelement, "a",
++	   [{"href", "http://xmpp.org/extensions/xep-0206.html"}],
++           [{xmlcdata, "XMPP over BOSH (XEP-0206)"}]}]},
++        {xmlelement, "p", [],
++         [{xmlcdata, "This web page is only informative. "
++	   "To use HTTP-Bind you need a Jabber/XMPP client that supports it."}
++	 ]}
++       ]}]}.
+ 
+-
+ %%%----------------------------------------------------------------------
+ %%% BEHAVIOUR CALLBACKS
+ %%%----------------------------------------------------------------------

diff --git a/ejabberd-mod_admin_extra.patch b/ejabberd-mod_admin_extra.patch
new file mode 100644
index 0000000..0e5ea0f
--- /dev/null
+++ b/ejabberd-mod_admin_extra.patch
@@ -0,0 +1,1305 @@
+Adds the mod_admin_extra module to ejabberd.
+This module extends the functionality provided by ejabberdctl
+by adding several new commands.
+
+The code is taken from the ProcessOne repository:
+https://forge.process-one.net/browse/ejabberd-modules/mod_admin_extra
+(trunk, revision 976).
+
+diff --git a/src/ejabberd.app b/src/ejabberd.app
+index e0e943e..f017015 100644
+--- a/src/ejabberd.app
++++ b/src/ejabberd.app
+@@ -104,6 +104,7 @@
+ 	     mod_vcard_ldap,
+ 	     mod_vcard_odbc,
+ 	     mod_version,
++             mod_admin_extra,
+ 	     node_buddy,
+ 	     node_club,
+ 	     node_default,
+diff --git a/src/mod_admin_extra.erl b/src/mod_admin_extra.erl
+new file mode 100644
+index 0000000..9f3ca14
+--- /dev/null
++++ b/src/mod_admin_extra.erl
+@@ -0,0 +1,1279 @@
++%%%-------------------------------------------------------------------
++%%% File    : mod_admin_extra.erl
++%%% Author  : Badlop <badlop@process-one.net>
++%%% Purpose : Contributed administrative functions and commands
++%%% Created : 10 Aug 2008 by Badlop <badlop@process-one.net>
++%%%
++%%%
++%%% ejabberd, Copyright (C) 2002-2008   ProcessOne
++%%%
++%%% This program is free software; you can redistribute it and/or
++%%% modify it under the terms of the GNU General Public License as
++%%% published by the Free Software Foundation; either version 2 of the
++%%% License, or (at your option) any later version.
++%%%
++%%% This program is distributed in the hope that it will be useful,
++%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
++%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++%%% General Public License for more details.
++%%%
++%%% You should have received a copy of the GNU General Public License
++%%% along with this program; if not, write to the Free Software
++%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
++%%% 02111-1307 USA
++%%%
++%%%-------------------------------------------------------------------
++
++-module(mod_admin_extra).
++-author('badlop@process-one.net').
++
++-behaviour(gen_mod).
++
++-export([start/2, stop/1,
++	 %% Node
++	 compile/1,
++	 load_config/1,
++	 get_cookie/0,
++	 remove_node/1,
++	 export2odbc/2,
++	 %% Accounts
++	 set_password/3,
++	 check_password_hash/4,
++	 delete_old_users/1,
++	 delete_old_users_vhost/2,
++	 ban_account/3,
++	 num_active_users/2,
++	 %% Sessions
++	 num_resources/2,
++	 resource_num/3,
++	 kick_session/4,
++	 status_num/2, status_num/1,
++	 status_list/2, status_list/1,
++	 %% Vcard
++	 set_nickname/3,
++	 get_vcard/3,
++	 get_vcard/4,
++	 set_vcard/4,
++	 set_vcard/5,
++	 %% Roster
++	 add_rosteritem/7,
++	 delete_rosteritem/4,
++	 process_rosteritems/5,
++	 get_roster/2,
++	 push_roster/3,
++	 push_roster_all/1,
++	 push_alltoall/2,
++	 %% mod_shared_roster
++	 srg_create/5,
++	 srg_delete/2,
++	 srg_list/1,
++	 srg_get_info/2,
++	 srg_get_members/2,
++	 srg_user_add/4,
++	 srg_user_del/4,
++	 %% Stanza
++	 send_message_headline/4,
++	 send_message_chat/3,
++	 %% Stats
++	 stats/1, stats/2
++	]).
++
++-include("ejabberd.hrl").
++-include("ejabberd_commands.hrl").
++-include("mod_roster.hrl").
++-include("jlib.hrl").
++
++%% Copied from ejabberd_sm.erl
++-record(session, {sid, usr, us, priority, info}).
++
++
++%%%
++%%% gen_mod
++%%%
++
++start(_Host, _Opts) ->
++    ejabberd_commands:register_commands(commands()).
++
++stop(_Host) ->
++    ejabberd_commands:unregister_commands(commands()).
++
++
++%%%
++%%% Register commands
++%%%
++
++commands() ->
++    Vcard1FieldsString = "Some vcard field names in get/set_vcard are:\n"
++	" FN		- Full Name\n"
++	" NICKNAME	- Nickname\n"
++	" BDAY		- Birthday\n"
++	" TITLE		- Work: Position\n",
++    " ROLE		- Work: Role",
++
++    Vcard2FieldsString = "Some vcard field names and subnames in get/set_vcard2 are:\n"
++	" N FAMILY	- Family name\n"
++	" N GIVEN	- Given name\n"
++	" N MIDDLE	- Middle name\n"
++	" ADR CTRY	- Address: Country\n"
++	" ADR LOCALITY	- Address: City\n"
++	" EMAIL USERID	- E-Mail Address\n"
++	" ORG ORGNAME	- Work: Company\n"
++	" ORG ORGUNIT	- Work: Department",
++
++    VcardXEP = "For a full list of vCard fields check XEP-0054: vcard-temp at "
++	"http://www.xmpp.org/extensions/xep-0054.html",
++
++    [
++     #ejabberd_commands{name = compile, tags = [erlang],
++			desc = "Recompile and reload Erlang source code file",
++			module = ?MODULE, function = compile,
++			args = [{file, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = load_config, tags = [server],
++			desc = "Load ejabberd configuration file",
++			module = ?MODULE, function = load_config,
++			args = [{file, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = get_cookie, tags = [erlang],
++			desc = "Get the Erlang cookie of this node",
++			module = ?MODULE, function = get_cookie,
++			args = [],
++			result = {cookie, string}},
++     #ejabberd_commands{name = remove_node, tags = [erlang],
++			desc = "Remove an ejabberd node from Mnesia clustering config",
++			module = ?MODULE, function = remove_node,
++			args = [{node, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = export2odbc, tags = [mnesia],
++			desc = "Export Mnesia tables to files in directory",
++			module = ?MODULE, function = export2odbc,
++			args = [{host, string}, {path, string}],
++			result = {res, rescode}},
++
++     #ejabberd_commands{name = num_active_users, tags = [accounts, stats],
++			desc = "Get number of users active in the last days",
++			module = ?MODULE, function = num_active_users,
++			args = [{host, string}, {days, integer}],
++			result = {users, integer}},
++     #ejabberd_commands{name = delete_old_users, tags = [accounts, purge],
++			desc = "Delete users that didn't log in last days",
++			module = ?MODULE, function = delete_old_users,
++			args = [{days, integer}],
++			result = {res, restuple}},
++     #ejabberd_commands{name = delete_old_users_vhost, tags = [accounts, purge],
++			desc = "Delete users that didn't log in last days in vhost",
++			module = ?MODULE, function = delete_old_users_vhost,
++			args = [{host, string}, {days, integer}],
++			result = {res, restuple}},
++
++     #ejabberd_commands{name = check_account, tags = [accounts],
++			desc = "Check if an acount exists or not",
++			module = ejabberd_auth, function = is_user_exists,
++			args = [{user, string}, {host, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = check_password, tags = [accounts],
++			desc = "Check if a password is correct",
++			module = ejabberd_auth, function = check_password,
++			args = [{user, string}, {host, string}, {password, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = check_password_hash, tags = [accounts],
++			desc = "Check if the password hash is correct",
++			longdesc = "Allowed hash methods: md5, sha.",
++			module = ?MODULE, function = check_password_hash,
++			args = [{user, string}, {host, string}, {passwordhash, string}, {hashmethod, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = change_password, tags = [accounts],
++			desc = "Change the password of an account",
++			module = ?MODULE, function = set_password,
++			args = [{user, string}, {host, string}, {newpass, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = ban_account, tags = [accounts],
++			desc = "Ban an account: kick sessions and set random password",
++			module = ?MODULE, function = ban_account,
++			args = [{user, string}, {host, string}, {reason, string}],
++			result = {res, rescode}},
++
++     #ejabberd_commands{name = num_resources, tags = [session],
++			desc = "Get the number of resources of a user",
++			module = ?MODULE, function = num_resources,
++			args = [{user, string}, {host, string}],
++			result = {resources, integer}},
++     #ejabberd_commands{name = resource_num, tags = [session],
++			desc = "Resource string of a session number",
++			module = ?MODULE, function = resource_num,
++			args = [{user, string}, {host, string}, {num, integer}],
++			result = {resource, string}},
++     #ejabberd_commands{name = kick_session, tags = [session],
++			desc = "Kick a user session",
++			module = ?MODULE, function = kick_session,
++			args = [{user, string}, {host, string}, {resource, string}, {reason, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = status_num_host, tags = [session, stats],
++			desc = "Number of logged users with this status in host",
++			module = ?MODULE, function = status_num,
++			args = [{host, string}, {status, string}],
++			result = {users, integer}},
++     #ejabberd_commands{name = status_num, tags = [session, stats],
++			desc = "Number of logged users with this status",
++			module = ?MODULE, function = status_num,
++			args = [{status, string}],
++			result = {users, integer}},
++     #ejabberd_commands{name = status_list_host, tags = [session],
++			desc = "List of users logged in host with their statuses",
++			module = ?MODULE, function = status_list,
++			args = [{host, string}, {status, string}],
++			result = {users, {list,
++					  {userstatus, {tuple, [
++								{user, string},
++								{host, string},
++								{resource, string},
++								{priority, integer},
++								{status, string}
++							       ]}}
++					 }}},
++     #ejabberd_commands{name = status_list, tags = [session],
++			desc = "List of logged users with this status",
++			module = ?MODULE, function = status_list,
++			args = [{status, string}],
++			result = {users, {list,
++					  {userstatus, {tuple, [
++								{user, string},
++								{host, string},
++								{resource, string},
++								{priority, integer},
++								{status, string}
++							       ]}}
++					 }}},
++
++     #ejabberd_commands{name = set_nickname, tags = [vcard],
++			desc = "Set nickname in a user's vcard",
++			module = ?MODULE, function = set_nickname,
++			args = [{user, string}, {host, string}, {nickname, string}],
++			result = {res, rescode}},
++
++     #ejabberd_commands{name = get_vcard, tags = [vcard],
++			desc = "Get content from a vCard field",
++			longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
++			module = ?MODULE, function = get_vcard,
++			args = [{user, string}, {host, string}, {name, string}],
++			result = {content, string}},
++     #ejabberd_commands{name = get_vcard2, tags = [vcard],
++			desc = "Get content from a vCard field",
++			longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
++			module = ?MODULE, function = get_vcard,
++			args = [{user, string}, {host, string}, {name, string}, {subname, string}],
++			result = {content, string}},
++     #ejabberd_commands{name = set_vcard, tags = [vcard],
++			desc = "Set content in a vCard field",
++			longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
++			module = ?MODULE, function = set_vcard,
++			args = [{user, string}, {host, string}, {name, string}, {content, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = set_vcard2, tags = [vcard],
++			desc = "Set content in a vCard subfield",
++			longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
++			module = ?MODULE, function = set_vcard2,
++			args = [{user, string}, {host, string}, {name, string}, {subname, string}, {content, string}],
++			result = {res, rescode}},
++
++     #ejabberd_commands{name = add_rosteritem, tags = [roster],
++			desc = "Add an item to a user's roster",
++			module = ?MODULE, function = add_rosteritem,
++			args = [{localuser, string}, {localserver, string},
++				{user, string}, {server, string},
++				{nick, string}, {group, string},
++				{subs, string}],
++			result = {res, rescode}},
++     %%{"", "subs= none, from, to or both"},
++     %%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
++     %%{"", "will add mike@server.com to peter@localhost roster"},
++     #ejabberd_commands{name = delete_rosteritem, tags = [roster],
++			desc = "Delete an item from a user's roster",
++			module = ?MODULE, function = delete_rosteritem,
++			args = [{localuser, string}, {localserver, string},
++				{user, string}, {server, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = process_rosteritems, tags = [roster],
++			desc = "List or delete rosteritems that match filtering options",
++			longdesc = "Explanation of each argument:\n"
++			" - action: what to do with each rosteritem that "
++			"matches all the filtering options\n"
++			" - subs: subscription type\n"
++			" - asks: pending subscription\n"
++			" - users: the JIDs of the local user\n"
++			" - contacts: the JIDs of the contact in the roster\n"
++			"\n"
++			"Allowed values in the arguments:\n"
++			"  ACTION = list | delete\n"
++			"  SUBS = SUB[:SUB]* | any\n"
++			"  SUB = none | from | to | both\n"
++			"  ASKS = ASK[:ASK]* | any\n"
++			"  ASK = none | out | in\n"
++			"  USERS = JID[:JID]* | any\n"
++			"  CONTACTS = JID[:JID]* | any\n"
++			"  JID = characters valid in a JID, and can use the "
++			"globs: *, ? and [...]\n"
++			"\n"
++			"This example will list roster items with subscription "
++			"'none', 'from' or 'to' that have any ask property, of "
++			"local users which JID is in the virtual host "
++			"'example.org' and that the contact JID is either a "
++			"bare server name (without user part) or that has a "
++			"user part and the server part contains the word 'icq'"
++			":\n  list none:from:to any *@example.org *:*@*icq*",
++			module = ?MODULE, function = process_rosteritems,
++			args = [{action, string}, {subs, string},
++				{asks, string}, {users, string},
++				{contacts, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = get_roster, tags = [roster],
++			desc = "Get roster of a local user",
++			module = ?MODULE, function = get_roster,
++			args = [{user, string}, {host, string}],
++			result = {contacts, {list, {contact, {tuple, [
++								      {jid, string},
++								      {nick, string},
++								      {group, string}
++								     ]}}}}},
++     #ejabberd_commands{name = push_roster, tags = [roster],
++			desc = "Push template roster from file to a user",
++			module = ?MODULE, function = push_roster,
++			args = [{file, string}, {user, string}, {host, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = push_roster_all, tags = [roster],
++			desc = "Push template roster from file to all those users",
++			module = ?MODULE, function = push_roster_all,
++			args = [{file, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = push_alltoall, tags = [roster],
++			desc = "Add all the users to all the users of Host in Group",
++			module = ?MODULE, function = push_alltoall,
++			args = [{host, string}, {group, string}],
++			result = {res, rescode}},
++
++     #ejabberd_commands{name = srg_create, tags = [shared_roster_group],
++			desc = "Create a Shared Roster Group",
++			longdesc = "If you want to specify several group "
++			"identifiers in the Display argument,\n"
++			"put  \\ \" around the argument and\nseparate the "
++			"identifiers with \\ \\ n\n"
++			"For example:\n"
++			"  ejabberdctl srg_create group3 localhost "
++			"name desc \\\"group1\\\\ngroup2\\\"",
++			module = ?MODULE, function = srg_create,
++			args = [{group, string}, {host, string},
++				{name, string}, {description, string}, {display, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
++			desc = "Delete a Shared Roster Group",
++			module = ?MODULE, function = srg_delete,
++			args = [{group, string}, {host, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = srg_list, tags = [shared_roster_group],
++			desc = "List the Shared Roster Groups in Host",
++			module = ?MODULE, function = srg_list,
++			args = [{host, string}],
++			result = {groups, {list, {id, string}}}},
++     #ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
++			desc = "Get info of a Shared Roster Group",
++			module = ?MODULE, function = srg_get_info,
++			args = [{group, string}, {host, string}],
++			result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
++     #ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
++			desc = "Get members of a Shared Roster Group",
++			module = ?MODULE, function = srg_get_members,
++			args = [{group, string}, {host, string}],
++			result = {members, {list, {member, string}}}},
++     #ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
++			desc = "Add the JID user@host to the Shared Roster Group",
++			module = ?MODULE, function = srg_user_add,
++			args = [{user, string}, {host, string}, {group, string}, {grouphost, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
++			desc = "Delete this JID user@host from the Shared Roster Group",
++			module = ?MODULE, function = srg_user_del,
++			args = [{user, string}, {host, string}, {group, string}, {grouphost, string}],
++			result = {res, rescode}},
++
++     #ejabberd_commands{name = send_message_chat, tags = [stanza],
++			desc = "Send a chat message to a local or remote bare of full JID",
++			module = ?MODULE, function = send_message_chat,
++			args = [{from, string}, {to, string}, {body, string}],
++			result = {res, rescode}},
++     #ejabberd_commands{name = send_message_headline, tags = [stanza],
++			desc = "Send a headline message to a local or remote bare of full JID",
++			module = ?MODULE, function = send_message_headline,
++			args = [{from, string}, {to, string},
++				{subject, string}, {body, string}],
++			result = {res, rescode}},
++
++     #ejabberd_commands{name = stats, tags = [stats],
++			desc = "Get statistical value: registeredusers onlineusers onlineusersnode uptimeseconds",
++			module = ?MODULE, function = stats,
++			args = [{name, string}],
++			result = {stat, integer}},
++     #ejabberd_commands{name = stats_host, tags = [stats],
++			desc = "Get statistical value for this host: registeredusers onlineusers",
++			module = ?MODULE, function = stats,
++			args = [{name, string}, {host, string}],
++			result = {stat, integer}}
++    ].
++
++
++%%%
++%%% Node
++%%%
++
++compile(File) ->
++    case compile:file(File) of
++	ok -> ok;
++	_ -> error
++    end.
++
++load_config(Path) ->
++    ok = ejabberd_config:load_file(Path).
++
++get_cookie() ->
++    atom_to_list(erlang:get_cookie()).
++
++remove_node(Node) ->
++    mnesia:del_table_copy(schema, list_to_atom(Node)),
++    ok.
++
++export2odbc(Host, Directory) ->
++    Tables = [
++	      {export_last, last},
++	      {export_offline, offline},
++	      {export_passwd, passwd},
++	      {export_private_storage, private_storage},
++	      {export_roster, roster},
++	      {export_vcard, vcard},
++	      {export_vcard_search, vcard_search}],
++    Export = fun({TableFun, Table}) ->
++		     Filename = filename:join([Directory, atom_to_list(Table)++".txt"]),
++		     io:format("Trying to export Mnesia table '~p' on Host '~s' to file '~s'~n", [Table, Host, Filename]),
++		     Res = (catch ejd2odbc:TableFun(Host, Filename)),
++		     io:format("  Result: ~p~n", [Res])
++	     end,
++    lists:foreach(Export, Tables),
++    ok.
++
++
++%%%
++%%% Accounts
++%%%
++
++set_password(User, Host, Password) ->
++    case ejabberd_auth:set_password(User, Host, Password) of
++	ok ->
++	    ok;
++	_ ->
++	    error
++    end.
++
++%% Copied some code from ejabberd_commands.erl
++check_password_hash(User, Host, PasswordHash, HashMethod) ->
++    AccountPass = ejabberd_auth:get_password_s(User, Host),
++    AccountPassHash = case HashMethod of
++			  "md5" -> get_md5(AccountPass);
++			  "sha" -> get_sha(AccountPass);
++			  _ -> undefined
++		      end,
++    case AccountPassHash of
++	undefined -> error;
++	PasswordHash -> ok;
++	_ -> error
++    end.
++get_md5(AccountPass) ->
++    lists:flatten([io_lib:format("~.16B", [X])
++		   || X <- binary_to_list(crypto:md5(AccountPass))]).
++get_sha(AccountPass) ->
++    lists:flatten([io_lib:format("~.16B", [X])
++		   || X <- binary_to_list(crypto:sha(AccountPass))]).
++
++num_active_users(Host, Days) ->
++    list_last_activity(Host, true, Days).
++
++%% Code based on ejabberd/src/web/ejabberd_web_admin.erl
++list_last_activity(Host, Integral, Days) ->
++    {MegaSecs, Secs, _MicroSecs} = now(),
++    TimeStamp = MegaSecs * 1000000 + Secs,
++    TS = TimeStamp - Days * 86400,
++    case catch mnesia:dirty_select(
++		 last_activity, [{{last_activity, {'_', Host}, '$1', '_'},
++				  [{'>', '$1', TS}],
++				  [{'trunc', {'/',
++					      {'-', TimeStamp, '$1'},
++					      86400}}]}]) of
++							      {'EXIT', _Reason} ->
++		 [];
++	       Vals ->
++		 Hist = histogram(Vals, Integral),
++		 if
++		     Hist == [] ->
++			 0;
++		     true ->
++			 Left = Days - length(Hist),
++			 Tail = if
++				    Integral ->
++					lists:duplicate(Left, lists:last(Hist));
++				    true ->
++					lists:duplicate(Left, 0)
++				end,
++			 lists:nth(Days, Hist ++ Tail)
++		 end
++	 end.
++histogram(Values, Integral) ->
++    histogram(lists:sort(Values), Integral, 0, 0, []).
++histogram([H | T], Integral, Current, Count, Hist) when Current == H ->
++    histogram(T, Integral, Current, Count + 1, Hist);
++histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H ->
++    if
++	Integral ->
++	    histogram(Values, Integral, Current + 1, Count, [Count | Hist]);
++	true ->
++	    histogram(Values, Integral, Current + 1, 0, [Count | Hist])
++    end;
++histogram([], _Integral, _Current, Count, Hist) ->
++    if
++	Count > 0 ->
++	    lists:reverse([Count | Hist]);
++	true ->
++	    lists:reverse(Hist)
++    end.
++
++
++-record(last_activity, {us, timestamp, status}).
++
++delete_old_users(Days) ->
++    %% Get the list of registered users
++    Users = ejabberd_auth:dirty_get_registered_users(),
++
++    {removed, N, UR} = delete_old_users(Days, Users),
++    {ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
++
++delete_old_users_vhost(Host, Days) ->
++    %% Get the list of registered users
++    Users = ejabberd_auth:get_vh_registered_users(Host),
++
++    {removed, N, UR} = delete_old_users(Days, Users),
++    {ok, io_lib:format("Deleted ~p users: ~p", [N, UR])}.
++
++delete_old_users(Days, Users) ->
++    %% Convert older time
++    SecOlder = Days*24*60*60,
++
++    %% Get current time
++    {MegaSecs, Secs, _MicroSecs} = now(),
++    TimeStamp_now = MegaSecs * 1000000 + Secs,
++
++    %% For a user, remove if required and answer true
++    F = fun({LUser, LServer}) ->
++		%% Check if the user is logged
++		case ejabberd_sm:get_user_resources(LUser, LServer) of
++		    %% If it isnt
++		    [] ->
++			%% Look for his last_activity
++			case mnesia:dirty_read(last_activity, {LUser, LServer}) of
++			    %% If it is
++			    %% existent:
++			    [#last_activity{timestamp = TimeStamp}] ->
++				%% get his age
++				Sec = TimeStamp_now - TimeStamp,
++				%% If he is
++				if
++				    %% younger than SecOlder:
++				    Sec < SecOlder ->
++					%% do nothing
++					false;
++				    %% older:
++				    true ->
++					%% remove the user
++					ejabberd_auth:remove_user(LUser, LServer),
++					true
++				end;
++			    %% nonexistent:
++			    [] ->
++				%% remove the user
++				ejabberd_auth:remove_user(LUser, LServer),
++				true
++			end;
++		    %% Else
++		    _ ->
++			%% do nothing
++			false
++		end
++	end,
++    %% Apply the function to every user in the list
++    Users_removed = lists:filter(F, Users),
++    {removed, length(Users_removed), Users_removed}.
++
++
++%%
++%% Ban account
++
++ban_account(User, Host, ReasonText) ->
++    Reason = prepare_reason(ReasonText),
++    kick_sessions(User, Host, Reason),
++    set_random_password(User, Host, Reason),
++    ok.
++
++kick_sessions(User, Server, Reason) ->
++    lists:map(
++      fun(Resource) ->
++	      kick_this_session(User, Server, Resource, Reason)
++      end,
++      get_resources(User, Server)).
++
++get_resources(User, Server) ->
++    lists:map(
++      fun(Session) ->
++	      element(3, Session#session.usr)
++      end,
++      get_sessions(User, Server)).
++
++get_sessions(User, Server) ->
++    LUser = jlib:nodeprep(User),
++    LServer = jlib:nameprep(Server),
++    Sessions =  mnesia:dirty_index_read(session, {LUser, LServer}, #session.us),
++    true = is_list(Sessions),
++    Sessions.
++
++set_random_password(User, Server, Reason) ->
++    NewPass = build_random_password(Reason),
++    set_password_auth(User, Server, NewPass).
++
++build_random_password(Reason) ->
++    Date = jlib:timestamp_to_iso(calendar:universal_time()),
++    RandomString = randoms:get_string(),
++    "BANNED_ACCOUNT--" ++ Date ++ "--" ++ RandomString ++ "--" ++ Reason.
++
++set_password_auth(User, Server, Password) ->
++    ok = ejabberd_auth:set_password(User, Server, Password).
++
++prepare_reason([]) ->
++    "Kicked by administrator";
++prepare_reason([Reason]) ->
++    Reason;
++prepare_reason(Reason) when is_list(Reason) ->
++    Reason;
++prepare_reason(StringList) ->
++    string:join(StringList, "_").
++
++
++%%%
++%%% Sessions
++%%%
++
++num_resources(User, Host) ->
++    length(ejabberd_sm:get_user_resources(User, Host)).
++
++resource_num(User, Host, Num) ->
++    Resources = ejabberd_sm:get_user_resources(User, Host),
++    case (0<Num) and (Num=<length(Resources)) of
++	true ->
++	    lists:nth(Num, Resources);
++	false ->
++	    lists:flatten(io_lib:format("Error: Wrong resource number: ~p", [Num]))
++    end.
++
++kick_session(User, Server, Resource, ReasonText) ->
++    kick_this_session(User, Server, Resource, prepare_reason(ReasonText)),
++    ok.
++
++kick_this_session(User, Server, Resource, Reason) ->
++    ejabberd_router:route(
++      jlib:make_jid("", "", ""),
++      jlib:make_jid(User, Server, Resource),
++      {xmlelement, "broadcast", [], [{exit, Reason}]}).
++
++
++status_num(Host, Status) ->
++    length(get_status_list(Host, Status)).
++status_num(Status) ->
++    status_num("all", Status).
++status_list(Host, Status) ->
++    Res = get_status_list(Host, Status),
++    [{U, S, R, P, St} || {U, S, R, P, St} <- Res].
++status_list(Status) ->
++    status_list("all", Status).
++
++
++get_status_list(Host, Status_required) ->
++    %% Get list of all logged users
++    Sessions = ejabberd_sm:dirty_get_my_sessions_list(),
++    %% Reformat the list
++    Sessions2 = [ {Session#session.usr, Session#session.sid, Session#session.priority} || Session <- Sessions],
++    Fhost = case Host of
++		"all" ->
++		    %% All hosts are requested, so dont filter at all
++		    fun(_, _) -> true end;
++		_ ->
++		    %% Filter the list, only Host is interesting
++		    fun(A, B) -> A == B end
++	    end,
++    Sessions3 = [ {Pid, Server, Priority} || {{_User, Server, _Resource}, {_, Pid}, Priority} <- Sessions2, apply(Fhost, [Server, Host])],
++    %% For each Pid, get its presence
++    Sessions4 = [ {ejabberd_c2s:get_presence(Pid), Server, Priority} || {Pid, Server, Priority} <- Sessions3],
++    %% Filter by status
++    Fstatus = case Status_required of
++		  "all" ->
++		      fun(_, _) -> true end;
++		  _ ->
++		      fun(A, B) -> A == B end
++	      end,
++    [{User, Server, Resource, Priority, stringize(Status_text)}
++     || {{User, Resource, Status, Status_text}, Server, Priority} <- Sessions4,
++	apply(Fstatus, [Status, Status_required])].
++
++%% Make string more print-friendly
++stringize(String) ->
++    %% Replace newline characters with other code
++    element(2, regexp:gsub(String, "\n", "\\n")).
++
++
++%%%
++%%% Vcard
++%%%
++
++set_nickname(User, Host, Nickname) ->
++    R = mod_vcard:process_sm_iq(
++	  {jid, User, Host, "", User, Host, ""},
++	  {jid, User, Host, "", User, Host, ""},
++	  {iq, "", set, "", "en",
++	   {xmlelement, "vCard",
++	    [{"xmlns", "vcard-temp"}], [
++					{xmlelement, "NICKNAME", [], [{xmlcdata, Nickname}]}
++				       ]
++	   }}),
++    case R of
++	{iq, [], result, [], _L, []} ->
++	    ok;
++	_ ->
++	    error
++    end.
++
++get_vcard(User, Host, Name) ->
++    get_vcard_content(User, Host, [Name]).
++
++get_vcard(User, Host, Name, Subname) ->
++    get_vcard_content(User, Host, [Name, Subname]).
++
++set_vcard(User, Host, Name, Content) ->
++    set_vcard_content(User, Host, [Name], Content).
++
++set_vcard(User, Host, Name, Subname, Content) ->
++    set_vcard_content(User, Host, [Name, Subname], Content).
++
++
++%%
++%% Internal vcard
++
++get_vcard_content(User, Server, Data) ->
++    [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
++    JID = jlib:make_jid(User, Server, ""),
++    IQ = #iq{type = get, xmlns = ?NS_VCARD},
++    IQr = Module:Function(JID, JID, IQ),
++    case IQr#iq.sub_el of
++	[A1] ->
++	    case get_vcard(Data, A1) of
++		false -> "Error: no_value";
++		Elem -> xml:get_tag_cdata(Elem)
++	    end;
++	[] ->
++	    "Error: no_vcard"
++    end.
++
++get_vcard([Data1, Data2], A1) ->
++    case xml:get_subtag(A1, Data1) of
++    	false -> false;
++	A2 -> get_vcard([Data2], A2)
++    end;
++
++get_vcard([Data], A1) ->
++    xml:get_subtag(A1, Data).
++
++set_vcard_content(User, Server, Data, Content) ->
++    [{_, Module, Function, _Opts}] = ets:lookup(sm_iqtable, {?NS_VCARD, Server}),
++    JID = jlib:make_jid(User, Server, ""),
++    IQ = #iq{type = get, xmlns = ?NS_VCARD},
++    IQr = Module:Function(JID, JID, IQ),
++
++    %% Get old vcard
++    A4 = case IQr#iq.sub_el of
++	     [A1] ->
++		 {_, _, _, A2} = A1,
++		 update_vcard_els(Data, Content, A2);
++	     [] ->
++		 update_vcard_els(Data, Content, [])
++	 end,
++
++    %% Build new vcard
++    SubEl = {xmlelement, "vCard", [{"xmlns","vcard-temp"}], A4},
++    IQ2 = #iq{type=set, sub_el = SubEl},
++
++    Module:Function(JID, JID, IQ2),
++    ok.
++
++update_vcard_els(Data, Content, Els1) ->
++    Els2 = lists:keysort(2, Els1),
++    [Data1 | Data2] = Data,
++    NewEl = case Data2 of
++		[] ->
++		    {xmlelement, Data1, [], [{xmlcdata,Content}]};
++		[D2] ->
++		    OldEl = case lists:keysearch(Data1, 2, Els2) of
++				{value, A} -> A;
++				false -> {xmlelement, Data1, [], []}
++			    end,
++		    {xmlelement, _, _, ContentOld1} = OldEl,
++		    Content2 = [{xmlelement, D2, [], [{xmlcdata,Content}]}],
++		    ContentOld2 = lists:keysort(2, ContentOld1),
++		    ContentOld3 = lists:keydelete(D2, 2, ContentOld2),
++		    ContentNew = lists:keymerge(2, Content2, ContentOld3),
++		    {xmlelement, Data1, [], ContentNew}
++	    end,
++    Els3 = lists:keydelete(Data1, 2, Els2),
++    lists:keymerge(2, [NewEl], Els3).
++
++
++%%%
++%%% Roster
++%%%
++
++add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, Subs) ->
++    case add_rosteritem(LocalUser, LocalServer, User, Server, Nick, Group, list_to_atom(Subs), []) of
++	{atomic, ok} ->
++	    push_roster_item(LocalUser, LocalServer, User, Server, {add, Nick, Subs, Group}),
++	    ok;
++	_ ->
++	    error
++    end.
++
++add_rosteritem(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) ->
++    subscribe(LU, LS, User, Server, Nick, Group, Subscription, Xattrs).
++
++subscribe(LU, LS, User, Server, Nick, Group, Subscription, Xattrs) ->
++    mnesia:transaction(
++      fun() ->
++	      mnesia:write({roster,
++			    {LU,LS,{User,Server,[]}}, % uj
++			    {LU,LS},                  % user
++			    {User,Server,[]},      % jid
++			    Nick,                  % name: "Mom", []
++			    Subscription,  % subscription: none, to=you see him, from=he sees you, both
++			    none,          % ask: out=send request, in=somebody requests you, none
++			    [Group],       % groups: ["Family"]
++			    Xattrs,        % xattrs: [{"category","conference"}]
++			    []             % xs: []
++			   })
++      end).
++
++delete_rosteritem(LocalUser, LocalServer, User, Server) ->
++    case unsubscribe(LocalUser, LocalServer, User, Server) of
++	{atomic, ok} ->
++	    push_roster_item(LocalUser, LocalServer, User, Server, remove),
++	    ok;
++	_  ->
++	    error
++    end.
++
++unsubscribe(LU, LS, User, Server) ->
++    mnesia:transaction(
++      fun() ->
++              mnesia:delete({roster, {LU, LS, {User, Server, []}}})
++      end).
++
++
++%% -----------------------------
++%% Get Roster
++%% -----------------------------
++
++get_roster(User, Server) ->
++    {ok, Roster} = get_roster2(User, Server),
++    make_roster_xmlrpc(Roster).
++
++get_roster2(User, Server) ->
++    Modules = gen_mod:loaded_modules(Server),
++    Roster = case lists:member(mod_roster, Modules) of
++		 true ->
++		     mod_roster:get_user_roster([], {User, Server});
++		 false ->
++		     case lists:member(mod_roster_odbc, Modules) of
++			 true ->
++			     mod_roster_odbc:get_user_roster([], {User, Server});
++			 false ->
++			     {error, "Neither mod_roster or mod_roster_odbc are enabled"}
++		     end
++	     end,
++    {ok, Roster}.
++
++%% Note: if a contact is in several groups, the contact is returned
++%% several times, each one in a different group.
++make_roster_xmlrpc(Roster) ->
++    lists:foldl(
++      fun(Item, Res) ->
++	      JIDS = jlib:jid_to_string(Item#roster.jid),
++	      Nick = Item#roster.name,
++	      Groups = case Item#roster.groups of
++			   [] -> [""];
++			   Gs -> Gs
++		       end,
++	      ItemsX = [{JIDS, Nick, Group}
++			|| Group <- Groups],
++	      ItemsX ++ Res
++      end,
++      [],
++      Roster).
++
++
++%%-----------------------------
++%% Push Roster from file
++%%-----------------------------
++
++push_roster(File, User, Server) ->
++    {ok, [Roster]} = file:consult(File),
++    subscribe_roster({User, Server, "", User}, Roster).
++
++push_roster_all(File) ->
++    {ok, [Roster]} = file:consult(File),
++    subscribe_all(Roster).
++
++subscribe_all(Roster) ->
++    subscribe_all(Roster, Roster).
++subscribe_all([], _) ->
++    ok;
++subscribe_all([User1 | Users], Roster) ->
++    subscribe_roster(User1, Roster),
++    subscribe_all(Users, Roster).
++
++subscribe_roster(_, []) ->
++    ok;
++%% Do not subscribe a user to itself
++subscribe_roster({Name, Server, Group, Nick}, [{Name, Server, _, _} | Roster]) ->
++    subscribe_roster({Name, Server, Group, Nick}, Roster);
++%% Subscribe Name2 to Name1
++subscribe_roster({Name1, Server1, Group1, Nick1}, [{Name2, Server2, Group2, Nick2} | Roster]) ->
++    subscribe(Name1, Server1, Name2, Server2, Nick2, Group2, both, []),
++    subscribe_roster({Name1, Server1, Group1, Nick1}, Roster).
++
++push_alltoall(S, G) ->
++    Users = ejabberd_auth:get_vh_registered_users(S),
++    Users2 = build_list_users(G, Users, []),
++    subscribe_all(Users2),
++    ok.
++
++build_list_users(_Group, [], Res) ->
++    Res;
++build_list_users(Group, [{User, Server}|Users], Res) ->
++    build_list_users(Group, Users, [{User, Server, Group, User}|Res]).
++
++%% @spec(LU, LS, U, S, Action) -> ok
++%%       Action = {add, Nick, Subs, Group} | remove
++%% @doc Push to the roster of account LU@LS the contact U@S.
++%% The specific action to perform is defined in Action.
++push_roster_item(LU, LS, U, S, Action) ->
++    lists:foreach(fun(R) ->
++			  push_roster_item(LU, LS, R, U, S, Action)
++		  end, ejabberd_sm:get_user_resources(LU, LS)).
++
++push_roster_item(LU, LS, R, U, S, Action) ->
++    Item = build_roster_item(U, S, Action),
++    ResIQ = build_iq_roster_push(Item),
++    LJID = jlib:make_jid(LU, LS, R),
++    ejabberd_router:route(LJID, LJID, ResIQ).
++
++build_roster_item(U, S, {add, Nick, Subs, Group}) ->
++    {xmlelement, "item",
++     [{"jid", jlib:jid_to_string(jlib:make_jid(U, S, ""))},
++      {"name", Nick},
++      {"subscription", Subs}],
++     [{xmlelement, "group", [], [{xmlcdata, Group}]}]
++    };
++build_roster_item(U, S, remove) ->
++    {xmlelement, "item",
++     [{"jid", jlib:jid_to_string(jlib:make_jid(U, S, ""))},
++      {"subscription", "remove"}],
++     []
++    }.
++
++build_iq_roster_push(Item) ->
++    {xmlelement, "iq",
++     [{"type", "set"}, {"id", "push"}],
++     [{xmlelement, "query",
++       [{"xmlns", ?NS_ROSTER}],
++       [Item]
++      }
++     ]
++    }.
++
++
++%%%
++%%% Shared Roster Groups
++%%%
++
++srg_create(Group, Host, Name, Description, Display) ->
++    {ok, DisplayList} = regexp:split(Display, "\\\\n"),
++    Opts = [{name, Name},
++	    {displayed_groups, DisplayList},
++	    {description, Description}],
++    {atomic, ok} = mod_shared_roster:create_group(Host, Group, Opts),
++    ok.
++
++srg_delete(Group, Host) ->
++    {atomic, ok} = mod_shared_roster:delete_group(Host, Group),
++    ok.
++
++srg_list(Host) ->
++    lists:sort(mod_shared_roster:list_groups(Host)).
++
++srg_get_info(Group, Host) ->
++    Opts = mod_shared_roster:get_group_opts(Host,Group),
++    [{io_lib:format("~p", [Title]),
++      io_lib:format("~p", [Value])} || {Title, Value} <- Opts].
++
++srg_get_members(Group, Host) ->
++    Members = mod_shared_roster:get_group_explicit_users(Host,Group),
++    [jlib:jid_to_string(jlib:make_jid(MUser, MServer, ""))
++     || {MUser, MServer} <- Members].
++
++srg_user_add(User, Host, Group, GroupHost) ->
++    {atomic, ok} = mod_shared_roster:add_user_to_group(GroupHost, {User, Host}, Group),
++    ok.
++
++srg_user_del(User, Host, Group, GroupHost) ->
++    {atomic, ok} = mod_shared_roster:remove_user_from_group(GroupHost, {User, Host}, Group),
++    ok.
++
++
++%%%
++%%% Stanza
++%%%
++
++%% @doc Send a chat message to a Jabber account.
++%% @spec (From::string(), To::string(), Body::string()) -> ok
++send_message_chat(From, To, Body) ->
++    Packet = build_packet(message_chat, [Body]),
++    send_packet_all_resources(From, To, Packet).
++
++%% @doc Send a headline message to a Jabber account.
++%% @spec (From::string(), To::string(), Subject::string(), Body::string()) -> ok
++send_message_headline(From, To, Subject, Body) ->
++    Packet = build_packet(message_headline, [Subject, Body]),
++    send_packet_all_resources(From, To, Packet).
++
++%% @doc Send a packet to a Jabber account.
++%% If a resource was specified in the JID,
++%% the packet is sent only to that specific resource.
++%% If no resource was specified in the JID,
++%% and the user is remote or local but offline,
++%% the packet is sent to the bare JID.
++%% If the user is local and is online in several resources,
++%% the packet is sent to all its resources.
++send_packet_all_resources(FromJIDString, ToJIDString, Packet) ->
++    FromJID = jlib:string_to_jid(FromJIDString),
++    ToJID = jlib:string_to_jid(ToJIDString),
++    ToUser = ToJID#jid.user,
++    ToServer = ToJID#jid.server,
++    case ToJID#jid.resource of
++	"" ->
++	    send_packet_all_resources(FromJID, ToUser, ToServer, Packet);
++	Res ->
++	    send_packet_all_resources(FromJID, ToUser, ToServer, Res, Packet)
++    end.
++
++send_packet_all_resources(FromJID, ToUser, ToServer, Packet) ->
++    case ejabberd_sm:get_user_resources(ToUser, ToServer) of
++	[] ->
++	    send_packet_all_resources(FromJID, ToUser, ToServer, "", Packet);
++	ToResources ->
++	    lists:foreach(
++	      fun(ToResource) ->
++		      send_packet_all_resources(FromJID, ToUser, ToServer,
++						ToResource, Packet)
++	      end,
++	      ToResources)
++    end.
++
++send_packet_all_resources(FromJID, ToU, ToS, ToR, Packet) ->
++    ToJID = jlib:make_jid(ToU, ToS, ToR),
++    ejabberd_router:route(FromJID, ToJID, Packet).
++
++
++build_packet(message_chat, [Body]) ->
++    {xmlelement, "message",
++     [{"type", "chat"}],
++     [{xmlelement, "body", [], [{xmlcdata, Body}]}]
++    };
++build_packet(message_headline, [Subject, Body]) ->
++    {xmlelement, "message",
++     [{"type", "headline"}],
++     [{xmlelement, "subject", [], [{xmlcdata, Subject}]},
++      {xmlelement, "body", [], [{xmlcdata, Body}]}
++     ]
++    }.
++
++%%%
++%%% Stats
++%%%
++
++stats(Name) ->
++    case Name of
++	"uptimeseconds" -> trunc(element(1, erlang:statistics(wall_clock))/1000);
++	"registeredusers" -> length(ejabberd_auth:dirty_get_registered_users());
++	"onlineusersnode" -> length(ejabberd_sm:dirty_get_my_sessions_list());
++	"onlineusers" -> length(ejabberd_sm:dirty_get_sessions_list())
++    end.
++
++stats(Name, Host) ->
++    case Name of
++	"registeredusers" -> length(ejabberd_auth:get_vh_registered_users(Host));
++	"onlineusers" -> length(ejabberd_sm:get_vh_session_list(Host))
++    end.
++
++
++
++%%-----------------------------
++%% Purge roster items
++%%-----------------------------
++
++process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) ->
++    Action = case ActionS of
++		 "list" -> list;
++		 "delete" -> delete
++	     end,
++
++    Subs = lists:foldl(
++	     fun(any, _) -> [none, from, to, both];
++		(Sub, Subs) -> [Sub | Subs]
++	     end,
++	     [],
++	     [list_to_atom(S) || S <- string:tokens(SubsS, ":")]
++	    ),
++
++    Asks = lists:foldl(
++	     fun(any, _) -> [none, out, in];
++		(Ask, Asks) -> [Ask | Asks]
++	     end,
++	     [],
++	     [list_to_atom(S) || S <- string:tokens(AsksS, ":")]
++	    ),
++
++    Users = lists:foldl(
++	      fun("any", _) -> ["*", "*@*"];
++		 (U, Us) -> [U | Us]
++	      end,
++	      [],
++	      [S || S <- string:tokens(UsersS, ":")]
++	     ),
++
++    Contacts = lists:foldl(
++		 fun("any", _) -> ["*", "*@*"];
++		    (U, Us) -> [U | Us]
++		 end,
++		 [],
++		 [S || S <- string:tokens(ContactsS, ":")]
++		),
++
++    case rosteritem_purge({Action, Subs, Asks, Users, Contacts}) of
++	{atomic, ok} ->
++	    ok;
++	{error, Reason} ->
++	    io:format("Error purging rosteritems: ~p~n", [Reason]),
++	    error;
++	{badrpc, Reason} ->
++	    io:format("BadRPC purging rosteritems: ~p~n", [Reason]),
++	    error
++    end.
++
++%% @spec ({Action::atom(), Subs::[atom()], Asks::[atom()], User::string(), Contact::string()}) -> {atomic, ok}
++rosteritem_purge(Options) ->
++    Num_rosteritems = mnesia:table_info(roster, size),
++    io:format("There are ~p roster items in total.~n", [Num_rosteritems]),
++    Key = mnesia:dirty_first(roster),
++    ok = rip(Key, Options, {0, Num_rosteritems, 0, 0}),
++    {atomic, ok}.
++
++rip('$end_of_table', _Options, Counters) ->
++    print_progress_line(Counters),
++    ok;
++rip(Key, Options, {Pr, NT, NV, ND}) ->
++    Key_next = mnesia:dirty_next(roster, Key),
++    {Action, _, _, _, _} = Options,
++    ND2 = case decide_rip(Key, Options) of
++	      true ->
++		  apply_action(Action, Key),
++		  ND+1;
++	      false ->
++		  ND
++	  end,
++    NV2 = NV+1,
++    Pr2 = print_progress_line({Pr, NT, NV2, ND2}),
++    rip(Key_next, Options, {Pr2, NT, NV2, ND2}).
++
++apply_action(list, Key) ->
++    {User, Server, JID} = Key,
++    {RUser, RServer, _} = JID,
++    io:format("Matches: ~s@~s ~s@~s~n", [User, Server, RUser, RServer]);
++apply_action(delete, Key) ->
++    apply_action(list, Key),
++    mnesia:dirty_delete(roster, Key).
++
++print_progress_line({Pr, NT, NV, ND}) ->
++    Pr2 = trunc((NV/NT)*100),
++    case Pr == Pr2 of
++	true ->
++	    ok;
++	false ->
++	    io:format("Progress ~p% - visited ~p - deleted ~p~n", [Pr2, NV, ND])
++    end,
++    Pr2.
++
++decide_rip(Key, {_Action, Subs, Asks, User, Contact}) ->
++    case catch mnesia:dirty_read(roster, Key) of
++	[RI] ->
++	    lists:member(RI#roster.subscription, Subs)
++		andalso lists:member(RI#roster.ask, Asks)
++		andalso decide_rip_jid(RI#roster.us, User)
++		andalso decide_rip_jid(RI#roster.jid, Contact);
++	_ ->
++	    false
++    end.
++
++%% Returns true if the server of the JID is included in the servers
++decide_rip_jid({UName, UServer, _UResource}, Match_list) ->
++    decide_rip_jid({UName, UServer}, Match_list);
++decide_rip_jid({UName, UServer}, Match_list) ->
++    lists:any(
++      fun(Match_string) ->
++	      MJID = jlib:string_to_jid(Match_string),
++	      MName = MJID#jid.luser,
++	      MServer = MJID#jid.lserver,
++	      Is_server = is_glob_match(UServer, MServer),
++	      case MName of
++		  [] when UName == [] ->
++		      Is_server;
++		  [] ->
++		      false;
++		  _ ->
++		      Is_server
++			  andalso is_glob_match(UName, MName)
++	      end
++      end,
++      Match_list).
++
++%% Copied from ejabberd-2.0.0/src/acl.erl
++is_regexp_match(String, RegExp) ->
++    case regexp:first_match(String, RegExp) of
++	nomatch ->
++	    false;
++	{match, _, _} ->
++	    true;
++	{error, ErrDesc} ->
++	    io:format(
++	      "Wrong regexp ~p in ACL: ~p",
++	      [RegExp, lists:flatten(regexp:format_error(ErrDesc))]),
++	    false
++    end.
++is_glob_match(String, Glob) ->
++    is_regexp_match(String, regexp:sh_to_awk(Glob)).

diff --git a/ejabberd-piefxis-vhost-export.patch b/ejabberd-piefxis-vhost-export.patch
new file mode 100644
index 0000000..0a01449
--- /dev/null
+++ b/ejabberd-piefxis-vhost-export.patch
@@ -0,0 +1,68 @@
+This patch enables generation of the main XML file
+when exporting data of a virtual host into PIEFXIS format.
+
+This issue is tracked upstream as EJAB-1098.
+
+This changeset was introduced in revisions 2753,
+branch http://svn.process-one.net/ejabberd/branches/ejabberd-2.1.x
+
+The first stable version containing the fix is 2.1.1.
+
+--- a/src/ejabberd_piefxis.erl	(revision 2752)
++++ b/src/ejabberd_piefxis.erl	(revision 2753)
+@@ -212,7 +212,7 @@
+ 		   El),
+ 	    ok;
+ 	{atomic, exists} ->
+-	    ?INFO_MSG("User ~p@~p already exists, using stored profile...~n",
++	    io:format("Account ~s@~s already exists, updating it...~n",
+ 		      [User, Domain]),
+ 	    io:format(""),
+ 	    ok = exmpp_xml:foreach(
+@@ -417,10 +417,10 @@
+ 
+ %%%==================================
+ 
+-%%%% Export server
++%%%% Export hosts
+ 
+-%% @spec (Dir::string()) -> ok
+-export_server(Dir) ->
++%% @spec (Dir::string(), Hosts::[string()]) -> ok
++export_hosts(Dir, Hosts) ->
+     try_start_exmpp(),
+ 
+     FnT = make_filename_template(),
+@@ -430,7 +430,6 @@
+     print(Fd, make_piefxis_xml_head()),
+     print(Fd, make_piefxis_server_head()),
+ 
+-    Hosts = ?MYHOSTS,
+     FilesAndHosts = [{make_host_filename(FnT, Host), Host} || Host <- Hosts],
+     [print(Fd, make_xinclude(FnH)) || {FnH, _Host} <- FilesAndHosts],
+ 
+@@ -443,14 +442,20 @@
+     ok.
+ 
+ %%%==================================
++%%%% Export server
++
++%% @spec (Dir::string()) -> ok
++export_server(Dir) ->
++    Hosts = ?MYHOSTS,
++    export_hosts(Dir, Hosts).
++
++%%%==================================
+ %%%% Export host
+ 
+ %% @spec (Dir::string(), Host::string()) -> ok
+ export_host(Dir, Host) ->
+-    try_start_exmpp(),
+-    FnT = make_filename_template(),
+-    FnH = make_host_filename(FnT, Host),
+-    export_host(Dir, FnH, Host).
++    Hosts = [Host],
++    export_hosts(Dir, Hosts).
+ 
+ %% @spec (Dir::string(), Fn::string(), Host::string()) -> ok
+ export_host(Dir, FnH, Host) ->

diff --git a/ejabberd-vcard-reqs-forwarding.patch b/ejabberd-vcard-reqs-forwarding.patch
new file mode 100644
index 0000000..6280351
--- /dev/null
+++ b/ejabberd-vcard-reqs-forwarding.patch
@@ -0,0 +1,127 @@
+This bug fixes a long-standing bug with ejabberd violating
+RFC 3920 by intercepting vCard request coming to a user's
+full JID, which should be forwarded to the user's client instead.
+This behaviour is especially noticeable in MUC rooms.
+
+This issue is tracked upstream as EJAB-1045.
+
+This changeset was introduced in revisions 2766-2768
+branch http://svn.process-one.net/ejabberd/branches/ejabberd-2.1.x
+
+The first stable version containing the fix is 2.1.1.
+
+--- a/src/ejabberd_sm.erl	(revision 2762)
++++ b/src/ejabberd_sm.erl	(revision 2768)
+@@ -481,7 +481,7 @@
+ 				_ ->
+ 				    Err =
+ 					jlib:make_error_reply(
+-					  Packet, ?ERR_RECIPIENT_UNAVAILABLE),
++					  Packet, ?ERR_SERVICE_UNAVAILABLE),
+ 				    ejabberd_router:route(To, From, Err)
+ 			    end;
+ 			_ ->
+--- a/src/mod_muc/mod_muc_room.erl	(revision 2762)
++++ b/src/mod_muc/mod_muc_room.erl	(revision 2768)
+@@ -470,9 +470,10 @@
+ 	      {xmlelement, "iq", Attrs, _Els} = Packet},
+ 	     StateData) ->
+     Lang = xml:get_attr_s("xml:lang", Attrs),
++    StanzaId = xml:get_attr_s("id", Attrs),
+     case {(StateData#state.config)#config.allow_query_users,
+-	  is_user_online(From, StateData)} of
+-	{true, true} ->
++	  is_user_online_iq(StanzaId, From, StateData)} of
++	{true, {true, NewId, FromFull}} ->
+ 	    case find_jid_by_nick(ToNick, StateData) of
+ 		false ->
+ 		    case jlib:iq_query_info(Packet) of
+@@ -489,13 +490,15 @@
+ 		    end;
+ 		ToJID ->
+ 		    {ok, #user{nick = FromNick}} =
+-			?DICT:find(jlib:jid_tolower(From),
++			?DICT:find(jlib:jid_tolower(FromFull),
+ 				   StateData#state.users),
++		    {ToJID2, Packet2} = handle_iq_vcard(FromFull, ToJID,
++							StanzaId, NewId,Packet),
+ 		    ejabberd_router:route(
+ 		      jlib:jid_replace_resource(StateData#state.jid, FromNick),
+-		      ToJID, Packet)
++		      ToJID2, Packet2)
+ 	    end;
+-	{_, false} ->
++	{_, {false, _, _}} ->
+ 	    case jlib:iq_query_info(Packet) of
+ 		reply ->
+ 		    ok;
+@@ -829,7 +832,6 @@
+ 	    {next_state, normal_state, StateData}
+     end.
+ 
+-
+ %% @doc Check if this non participant can send message to room.
+ %%
+ %% XEP-0045 v1.23:
+@@ -969,6 +971,50 @@
+     LJID = jlib:jid_tolower(JID),
+     ?DICT:is_key(LJID, StateData#state.users).
+ 
++
++%%%
++%%% Handle IQ queries of vCard
++%%%
++is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource /= "" ->
++    {is_user_online(JID, StateData), StanzaId, JID};
++is_user_online_iq(StanzaId, JID, StateData) when JID#jid.lresource == "" ->
++    try stanzaid_unpack(StanzaId) of
++	{OriginalId, Resource} ->
++	    JIDWithResource = jlib:jid_replace_resource(JID, Resource),
++	    {is_user_online(JIDWithResource, StateData),
++	     OriginalId, JIDWithResource}
++    catch
++	_:_ ->
++	    {is_user_online(JID, StateData), StanzaId, JID}
++    end.
++
++handle_iq_vcard(FromFull, ToJID, StanzaId, NewId, Packet) ->
++    ToBareJID = jlib:jid_remove_resource(ToJID),
++    IQ = jlib:iq_query_info(Packet),
++    handle_iq_vcard2(FromFull, ToJID, ToBareJID, StanzaId, NewId, IQ, Packet).
++handle_iq_vcard2(_FromFull, ToJID, ToBareJID, StanzaId, _NewId,
++		 #iq{type = get, xmlns = ?NS_VCARD}, Packet)
++  when ToBareJID /= ToJID ->
++    {ToBareJID, change_stanzaid(StanzaId, ToJID, Packet)};
++handle_iq_vcard2(_FromFull, ToJID, _ToBareJID, _StanzaId, NewId, _IQ, Packet) ->
++    {ToJID, change_stanzaid(NewId, Packet)}.
++
++stanzaid_pack(OriginalId, Resource) ->
++    "berd"++base64:encode_to_string("ejab\0" ++ OriginalId ++ "\0" ++ Resource).
++stanzaid_unpack("berd"++StanzaIdBase64) ->
++    StanzaId = base64:decode_to_string(StanzaIdBase64),
++    ["ejab", OriginalId, Resource] = string:tokens(StanzaId, "\0"),
++    {OriginalId, Resource}.
++
++change_stanzaid(NewId, Packet) ->
++    {xmlelement, Name, Attrs, Els} = jlib:remove_attr("id", Packet),
++    {xmlelement, Name, [{"id", NewId} | Attrs], Els}.
++change_stanzaid(PreviousId, ToJID, Packet) ->
++    NewId = stanzaid_pack(PreviousId, ToJID#jid.lresource),
++    change_stanzaid(NewId, Packet).
++%%%
++%%%
++
+ role_to_list(Role) ->
+     case Role of
+ 	moderator ->   "moderator";
+--- a/src/ejabberd_c2s.erl	(revision 2762)
++++ b/src/ejabberd_c2s.erl	(revision 2768)
+@@ -1220,7 +1220,7 @@
+ 	    "iq" ->
+ 		IQ = jlib:iq_query_info(Packet),
+ 		case IQ of
+-		    #iq{xmlns = ?NS_VCARD} ->
++		    #iq{xmlns = ?NS_VCARD} when (To#jid.luser == "") or (To#jid.lresource == "") ->
+ 			Host = StateData#state.server,
+ 			case ets:lookup(sm_iqtable, {?NS_VCARD, Host}) of
+ 			    [{_, Module, Function, Opts}] ->

diff --git a/ejabberd.spec b/ejabberd.spec
index 9086ec0..87f88fd 100644
--- a/ejabberd.spec
+++ b/ejabberd.spec
@@ -12,7 +12,7 @@
 
 Name:           ejabberd
 Version:        2.1.0
-Release:        1%{?dist}
+Release:        2%{?dist}
 Summary:        A distributed, fault-tolerant Jabber/XMPP server
 
 Group:          Applications/Internet
@@ -24,7 +24,7 @@ Source2:        ejabberd.logrotate
 Source3:	ejabberd.sysconfig
 
 # http://www.ejabberd.im/mod_ctlextra
-# this module will be removed in the near future in favor of mod_admin_extra
+# this module will be removed in the nearest future in favor of mod_admin_extra
 # svn export -r 1020 https://svn.process-one.net/ejabberd-modules/mod_ctlextra/trunk/src/mod_ctlextra.erl
 Source4:        mod_ctlextra.erl
 
@@ -47,7 +47,23 @@ Patch3: ejabberd-ejabberd_cfg_pam_name.diff
 Patch5: ejabberd-mod_ctlextra_mentioning_in_ejabberd_app.diff
 # fixed delays in s2s connections
 Patch8: ejabberd-fixed_delays_in_s2s.patch
-
+# captcha.sh uses some BASH-specific variables
+# see https://support.process-one.net/browse/EJAB-1105
+Patch9: ejabberd-captcha-bashism.patch
+# Introducing mod_admin_extra
+Patch10: ejabberd-mod_admin_extra.patch
+# Fixed long-standing bug with RFC 3920 violation
+# see https://support.process-one.net/browse/EJAB-1045
+Patch11: ejabberd-vcard-reqs-forwarding.patch
+# Generates a human-readable XHTML page which is returned
+# when a user tries to browse the HTTP-poll URL configured
+# for the server (a common mistake).
+# see https://support.process-one.net/browse/EJAB-1106
+Patch12: ejabberd-http-poll-web-page.patch
+# This patch enables generation of the main XML file
+# when exporting data of a virtual host into PIEFXIS format.
+# see https://support.process-one.net/browse/EJAB-1098
+Patch13: ejabberd-piefxis-vhost-export.patch
 
 BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 
@@ -93,6 +109,33 @@ Documentation for ejabberd.
 %{__fe_groupadd} %{uid} -r %{name} &>/dev/null || :
 %{__fe_useradd} %{uid} -r -s /sbin/nologin -d /var/lib/ejabberd -M \
 			-c 'ejabberd' -g %{name} %{name} &>/dev/null || :
+# we should backup DB in every upgrade
+if ejabberdctl status >/dev/null ; then
+	# Use timestamp to make database restoring easier
+	TIME=$(date +%Y-%m-%dT%H:%M:%S)
+	BACKUPDIR=$(mktemp -d -p /var/tmp/ ejabberd-$TIME.XXXXXX)
+	chown ejabberd:ejabberd $BACKUPDIR
+	BACKUP=$BACKUPDIR/ejabberd-database
+	ejabberdctl backup $BACKUP
+	# Change ownership to root:root because ejabberd user might be
+	# removed on package removal.
+	chown -R root:root $BACKUPDIR
+	chmod 700 $BACKUPDIR
+	echo
+	echo The ejabberd database has been backed up to $BACKUP.
+	echo
+fi
+
+# fix cookie path (since ver. 2.1.0 cookie stored in /var/lib/ejabberd/spool
+# rather than in /var/lib/ejabberd
+if [ -f /var/lib/ejabberd/.erlang.cookie ]; then
+	cp -pf /var/lib/ejabberd/{,spool/}.erlang.cookie
+	echo
+	echo The ejabberd cookie file was moved.
+	echo Please remove old one from /var/lib/ejabberd/.erlang.cookie
+	echo
+fi
+
 
 %post
 /sbin/chkconfig --add %{name}
@@ -134,6 +177,11 @@ fi
 %patch3 -p0 -b .pam_name
 %patch5 -p0 -b .mod_ctlextra
 %patch8 -p0 -b .s2s
+%patch9 -p1 -b .bashism
+%patch10 -p1 -b .mod_admin_extra
+%patch11 -p1 -b .vcard_rfc_violation
+%patch12 -p1 -b .xhtml
+%patch13 -p1 -b .piefxis
 
 dos2unix src/odbc/mssql2000.sql
 
@@ -169,6 +217,9 @@ chmod a+x %{buildroot}%{_libdir}/%{name}/priv/lib/*.so
 # fix example SSL certificate path to real one, which we created recently (see above)
 %{__perl} -pi -e 's!/path/to/ssl.pem!/etc/ejabberd/ejabberd.pem!g' %{buildroot}/etc/ejabberd/ejabberd.cfg
 
+# fix captcha path
+%{__perl} -pi -e 's!/lib/ejabberd/priv/bin/captcha.sh!%{_libdir}/%{name}/priv/bin/captcha.sh!g' %{buildroot}/etc/ejabberd/ejabberd.cfg
+
 mkdir -p %{buildroot}/var/log/ejabberd
 mkdir -p %{buildroot}/var/lib/ejabberd/spool
 
@@ -194,12 +245,9 @@ install -p -m 0644 src/odbc/mssql2000.sql %{buildroot}%{_datadir}/%{name}
 install -p -m 0644 src/odbc/mssql2005.sql %{buildroot}%{_datadir}/%{name}
 install -p -m 0644 src/odbc/mysql.sql %{buildroot}%{_datadir}/%{name}
 install -p -m 0644 src/odbc/pg.sql %{buildroot}%{_datadir}/%{name}
-# install example script for CAPTCHA-protection
-install -p -m 0644 tools/captcha.sh %{buildroot}%{_datadir}/%{name}
 
 # removed files, which would be packaged later (see 'files' section)
 rm -rf %{buildroot}%{_docdir}/%{name}
-rm -f %{buildroot}%{_libdir}/%{name}/priv/bin/captcha.sh
 
 %clean
 rm -rf %{buildroot}
@@ -253,6 +301,7 @@ rm -rf %{buildroot}
 %{_libdir}/%{name}/include/mod_roster.hrl
 %{_libdir}/%{name}/include/web/ejabberd_http.hrl
 %{_libdir}/%{name}/include/web/ejabberd_web_admin.hrl
+%{_libdir}/%{name}/priv/bin/captcha.sh
 %attr(4750,root,ejabberd) %{_libdir}/%{name}/priv/bin/epam
 %{_libdir}/%{name}/priv/lib/ejabberd_zlib_drv.so
 %{_libdir}/%{name}/priv/lib/expat_erl.so
@@ -262,7 +311,6 @@ rm -rf %{buildroot}
 %{_libdir}/%{name}/priv/msgs/*.msg
 
 %dir %{_datadir}/%{name}
-%{_datadir}/%{name}/captcha.sh
 %{_datadir}/%{name}/mssql2000.sql
 %{_datadir}/%{name}/mssql2005.sql
 %{_datadir}/%{name}/mysql.sql
@@ -303,6 +351,11 @@ rm -rf %{buildroot}
 %doc doc/yozhikheader.png
 
 %changelog
+* Thu Dec 10 2009 Peter Lemenkov <lemenkov@gmail.com> 2.1.0-2
+- DB backups are made on every upgrade/uninstall
+- Fixed installation of captcha.sh example helper
+- Added patches 9,10,11,12,13 from Debian's package
+
 * Fri Nov 20 2009 Peter Lemenkov <lemenkov@gmail.com> 2.1.0-1
 - Ver. 2.1.0
 - Upstream no longer providing ChangeLog

diff --git a/import.log b/import.log
index cc1cb86..6d5bba4 100644
--- a/import.log
+++ b/import.log
@@ -13,3 +13,4 @@ ejabberd-2_0_5-6_fc11:HEAD:ejabberd-2.0.5-6.fc11.src.rpm:1251228870
 ejabberd-2_0_5-8_fc11:HEAD:ejabberd-2.0.5-8.fc11.src.rpm:1252493017
 ejabberd-2_0_5-10_fc11:HEAD:ejabberd-2.0.5-10.fc11.src.rpm:1257428310
 ejabberd-2_1_0-1_fc12:HEAD:ejabberd-2.1.0-1.fc12.src.rpm:1258790332
+ejabberd-2_1_0-2_fc12:HEAD:ejabberd-2.1.0-2.fc12.src.rpm:1260471420

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-09-23 19:06 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23 19:06 [rpms/ejabberd] f45: Fixed all issues, related with upgrading from the previous 2.0.5 Peter Lemenkov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox