New Upstream Snapshot - ejabberd-contrib

Ready changes

Summary

Merged new upstream version: 0.2023.01.25+git20230125.1.0db4985 (was: 0.2023.01.25~dfsg0).

Diff

diff --git a/atom_pubsub/COPYING b/atom_pubsub/COPYING
new file mode 100644
index 0000000..cc498bd
--- /dev/null
+++ b/atom_pubsub/COPYING
@@ -0,0 +1,342 @@
+As a special exception, the authors give permission to link this program
+with the OpenSSL library and distribute the resulting binary.
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/atom_pubsub/README.txt b/atom_pubsub/README.txt
new file mode 100644
index 0000000..38dc16f
--- /dev/null
+++ b/atom_pubsub/README.txt
@@ -0,0 +1,105 @@
+
+
+		***************
+		  PLEASE NOTE
+		***************
+
+	This module does NOT work
+	with ejabberd 13 or newer.
+
+		***************
+
+
+	atom_pubsub - the Atom PubSub tunnel
+
+	Author: Eric Cestari <eric@ohmforce.com> http://www.cestari.info/
+    Licensed under the same terms as ejabberd (GPL 2)
+	Requires: ejabberd 2.0.3 or newer.
+
+
+	DESCRIPTION
+	-----------
+
+The atom_pubsub module provides access to all PEP nodes via an AtomPub interface.
+Also gives access to tune, mood and geoloc nodes if they exist.
+
+urn:xmpp:microblog is not a XEP yet, but its latest incarnation can be found here :
+http://www.xmpp.org/extensions/inbox/microblogging.html
+
+AtomPub RFC : http://bitworking.org/projects/atom/rfc5023.html
+
+For more information refer to:
+http://www.cestari.info/2008/6/19/atom-pubsub-module-for-ejabberd
+http://www.cestari.info/2008/9/12/atom_pubsub-dead-long-live-atom_microblog
+
+
+	USAGE
+	-----
+
+URL for the service document for a given user@domain is :
+http://<server>:5280/pep/<domain>/<user>
+
+The atom_pubsub module provides access to all nodes below the /home/server/user tree.
+SVC document : http://<server>:5280/pubsub/<domain>/<user>
+
+
+# Configuring your PEP nodes 
+
+For your PEP nodes to be published, you need to activate persistence.
+Two ways:
+
+A) For existing nodes :
+<iq type='set'
+    id='config2'>
+  <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
+    <configure node='http://jabber.org/protocol/tune'> <!-- for user-tune -->
+      <x xmlns='jabber:x:data' type='submit'>
+        <field var='FORM_TYPE' type='hidden'>
+          <value>http://jabber.org/protocol/pubsub#node_config</value>
+        </field>
+        <field var='pubsub#persist_items'><value>1</value></field>
+        <field var='pubsub#max_items'><value>1</value></field>
+      </x>
+    </configure>
+  </pubsub>
+</iq>
+
+B) for new nodes :
+It's better to change src/mod_pubsub/node_pep.erl in the options() function:
+  {persist_items, true},
+  {max_items, 1},
+All future PEP nodes will be published with those parameters by default.
+
+
+# What you get
+
+Full caching support with Etag, Conditional Get, etc.
+
+Authentication is required for writing, updating via Atom. Use your full JID as username for authentication.
+
+It expects the payload to be an atom entry, but does not enforce it.
+
+However, it has to be well formed XML.
+
+
+# Can I have it with OpenFire and Epeios ?
+
+That's not possible. atom_microblog needs direct access to the pubsub structures.
+
+
+	WHAT'S NEXT?
+	------------
+
+* Better understanding of Atom entries. have better links, implement reply-to 
+
+* Adding a local friend collection for personal use
+
+* But that may be implemented in mod_pubsub/node_microblog.erl (it will arrive soon)
+
+
+	THANKS
+	------
+
+* johnny_ for testing and giving feedback.
+* badlop and C Romain from ProcessOne for feeding ejabberd with my patches, making this code work.
+
diff --git a/atom_pubsub/atom_pubsub.spec.broken b/atom_pubsub/atom_pubsub.spec.broken
new file mode 100644
index 0000000..ba81992
--- /dev/null
+++ b/atom_pubsub/atom_pubsub.spec.broken
@@ -0,0 +1,5 @@
+author: "Eric Cestari <eric at ohmforce.com>"
+category: "pubsub"
+summary: "Provides access to all PEP nodes via an AtomPub interface"
+home: "https://github.com/processone/ejabberd-contrib/tree/master/atom_pubsub"
+url: "git@github.com:processone/ejabberd-contrib.git"
diff --git a/atom_pubsub/conf/atom_pubsub.yml b/atom_pubsub/conf/atom_pubsub.yml
new file mode 100644
index 0000000..a2879eb
--- /dev/null
+++ b/atom_pubsub/conf/atom_pubsub.yml
@@ -0,0 +1,7 @@
+listen:
+  -
+    port: 8080
+    module: ejabberd_http
+    request_handlers:
+      "pep": atom_microblog
+      "pubsub": atom_pubsub
diff --git a/atom_pubsub/src/atom_microblog.erl b/atom_pubsub/src/atom_microblog.erl
new file mode 100644
index 0000000..7819c26
--- /dev/null
+++ b/atom_pubsub/src/atom_microblog.erl
@@ -0,0 +1,368 @@
+%%
+% This module enables access to the PEP node "urn:xmpp:microblog" via 
+% an Atompub compliant interface.
+% 
+% 
+-module(atom_microblog).
+-author('eric@ohmforce.com').
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("pubsub.hrl").
+-include("ejabberd_http.hrl").
+-include("logger.hrl").
+-export([process/2]).
+ 
+process([Domain,User|_]=LocalPath,  #request{auth = Auth} = Request)->
+	case get_auth(Auth) of
+	%%make sure user belongs to pubsub domain
+	{User, Domain} ->
+	    out(Request, Request#request.method, LocalPath,User);
+	_ ->
+	    out(Request, Request#request.method, LocalPath,undefined)
+    end;
+	
+
+process(_LocalPath, _Request)->
+	 error(404).
+
+get_host([Domain,User|_Rest])-> {User, Domain, []}.
+
+get_collection([_Domain,_User, Node|_R])->
+	case lists:member(Node, ["mood", "geoloc", "tune"]) of 
+		true -> "http://jabber.org/protocol/"++Node;
+		false -> Node
+	end;
+get_collection(_)->error.
+
+get_member([_Domain,_User, _Node, Member]=Uri)->
+	[get_host(Uri), get_collection(Uri), Member].
+	
+base_uri(#request{host=Host, port=Port}, Domain, User)->
+	"http://"++Host++":"++i2l(Port)++"/pep/"++Domain++"/"++ User.
+
+collection_uri(R, Domain, User, Node)->
+	 Clean=lists:last(string:tokens(Node, "/")),
+	 base_uri(R, Domain, User)++"/"++Clean.
+
+entry_uri(R, Domain, User, Node, Id)->
+	collection_uri(R, Domain, User, Node)++"/"++Id.
+
+generate_etag(#pubsub_item{modification={_JID, {_, D2, D3}}})->integer_to_list(D3+D2).
+
+out(_Args, 'POST', [_,_, _], undefined) ->error(401);
+out(_Args, 'PUT', [_,_, _], undefined) ->error(401);	
+out(_Args, 'DELETE', [_,_, _], undefined) ->error(401);
+
+
+%% Service document
+out(Args, 'GET', [Domain, UserNode]=Uri, _User) ->
+	%%Collections = mnesia:dirty_match_object(#pubsub_node{nodeid={get_host(Uri), '_'},_ = '_'}),
+	case mod_pubsub:tree_action(get_host(Uri), get_nodes, [get_host(Uri)]) of
+		[] -> error(404);
+		Collections ->
+			?DEBUG("PEP nodes : ~p~n",[Collections]),
+			{200, [{"Content-Type", "application/atomsvc+xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>" 
+				++	xml:element_to_string(service(Args,Domain, UserNode, Collections))}
+	end;
+
+%% Collection
+
+out(Args, 'GET', [Domain, User, Node]=Uri, _User) -> 
+    case mod_pubsub:tree_action(get_host(Uri), get_node, [get_host(Uri),get_collection(Uri)]) of
+	{error, _} -> error(404);
+	_ ->
+		Items = lists:sort(fun(X,Y)->
+				{_,DateX} = X#pubsub_item.modification,
+				{_,DateY} = Y#pubsub_item.modification,
+				DateX > DateY
+			end, mod_pubsub:get_items(
+					get_host(Uri),
+					get_collection(Uri), "")),
+		case Items of
+			[] -> ?DEBUG("Items : ~p ~n", [collection(get_collection(Uri), 
+				collection_uri(Args,Domain,User,Node), calendar:now_to_universal_time(erlang:now()), User, "", [])]),
+				{200, [{"Content-Type", "application/atom+xml"}],
+					collection(get_collection(Uri), 
+						collection_uri(Args,Domain,User,Node), calendar:now_to_universal_time(erlang:now()), User, "", [])};
+			_ ->
+				#pubsub_item{modification = {_JID,LastDate}} = LastItem = hd(Items),
+				Etag =generate_etag(LastItem),
+				IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
+				if IfNoneMatch==Etag
+					-> 
+						success(304);
+					true ->
+						XMLEntries= [item_to_entry(Args,Node,Entry)||Entry <-  Items], 
+						{200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}], 
+						"<?xml version=\"1.0\" encoding=\"utf-8\"?>" 
+						++	xml:element_to_string(
+						collection(get_collection(Uri), collection_uri(Args,Domain,User,Node),
+							calendar:now_to_universal_time(LastDate), User, "", XMLEntries))}
+			end
+		end
+	end;
+%% Add new collection
+out(_Args, 'POST', [_Domain, _User], _User)-> error(403);
+
+out(Args, 'POST', [Domain,User, Node]=Uri, User) -> 
+	%%FIXME Slug
+	Slug = case lists:keysearch("Slug",3,Args#request.headers) of 
+		false -> uniqid(false) ; 
+		{value, {_,_,_,_,Value}} -> Value 
+	end,
+	Payload = xml_stream:parse_element(Args#request.data),
+	case mod_pubsub:publish_item(get_host(Uri),
+								 Domain,
+								 get_collection(Uri),
+								 jlib:make_jid(User,Domain, ""), 
+								 Slug, 
+								 [Payload]) of
+		{result, []} ->
+				?DEBUG("Publishing to ~p~n",[entry_uri(Args, Domain,User, Node,Slug)]),
+			{201, [{"location", entry_uri(Args, Domain,User,Node,Slug)}], Payload};
+		{error, Error} ->
+			error(400, Error)
+		end;
+out(_Args, 'POST', [_, _, _], _) ->
+	{status, 403};
+			
+%% Atom doc
+out(Args, 'GET', [_Domain,_U, Node, _Member]=URI, _User) -> 
+	Failure = fun(_Error)->error(404)end,
+	Success = fun(Item)->
+		Etag =generate_etag(Item),
+		IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
+		if IfNoneMatch==Etag
+			-> 
+				success(304);
+			true ->
+		{200, [{"Content-Type",  "application/atom+xml"},{"Etag", Etag}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>" 
+				++ xml:element_to_string(item_to_entry(Args, Node, Item))}
+		end
+	end,
+	get_item(URI, Failure, Success);
+		
+
+%% Update doc
+out(Args, 'PUT', [Domain,User, _Node, Member]=Uri, User) -> 
+	Payload = xml_stream:parse_element(Args#request.data),
+	Failure = fun(_Error)->error(404)end,
+	Success = fun(Item)->
+		Etag =generate_etag(Item),
+		IfMatch=proplists:get_value('If-Match', Args#request.headers),
+		if IfMatch==Etag
+			-> 
+			case mod_pubsub:publish_item(get_host(Uri),
+										 Domain,
+										 get_collection(Uri),
+										 jlib:make_jid(User,Domain, ""), 
+										 Member, 
+										 [Payload]) of
+				{result, _Result} -> 
+					{200, [{"Content-Type",  "application/atom+xml"}],""};
+				{error, {xmlelement, "error", [{"code",Code},_],_}} ->
+					error(Code);
+				{error, _Error} ->
+					error(500)
+				end;
+			true ->
+				error(412) %% ressource has been modified since last get for this client.
+		end
+	end,
+	get_item(Uri, Failure, Success);
+	
+%%
+out(_Args, 'PUT',_Url, _User) ->
+	error(401);
+
+out(_Args, 'DELETE', [Domain,User, _Node, _Member]=Uri, User) ->
+	case mod_pubsub:delete_item(get_host(Uri), 
+								get_collection(Uri),
+								jlib:make_jid(User,Domain, ""),
+								get_member(Uri)) of
+		{result, _Result} -> 
+			success(200);
+		{error, {xmlelement, "error", [{"code",Code},_],_}} ->
+			error(Code);
+		{error, _Code1} ->
+			error(500)
+		end;
+
+out(_Args, 'DELETE',_Url, _User) ->
+		error(401);	
+		
+out(_, _, _, _) ->
+	error(403).
+
+get_item(Uri, Failure, Success)->
+	case catch mod_pubsub:node_action(get_host(Uri), 
+									get_collection(Uri),
+									get_item, 
+									get_member(Uri)) of
+		{error, Reason} ->
+			Failure(Reason);
+		{result, Item} ->
+			Success(Item)
+		end.
+		
+
+	
+
+item_to_entry(Args,Node,#pubsub_item{itemid={Id,_}, payload=Entry}=Item)->
+	[R | _]=xml:remove_cdata(Entry),
+	item_to_entry(Args, Node, Id, R, Item).
+
+item_to_entry(Args,Node,  Id,{xmlelement, "entry", Attrs, SubEl}, 
+		#pubsub_item{modification={_, Secs}, itemid={Id, {{User, Domain, []},_}}}) ->	
+	Date = calendar:now_to_local_time(Secs),
+	SubEl2=[{xmlelement, "app:edited", [], [{xmlcdata, w3cdtf(Date)}]},
+			{xmlelement, "link",[{"rel", "edit"}, 
+			{"href", entry_uri(Args,Domain,User, Node, Id)}],[] }, 
+			{xmlelement, "id", [],[{xmlcdata, Id}]}
+			| SubEl],
+	{xmlelement, "entry", [{"xmlns:app","http://www.w3.org/2007/app"}|Attrs], SubEl2};
+	
+%% Don't do anything except adding xmlns
+item_to_entry(_Args,Node,  _Id, {xmlelement, Name, Attrs, Subels}=Element, _Item)->
+	case proplists:is_defined("xmlns",Attrs) of
+		true -> Element;
+		false -> {xmlelement, Name, [{"xmlns", Node}|Attrs], Subels}
+	end.
+	
+	
+
+collection(Title, Link, Updated, Author, _Id, Entries)->
+	{xmlelement, "feed", [{"xmlns", "http://www.w3.org/2005/Atom"}, 
+						 {"xmlns:app", "http://www.w3.org/2007/app"}], [
+		{xmlelement, "title", [],[{xmlcdata, Title}]},
+		{xmlelement, "updated", [],[{xmlcdata, w3cdtf(Updated)}]},
+		{xmlelement, "link", [{"href", Link}], []},
+		{xmlelement, "author", [], [
+			{xmlelement, "name", [], [{xmlcdata,Author}]}
+		]},
+		{xmlelement, "title", [],[{xmlcdata, Title}]} | 
+		Entries
+	]}.
+
+service(Args, Domain, User, Collections)->
+	{xmlelement, "service", [{"xmlns", "http://www.w3.org/2007/app"},
+							{"xmlns:atom", "http://www.w3.org/2005/Atom"},
+							{"xmlns:app", "http://www.w3.org/2007/app"}],[
+		{xmlelement, "workspace", [],[
+			{xmlelement, "atom:title", [],[{xmlcdata,"Feed for "++User++"@"++Domain}]} | 
+			lists:map(fun(#pubsub_node{nodeid={_Server, Id}})->
+				{xmlelement, "collection", [{"href", collection_uri(Args,Domain,User, Id)}], [
+					{xmlelement, "atom:title", [], [{xmlcdata, Id}]}
+				]}
+				end, Collections)
+		]}
+	]}.
+
+%%% lifted from ejabberd_web_admin
+get_auth(Auth) ->
+    case Auth of
+        {SJID, P} ->
+            case jlib:string_to_jid(SJID) of
+                error ->
+                    unauthorized;
+                #jid{user = U, server = S} ->
+                    case ejabberd_auth:check_password(U, S, P) of
+                        true ->
+                            {U, S};
+                        false ->
+                            unauthorized
+                    end
+            end;
+         _ ->
+            unauthorized
+    end.
+
+
+
+%% simple output functions
+error(404)->
+	{404, [], "Not Found"};
+error(403)->
+	{403, [], "Forbidden"};
+error(500)->
+	{500, [], "Internal server error"};
+error(401)->
+	{401, [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],"Unauthorized"};
+error(Code)->
+	{Code, [], ""}.
+success(200)->
+	{200, [], ""};
+success(Code)->
+	{Code, [], ""}.
+error(Code, Error) when is_list(Error) -> {Code, [], Error};
+error(Code, {xmlelement, "error",_,_}=Error) -> {Code, [], xml:element_to_string(Error)};
+error(Code, _Error) -> {Code, [], "Bad request"}.
+
+	
+% Code below is taken (with some modifications) from the yaws webserver, which
+% is distributed under the folowing license:
+%
+% This software (the yaws webserver) is free software.
+% Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org>
+% Any use or misuse of the source code is hereby freely allowed.
+%
+% 1. Redistributions of source code must retain the above copyright
+%    notice as well as this list of conditions.
+%
+% 2. Redistributions in binary form must reproduce the above copyright
+%    notice as well as this list of conditions.
+%%% Create W3CDTF (http://www.w3.org/TR/NOTE-datetime) formatted date
+%%% w3cdtf(GregSecs) -> "YYYY-MM-DDThh:mm:ssTZD"
+%%%
+uniqid(false)->
+	{T1, T2, T3} = now(),
+    lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]));
+uniqid(Slug) ->
+	Slut = string:to_lower(Slug),
+	S = string:substr(Slut, 1, 9),
+    {_T1, T2, T3} = now(),
+    lists:flatten(io_lib:fwrite("~s-~.16B~.16B", [S, T2, T3])).
+
+w3cdtf(Date) -> %1   Date = calendar:gregorian_seconds_to_datetime(GregSecs),
+    {{Y, Mo, D},{H, Mi, S}} = Date,
+    [UDate|_] = calendar:local_time_to_universal_time_dst(Date),
+    {DiffD,{DiffH,DiffMi,_}}=calendar:time_difference(UDate,Date),
+    w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi). 
+
+%%%  w3cdtf's helper function
+w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi) when DiffH < 12,  DiffH /= 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":"  ++ add_zero(DiffMi);
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12,  DiffD == 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":"  ++
+        add_zero(DiffMi);
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12,  DiffD /= 0, DiffMi /= 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "-" ++ add_zero(23-DiffH) ++
+        ":" ++ add_zero(60-DiffMi);
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12,  DiffD /= 0, DiffMi == 0 ->
+   i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "-" ++ add_zero(24-DiffH) ++
+        ":" ++ add_zero(DiffMi); 
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi) when DiffH == 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "Z".
+
+add_zero(I) when is_integer(I) -> add_zero(i2l(I));
+add_zero([A])               -> [$0,A];
+add_zero(L) when is_list(L)    -> L. 
+
+i2l(I) when is_integer(I) -> integer_to_list(I);
+i2l(L) when is_list(L)    -> L.
+
+
diff --git a/atom_pubsub/src/atom_pubsub.erl b/atom_pubsub/src/atom_pubsub.erl
new file mode 100644
index 0000000..d518931
--- /dev/null
+++ b/atom_pubsub/src/atom_pubsub.erl
@@ -0,0 +1,375 @@
+%%
+% This module enables access to the PEP node "urn:xmpp:microblog" via 
+% an Atompub compliant interface.
+% 
+% 
+-module(atom_pubsub).
+-author('eric@ohmforce.com').
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("pubsub.hrl").
+-include("ejabberd_http.hrl").
+-include("logger.hrl").
+-export([process/2]).
+ 
+process([Domain,User|_]=LocalPath,  #request{auth = Auth} = Request)->
+	case get_auth(Auth) of
+	%%make sure user belongs to pubsub domain
+	{User, Domain} ->
+	    out(Request, Request#request.method, LocalPath,User);
+	_ ->
+	    out(Request, Request#request.method, LocalPath,undefined)
+    end;
+	
+
+process(_LocalPath, _Request)->
+	 error(404).
+
+get_host([Domain,_User|_Rest])-> "pubsub."++Domain.
+get_root([Domain,User|_Rest]) -> ["home", Domain, User].
+get_collection([Domain,User, Node|_Rest])->["home", Domain, User, Node].
+get_item_name([_Domain,_User, _Node, Member]) -> Member.
+collection_uri(R, Domain, User, Node) ->
+ case Node of 
+	["home", Domain, User|_Rest]->
+	 base_uri(R, Domain, User)++"/"++lists:last(Node);
+	_ -> base_uri(R, Domain, User)++"/"++Node
+ end.
+entry_uri(R, Domain, User, Node, Id)->
+	collection_uri(R, Domain, User, Node)++"/"++Id.
+
+
+
+get_member([_Domain,_User, _Node, Member]=Uri)->
+	[get_host(Uri), get_collection(Uri), Member].
+	
+base_uri(#request{host=Host, port=Port}, Domain, User)->
+	"http://"++Host++":"++i2l(Port)++"/pubsub/"++Domain++"/"++ User.
+
+
+
+
+generate_etag(#pubsub_item{modification={_JID, {_, D2, D3}}})->integer_to_list(D3+D2).
+
+out(_Args, 'POST', [_,_, _], undefined) ->error(401);
+out(_Args, 'PUT', [_,_, _], undefined) ->error(401);	
+out(_Args, 'DELETE', [_,_, _], undefined) ->error(401);
+
+
+%% Service document
+out(Args, 'GET', [Domain, UserNode]=Uri, _User) ->
+	%%Collections = mnesia:dirty_match_object(#pubsub_node{nodeid={get_host(Uri), '_'},_ = '_'}),
+	case mod_pubsub:tree_action(get_host(Uri), get_subnodes, [get_host(Uri),get_root(Uri), "" ]) of
+		[] -> error(404);
+		Collections ->
+			{200, [{"Content-Type", "application/atomsvc+xml"}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>" 
+				++	xml:element_to_string(service(Args,Domain, UserNode, Collections))}
+	end;
+
+%% Collection
+
+out(Args, 'GET', [Domain, User, Node]=Uri, _User) -> 
+    case mod_pubsub:tree_action(get_host(Uri), get_node, [get_host(Uri),get_collection(Uri)]) of
+	{error, _} -> error(404);
+	_ ->
+		Items = lists:sort(fun(X,Y)->
+				{_,DateX} = X#pubsub_item.modification,
+				{_,DateY} = Y#pubsub_item.modification,
+				DateX > DateY
+			end, mod_pubsub:get_items(
+					get_host(Uri),
+					get_collection(Uri), "")),
+		case Items of
+			[] -> ?DEBUG("Items : ~p ~n", [collection(get_collection(Uri), 
+				collection_uri(Args,Domain,User,Node), calendar:now_to_universal_time(erlang:now()), User, "", [])]),
+				{200, [{"Content-Type", "application/atom+xml"}],
+					collection(get_collection(Uri), 
+						collection_uri(Args,Domain,User,Node), calendar:now_to_universal_time(erlang:now()), User, "", [])};
+			_ ->
+				#pubsub_item{modification = {_JID,LastDate}} = LastItem = hd(Items),
+				Etag =generate_etag(LastItem),
+				IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
+				if IfNoneMatch==Etag
+					-> 
+						success(304);
+					true ->
+						XMLEntries= [item_to_entry(Args,Node,Entry)||Entry <-  Items], 
+						{200, [{"Content-Type", "application/atom+xml"},{"Etag", Etag}], 
+						"<?xml version=\"1.0\" encoding=\"utf-8\"?>" 
+						++	xml:element_to_string(
+						collection(get_collection(Uri), collection_uri(Args,Domain,User,Node),
+							calendar:now_to_universal_time(LastDate), User, "", XMLEntries))}
+			end
+		end
+	end;
+
+%% Add new collection
+out(_Args, 'POST', [_Domain, _User], _User)-> error(403);
+
+out(Args, 'POST', [Domain,User, Node]=Uri, User) -> 
+	%%FIXME Slug
+	Slug = case lists:keysearch("Slug",3,Args#request.headers) of 
+		false -> uniqid(false) ; 
+		{value, {_,_,_,_,Value}} -> Value 
+	end,
+	Payload = xml_stream:parse_element(Args#request.data),
+	[FilteredPayload]=xml:remove_cdata([Payload]),
+
+	%FilteredPayload2 = case xml:get_subtag(FilteredPayload, "app:edited") ->
+	%	{xmlelement, Name, Attrs, [{cdata, }]}
+	case mod_pubsub:publish_item(get_host(Uri),
+								 Domain,
+								 get_collection(Uri),
+								 jlib:make_jid(User,Domain, ""), 
+								 Slug, 
+								 [FilteredPayload]) of
+		{result, []} ->
+				?DEBUG("Publishing to ~p~n",[entry_uri(Args, Domain,User, Node,Slug)]),
+			{201, [{"location", entry_uri(Args, Domain,User,Node,Slug)}], Payload};
+		{error, Error} ->
+			error(400, Error)
+		end;
+out(_Args, 'POST', [_, _, _], _) ->
+	{status, 403};
+			
+%% Atom doc
+out(Args, 'GET', [_Domain,_U, Node, _Member]=URI, _User) -> 
+	Failure = fun(_Error)->error(404)end,
+	Success = fun(Item)->
+		Etag =generate_etag(Item),
+		IfNoneMatch=proplists:get_value('If-None-Match', Args#request.headers),
+		if IfNoneMatch==Etag
+			-> 
+				success(304);
+			true ->
+		{200, [{"Content-Type",  "application/atom+xml"},{"Etag", Etag}], "<?xml version=\"1.0\" encoding=\"utf-8\"?>" 
+				++ xml:element_to_string(item_to_entry(Args, Node, Item))}
+		end
+	end,
+	get_item(URI, Failure, Success);
+		
+
+%% Update doc
+out(Args, 'PUT', [Domain,User, _Node, Member]=Uri, User) -> 
+	Payload = xml_stream:parse_element(Args#request.data),
+	Failure = fun(_Error)->error(404)end,
+	Success = fun(Item)->
+		Etag =generate_etag(Item),
+		IfMatch=proplists:get_value('If-Match', Args#request.headers),
+		if IfMatch==Etag
+			-> 
+			case mod_pubsub:publish_item(get_host(Uri),
+										 Domain,
+										 get_collection(Uri),
+										 jlib:make_jid(User,Domain, ""), 
+										 Member, 
+										 [Payload]) of
+				{result, _Result} -> 
+					{200, [{"Content-Type",  "application/atom+xml"}],""};
+				{error, {xmlelement, "error", [{"code","404"},_],_}} ->
+					error(404);
+				{error, _Error} ->
+					error(500)
+				end;
+			true ->
+				error(412) %% ressource has been modified since last get for this client.
+		end
+	end,
+	get_item(Uri, Failure, Success);
+	
+%%
+out(_Args, 'PUT',_Url, _User) ->
+	error(401);
+
+out(_Args, 'DELETE', [Domain,User, _Node, _Member]=Uri, User) ->
+	case mod_pubsub:delete_item(get_host(Uri), 
+								get_collection(Uri),
+								jlib:make_jid(User,Domain, ""),
+								get_item_name(Uri)) of
+		{result, _Result} -> 
+			success(200);
+		{error, {xmlelement, "error", [{"code","404"},_],_}} ->
+			error(404);
+		{error, _Code1} ->
+			error(500)
+		end;
+
+out(_Args, 'DELETE',_Url, _User) ->
+		error(401);	
+		
+out(_, _, _, _) ->
+	error(403).
+
+get_item(Uri, Failure, Success)->
+	case catch mod_pubsub:node_action(get_host(Uri), 
+									get_collection(Uri),
+									get_item, 
+									get_member(Uri)) of
+		{error, Reason} ->
+			Failure(Reason);
+		{result, Item} ->
+			Success(Item)
+		end.
+		
+
+	
+
+item_to_entry(Args,Node,#pubsub_item{itemid={Id,_}, payload=Entry}=Item)->
+	[R]=xml:remove_cdata(Entry),
+	item_to_entry(Args, Node, Id, R, Item).
+
+item_to_entry(Args,Node,  Id,{xmlelement, "entry", Attrs, SubEl}, 
+		#pubsub_item{modification={JID, Secs} }) ->	
+	Date = calendar:now_to_local_time(Secs),
+	{User, Domain, _}=jlib:jid_tolower(JID),
+	SubEl2=[{xmlelement, "app:edited", [], [{xmlcdata, w3cdtf(Date)}]},
+			{xmlelement, "link",[{"rel", "edit"}, 
+			{"href", entry_uri(Args,Domain,User, Node, Id)}],[] }, 
+			{xmlelement, "id", [],[{xmlcdata, Id}]}
+			| SubEl],
+	{xmlelement, "entry", [{"xmlns:app","http://www.w3.org/2007/app"}|Attrs], SubEl2};
+   
+% Don't do anything except adding xmlns
+item_to_entry(_Args,Node,  _Id, {xmlelement, Name, Attrs, Subels}=Element, _Item)->
+	case proplists:is_defined("xmlns",Attrs) of
+		true -> Element;
+		false -> {xmlelement, Name, [{"xmlns", Node}|Attrs], Subels}
+	end.
+	
+	
+
+collection(Title, Link, Updated, Author, _Id, Entries)->
+	{xmlelement, "feed", [{"xmlns", "http://www.w3.org/2005/Atom"}, 
+						 {"xmlns:app", "http://www.w3.org/2007/app"}], [
+		{xmlelement, "title", [],[{xmlcdata, Title}]},
+		{xmlelement, "updated", [],[{xmlcdata, w3cdtf(Updated)}]},
+		{xmlelement, "link", [{"href", Link}], []},
+		{xmlelement, "author", [], [
+			{xmlelement, "name", [], [{xmlcdata,Author}]}
+		]},
+		{xmlelement, "title", [],[{xmlcdata, Title}]} | 
+		Entries
+	]}.
+
+service(Args, Domain, User, Collections)->
+	{xmlelement, "service", [{"xmlns", "http://www.w3.org/2007/app"},
+							{"xmlns:atom", "http://www.w3.org/2005/Atom"},
+							{"xmlns:app", "http://www.w3.org/2007/app"}],[
+		{xmlelement, "workspace", [],[
+			{xmlelement, "atom:title", [],[{xmlcdata,"Feed for "++User++"@"++Domain}]} | 
+			lists:map(fun(#pubsub_node{nodeid={_Server, Id}, type=_Type})->
+				{xmlelement, "collection", [{"href", collection_uri(Args,Domain,User, Id)}], [
+					{xmlelement, "atom:title", [], [{xmlcdata, lists:last(Id)}]}
+				]}
+				end, Collections)
+		]}
+	]}.
+
+%%% lifted from ejabberd_web_admin
+get_auth(Auth) ->
+    case Auth of
+        {SJID, P} ->
+            case jlib:string_to_jid(SJID) of
+                error ->
+                    unauthorized;
+                #jid{user = U, server = S} ->
+                    case ejabberd_auth:check_password(U, S, P) of
+                        true ->
+                            {U, S};
+                        false ->
+                            unauthorized
+                    end
+            end;
+         _ ->
+            unauthorized
+    end.
+
+
+
+%% simple output functions
+error(404)->
+	{404, [], "Not Found"};
+error(403)->
+	{403, [], "Forbidden"};
+error(500)->
+	{500, [], "Internal server error"};
+error(401)->
+	{401, [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],"Unauthorized"};
+error(Code)->
+	{Code, [], ""}.
+success(200)->
+	{200, [], ""};
+success(Code)->
+	{Code, [], ""}.
+error(Code, Error) when is_list(Error) -> {Code, [], Error};
+error(Code, {xmlelement, "error",_,_}=Error) -> {Code, [], xml:element_to_string(Error)};
+error(Code, _Error) -> {Code, [], "Bad request"}.
+
+	
+% Code below is taken (with some modifications) from the yaws webserver, which
+% is distributed under the folowing license:
+%
+% This software (the yaws webserver) is free software.
+% Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org>
+% Any use or misuse of the source code is hereby freely allowed.
+%
+% 1. Redistributions of source code must retain the above copyright
+%    notice as well as this list of conditions.
+%
+% 2. Redistributions in binary form must reproduce the above copyright
+%    notice as well as this list of conditions.
+%%% Create W3CDTF (http://www.w3.org/TR/NOTE-datetime) formatted date
+%%% w3cdtf(GregSecs) -> "YYYY-MM-DDThh:mm:ssTZD"
+%%%
+uniqid(false)->
+	{T1, T2, T3} = now(),
+    lists:flatten(io_lib:fwrite("~.16B~.16B~.16B", [T1, T2, T3]));
+uniqid(Slug) ->
+	Slut = string:to_lower(Slug),
+	S = string:substr(Slut, 1, 9),
+    {_T1, T2, T3} = now(),
+    lists:flatten(io_lib:fwrite("~s-~.16B~.16B", [S, T2, T3])).
+
+w3cdtf(Date) -> %1   Date = calendar:gregorian_seconds_to_datetime(GregSecs),
+    {{Y, Mo, D},{H, Mi, S}} = Date,
+    [UDate|_] = calendar:local_time_to_universal_time_dst(Date),
+    {DiffD,{DiffH,DiffMi,_}}=calendar:time_difference(UDate,Date),
+    w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi). 
+
+%%%  w3cdtf's helper function
+w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, DiffMi) when DiffH < 12,  DiffH /= 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":"  ++ add_zero(DiffMi);
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12,  DiffD == 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "+" ++ add_zero(DiffH) ++ ":"  ++
+        add_zero(DiffMi);
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12,  DiffD /= 0, DiffMi /= 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "-" ++ add_zero(23-DiffH) ++
+        ":" ++ add_zero(60-DiffMi);
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, DiffD, DiffH, DiffMi) when DiffH > 12,  DiffD /= 0, DiffMi == 0 ->
+   i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "-" ++ add_zero(24-DiffH) ++
+        ":" ++ add_zero(DiffMi); 
+
+w3cdtf_diff(Y, Mo, D, H, Mi, S, _DiffD, DiffH, _DiffMi) when DiffH == 0 ->
+    i2l(Y) ++ "-" ++ add_zero(Mo) ++ "-" ++ add_zero(D) ++ "T" ++
+        add_zero(H) ++ ":" ++ add_zero(Mi) ++ ":"  ++
+        add_zero(S) ++ "Z".
+
+add_zero(I) when is_integer(I) -> add_zero(i2l(I));
+add_zero([A])               -> [$0,A];
+add_zero(L) when is_list(L)    -> L. 
+
+i2l(I) when is_integer(I) -> integer_to_list(I);
+i2l(L) when is_list(L)    -> L.
+
+
diff --git a/atom_pubsub/src/mod_couch.erl b/atom_pubsub/src/mod_couch.erl
new file mode 100644
index 0000000..d909bd1
--- /dev/null
+++ b/atom_pubsub/src/mod_couch.erl
@@ -0,0 +1,24 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_couch.erl
+%%% Author  : Eric Cestari
+%%% Purpose : Configures and starts ecouch client
+%%% Created : 
+%%% Id      : $Id$
+%%%----------------------------------------------------------------------
+
+-module(mod_couch).
+-author('eric@ohmforce.com').
+-vsn('0.2.0').
+
+-behaviour(gen_mod).
+
+-export([start/2, stop/1]).
+
+start(Host, Opts) ->
+	Server = gen_mod:get_opt(server, Opts, {"127.0.0.1", "5984"}),
+	inets:start(),
+	application:set_env(ecouch, Server, {}),
+	application:start(ecouch).
+
+stop(_Host) ->
+	application:stop(ecouch).
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index 76570f6..eb4b892 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ejabberd-contrib (0.2023.01.25+git20230125.1.0db4985-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 08 Feb 2023 22:13:42 -0000
+
 ejabberd-contrib (0.2023.01.25~dfsg0-1) unstable; urgency=medium
 
   * New upstream version 0.2023.01.25~dfsg0
diff --git a/debian/patches/src.includes.patch b/debian/patches/src.includes.patch
index cb76d40..0e2ef62 100644
--- a/debian/patches/src.includes.patch
+++ b/debian/patches/src.includes.patch
@@ -31,10 +31,10 @@ Author: Philipp Huebner <debalance@debian.org>
  mod_webpresence/src/mod_webpresence.erl           | 2 +-
  25 files changed, 29 insertions(+), 29 deletions(-)
 
-Index: ejabberd-contrib/mod_cron/src/mod_cron.erl
+Index: ejabberd-contrib.git/mod_cron/src/mod_cron.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_cron/src/mod_cron.erl
-+++ ejabberd-contrib/mod_cron/src/mod_cron.erl
+--- ejabberd-contrib.git.orig/mod_cron/src/mod_cron.erl
++++ ejabberd-contrib.git/mod_cron/src/mod_cron.erl
 @@ -23,7 +23,7 @@
  -include("ejabberd_web_admin.hrl").
  -include("logger.hrl").
@@ -44,10 +44,10 @@ Index: ejabberd-contrib/mod_cron/src/mod_cron.erl
  
  -record(task, {taskid, timerref, host, task}).
  
-Index: ejabberd-contrib/mod_default_contacts/src/mod_default_contacts.erl
+Index: ejabberd-contrib.git/mod_default_contacts/src/mod_default_contacts.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_default_contacts/src/mod_default_contacts.erl
-+++ ejabberd-contrib/mod_default_contacts/src/mod_default_contacts.erl
+--- ejabberd-contrib.git.orig/mod_default_contacts/src/mod_default_contacts.erl
++++ ejabberd-contrib.git/mod_default_contacts/src/mod_default_contacts.erl
 @@ -35,7 +35,7 @@
  -export([register_user/2]).
  
@@ -57,10 +57,10 @@ Index: ejabberd-contrib/mod_default_contacts/src/mod_default_contacts.erl
  
  %%--------------------------------------------------------------------
  %% gen_mod callbacks.
-Index: ejabberd-contrib/mod_default_rooms/src/mod_default_rooms.erl
+Index: ejabberd-contrib.git/mod_default_rooms/src/mod_default_rooms.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_default_rooms/src/mod_default_rooms.erl
-+++ ejabberd-contrib/mod_default_rooms/src/mod_default_rooms.erl
+--- ejabberd-contrib.git.orig/mod_default_rooms/src/mod_default_rooms.erl
++++ ejabberd-contrib.git/mod_default_rooms/src/mod_default_rooms.erl
 @@ -35,7 +35,7 @@
  -export([register_user/2]).
  
@@ -70,10 +70,10 @@ Index: ejabberd-contrib/mod_default_rooms/src/mod_default_rooms.erl
  
  %%--------------------------------------------------------------------
  %% gen_mod callbacks.
-Index: ejabberd-contrib/mod_deny_omemo/src/mod_deny_omemo.erl
+Index: ejabberd-contrib.git/mod_deny_omemo/src/mod_deny_omemo.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_deny_omemo/src/mod_deny_omemo.erl
-+++ ejabberd-contrib/mod_deny_omemo/src/mod_deny_omemo.erl
+--- ejabberd-contrib.git.orig/mod_deny_omemo/src/mod_deny_omemo.erl
++++ ejabberd-contrib.git/mod_deny_omemo/src/mod_deny_omemo.erl
 @@ -35,7 +35,7 @@
  -export([user_receive_packet/1, user_send_packet/1]).
  
@@ -83,10 +83,10 @@ Index: ejabberd-contrib/mod_deny_omemo/src/mod_deny_omemo.erl
  
  -define(NS_AXOLOTL, "eu.siacs.conversations.axolotl").
  -define(DEVICELIST_NODE, ?NS_AXOLOTL ".devicelist").
-Index: ejabberd-contrib/mod_filter/src/mod_filter.erl
+Index: ejabberd-contrib.git/mod_filter/src/mod_filter.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_filter/src/mod_filter.erl
-+++ ejabberd-contrib/mod_filter/src/mod_filter.erl
+--- ejabberd-contrib.git.orig/mod_filter/src/mod_filter.erl
++++ ejabberd-contrib.git/mod_filter/src/mod_filter.erl
 @@ -13,7 +13,7 @@
  -export([start/2, stop/1, depends/2, mod_options/1, filter_packet/1, mod_doc/0]).
  
@@ -96,10 +96,10 @@ Index: ejabberd-contrib/mod_filter/src/mod_filter.erl
  
  -dialyzer({no_match, [check_stanza_type/2, check_access/1]}).
  
-Index: ejabberd-contrib/mod_grafite/src/mod_grafite.erl
+Index: ejabberd-contrib.git/mod_grafite/src/mod_grafite.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_grafite/src/mod_grafite.erl
-+++ ejabberd-contrib/mod_grafite/src/mod_grafite.erl
+--- ejabberd-contrib.git.orig/mod_grafite/src/mod_grafite.erl
++++ ejabberd-contrib.git/mod_grafite/src/mod_grafite.erl
 @@ -13,7 +13,7 @@
  -behaviour(gen_mod).
  
@@ -109,10 +109,10 @@ Index: ejabberd-contrib/mod_grafite/src/mod_grafite.erl
  
  -define(HOOKS, [offline_message_hook,
                  sm_register_connection_hook, sm_remove_connection_hook,
-Index: ejabberd-contrib/mod_irc/src/mod_irc.erl
+Index: ejabberd-contrib.git/mod_irc/src/mod_irc.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_irc/src/mod_irc.erl
-+++ ejabberd-contrib/mod_irc/src/mod_irc.erl
+--- ejabberd-contrib.git.orig/mod_irc/src/mod_irc.erl
++++ ejabberd-contrib.git/mod_irc/src/mod_irc.erl
 @@ -42,8 +42,8 @@
  	 mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]).
  
@@ -124,10 +124,10 @@ Index: ejabberd-contrib/mod_irc/src/mod_irc.erl
  -include("translate.hrl").
  
  -define(DEFAULT_IRC_PORT, 6667).
-Index: ejabberd-contrib/mod_irc/src/mod_irc_connection.erl
+Index: ejabberd-contrib.git/mod_irc/src/mod_irc_connection.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_irc/src/mod_irc_connection.erl
-+++ ejabberd-contrib/mod_irc/src/mod_irc_connection.erl
+--- ejabberd-contrib.git.orig/mod_irc/src/mod_irc_connection.erl
++++ ejabberd-contrib.git/mod_irc/src/mod_irc_connection.erl
 @@ -40,7 +40,7 @@
  	 code_change/4]).
  
@@ -137,10 +137,10 @@ Index: ejabberd-contrib/mod_irc/src/mod_irc_connection.erl
  
  -define(SETS, gb_sets).
  
-Index: ejabberd-contrib/mod_irc/src/mod_irc_mnesia.erl
+Index: ejabberd-contrib.git/mod_irc/src/mod_irc_mnesia.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_irc/src/mod_irc_mnesia.erl
-+++ ejabberd-contrib/mod_irc/src/mod_irc_mnesia.erl
+--- ejabberd-contrib.git.orig/mod_irc/src/mod_irc_mnesia.erl
++++ ejabberd-contrib.git/mod_irc/src/mod_irc_mnesia.erl
 @@ -30,8 +30,8 @@
  -export([init/2, get_data/3, set_data/4, import/2]).
  -export([need_transform/1, transform/1]).
@@ -152,10 +152,10 @@ Index: ejabberd-contrib/mod_irc/src/mod_irc_mnesia.erl
  -include("logger.hrl").
  
  %%%===================================================================
-Index: ejabberd-contrib/mod_irc/src/mod_irc_riak.erl
+Index: ejabberd-contrib.git/mod_irc/src/mod_irc_riak.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_irc/src/mod_irc_riak.erl
-+++ ejabberd-contrib/mod_irc/src/mod_irc_riak.erl
+--- ejabberd-contrib.git.orig/mod_irc/src/mod_irc_riak.erl
++++ ejabberd-contrib.git/mod_irc/src/mod_irc_riak.erl
 @@ -29,8 +29,8 @@
  %% API
  -export([init/2, get_data/3, set_data/4, import/2]).
@@ -167,10 +167,10 @@ Index: ejabberd-contrib/mod_irc/src/mod_irc_riak.erl
  
  %%%===================================================================
  %%% API
-Index: ejabberd-contrib/mod_irc/src/mod_irc_sql.erl
+Index: ejabberd-contrib.git/mod_irc/src/mod_irc_sql.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_irc/src/mod_irc_sql.erl
-+++ ejabberd-contrib/mod_irc/src/mod_irc_sql.erl
+--- ejabberd-contrib.git.orig/mod_irc/src/mod_irc_sql.erl
++++ ejabberd-contrib.git/mod_irc/src/mod_irc_sql.erl
 @@ -31,8 +31,8 @@
  %% API
  -export([init/2, get_data/3, set_data/4, import/1, import/2, export/1]).
@@ -182,10 +182,10 @@ Index: ejabberd-contrib/mod_irc/src/mod_irc_sql.erl
  -include("ejabberd_sql_pt.hrl").
  
  %%%===================================================================
-Index: ejabberd-contrib/mod_isolation/src/mod_isolation.erl
+Index: ejabberd-contrib.git/mod_isolation/src/mod_isolation.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_isolation/src/mod_isolation.erl
-+++ ejabberd-contrib/mod_isolation/src/mod_isolation.erl
+--- ejabberd-contrib.git.orig/mod_isolation/src/mod_isolation.erl
++++ ejabberd-contrib.git/mod_isolation/src/mod_isolation.erl
 @@ -24,7 +24,7 @@
  %% hooks
  -export([filter_packet/1]).
@@ -195,10 +195,10 @@ Index: ejabberd-contrib/mod_isolation/src/mod_isolation.erl
  
  %%%===================================================================
  %%% API
-Index: ejabberd-contrib/mod_log_chat/src/mod_log_chat.erl
+Index: ejabberd-contrib.git/mod_log_chat/src/mod_log_chat.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_log_chat/src/mod_log_chat.erl
-+++ ejabberd-contrib/mod_log_chat/src/mod_log_chat.erl
+--- ejabberd-contrib.git.orig/mod_log_chat/src/mod_log_chat.erl
++++ ejabberd-contrib.git/mod_log_chat/src/mod_log_chat.erl
 @@ -16,7 +16,7 @@
  	 log_packet_receive/1]).
  
@@ -208,10 +208,10 @@ Index: ejabberd-contrib/mod_log_chat/src/mod_log_chat.erl
  
  -define(PROCNAME, ?MODULE).
  
-Index: ejabberd-contrib/mod_logsession/src/mod_logsession.erl
+Index: ejabberd-contrib.git/mod_logsession/src/mod_logsession.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_logsession/src/mod_logsession.erl
-+++ ejabberd-contrib/mod_logsession/src/mod_logsession.erl
+--- ejabberd-contrib.git.orig/mod_logsession/src/mod_logsession.erl
++++ ejabberd-contrib.git/mod_logsession/src/mod_logsession.erl
 @@ -35,7 +35,7 @@
  	 failed_auth/3,
  	 forbidden/1]).
@@ -221,10 +221,10 @@ Index: ejabberd-contrib/mod_logsession/src/mod_logsession.erl
  -include("ejabberd_commands.hrl").
  
  -define(PROCNAME, ejabberd_logsession).
-Index: ejabberd-contrib/mod_logxml/src/mod_logxml.erl
+Index: ejabberd-contrib.git/mod_logxml/src/mod_logxml.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_logxml/src/mod_logxml.erl
-+++ ejabberd-contrib/mod_logxml/src/mod_logxml.erl
+--- ejabberd-contrib.git.orig/mod_logxml/src/mod_logxml.erl
++++ ejabberd-contrib.git/mod_logxml/src/mod_logxml.erl
 @@ -15,7 +15,7 @@
  	 send_packet/1, receive_packet/1,
  	 mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]).
@@ -234,10 +234,10 @@ Index: ejabberd-contrib/mod_logxml/src/mod_logxml.erl
  
  -define(PROCNAME, ejabberd_mod_logxml).
  
-Index: ejabberd-contrib/mod_message_log/src/mod_message_log.erl
+Index: ejabberd-contrib.git/mod_message_log/src/mod_message_log.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_message_log/src/mod_message_log.erl
-+++ ejabberd-contrib/mod_message_log/src/mod_message_log.erl
+--- ejabberd-contrib.git.orig/mod_message_log/src/mod_message_log.erl
++++ ejabberd-contrib.git/mod_message_log/src/mod_message_log.erl
 @@ -52,7 +52,7 @@
  	 log_packet_offline/1,
  	 reopen_log/0]).
@@ -247,10 +247,10 @@ Index: ejabberd-contrib/mod_message_log/src/mod_message_log.erl
  
  -define(FILE_MODES, [append, raw]).
  
-Index: ejabberd-contrib/mod_muc_log_http/src/mod_muc_log_http.erl
+Index: ejabberd-contrib.git/mod_muc_log_http/src/mod_muc_log_http.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_muc_log_http/src/mod_muc_log_http.erl
-+++ ejabberd-contrib/mod_muc_log_http/src/mod_muc_log_http.erl
+--- ejabberd-contrib.git.orig/mod_muc_log_http/src/mod_muc_log_http.erl
++++ ejabberd-contrib.git/mod_muc_log_http/src/mod_muc_log_http.erl
 @@ -14,7 +14,7 @@
  
  -export([process/2]).
@@ -260,10 +260,10 @@ Index: ejabberd-contrib/mod_muc_log_http/src/mod_muc_log_http.erl
  -include("ejabberd_http.hrl").
  -include("mod_muc_room.hrl").
  -include("logger.hrl").
-Index: ejabberd-contrib/mod_post_log/src/mod_post_log.erl
+Index: ejabberd-contrib.git/mod_post_log/src/mod_post_log.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_post_log/src/mod_post_log.erl
-+++ ejabberd-contrib/mod_post_log/src/mod_post_log.erl
+--- ejabberd-contrib.git.orig/mod_post_log/src/mod_post_log.erl
++++ ejabberd-contrib.git/mod_post_log/src/mod_post_log.erl
 @@ -22,7 +22,7 @@
  	 log_user_send/4,
           post_result/1]).
@@ -273,10 +273,10 @@ Index: ejabberd-contrib/mod_post_log/src/mod_post_log.erl
  
  start(Host, _Opts) ->
      ok = case inets:start() of
-Index: ejabberd-contrib/mod_pottymouth/src/mod_pottymouth.erl
+Index: ejabberd-contrib.git/mod_pottymouth/src/mod_pottymouth.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_pottymouth/src/mod_pottymouth.erl
-+++ ejabberd-contrib/mod_pottymouth/src/mod_pottymouth.erl
+--- ejabberd-contrib.git.orig/mod_pottymouth/src/mod_pottymouth.erl
++++ ejabberd-contrib.git/mod_pottymouth/src/mod_pottymouth.erl
 @@ -3,7 +3,7 @@
  -behaviour(gen_mod).
  
@@ -286,10 +286,10 @@ Index: ejabberd-contrib/mod_pottymouth/src/mod_pottymouth.erl
  
  -export([
    start/2,
-Index: ejabberd-contrib/mod_rest/src/mod_rest.erl
+Index: ejabberd-contrib.git/mod_rest/src/mod_rest.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_rest/src/mod_rest.erl
-+++ ejabberd-contrib/mod_rest/src/mod_rest.erl
+--- ejabberd-contrib.git.orig/mod_rest/src/mod_rest.erl
++++ ejabberd-contrib.git/mod_rest/src/mod_rest.erl
 @@ -37,7 +37,7 @@
  -include("logger.hrl").
  -include("ejabberd_http.hrl").
@@ -299,10 +299,10 @@ Index: ejabberd-contrib/mod_rest/src/mod_rest.erl
  
  start(_Host, _Opts) ->
      ?DEBUG("Starting: ~p ~p", [_Host, _Opts]),
-Index: ejabberd-contrib/mod_shcommands/src/mod_shcommands.erl
+Index: ejabberd-contrib.git/mod_shcommands/src/mod_shcommands.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_shcommands/src/mod_shcommands.erl
-+++ ejabberd-contrib/mod_shcommands/src/mod_shcommands.erl
+--- ejabberd-contrib.git.orig/mod_shcommands/src/mod_shcommands.erl
++++ ejabberd-contrib.git/mod_shcommands/src/mod_shcommands.erl
 @@ -15,7 +15,7 @@
  -export([execute_system/1, execute_erlang/1]).
  -export([web_menu_node/3, web_page_node/5]).
@@ -312,10 +312,10 @@ Index: ejabberd-contrib/mod_shcommands/src/mod_shcommands.erl
  -include("ejabberd_commands.hrl").
  -include("ejabberd_http.hrl").
  -include("ejabberd_web_admin.hrl").
-Index: ejabberd-contrib/mod_spam_filter/src/mod_spam_filter.erl
+Index: ejabberd-contrib.git/mod_spam_filter/src/mod_spam_filter.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_spam_filter/src/mod_spam_filter.erl
-+++ ejabberd-contrib/mod_spam_filter/src/mod_spam_filter.erl
+--- ejabberd-contrib.git.orig/mod_spam_filter/src/mod_spam_filter.erl
++++ ejabberd-contrib.git/mod_spam_filter/src/mod_spam_filter.erl
 @@ -58,7 +58,7 @@
  
  -include("ejabberd_commands.hrl").
@@ -325,10 +325,10 @@ Index: ejabberd-contrib/mod_spam_filter/src/mod_spam_filter.erl
  
  -define(COMMAND_TIMEOUT, timer:seconds(30)).
  
-Index: ejabberd-contrib/mod_statsdx/src/mod_stats2file.erl
+Index: ejabberd-contrib.git/mod_statsdx/src/mod_stats2file.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_statsdx/src/mod_stats2file.erl
-+++ ejabberd-contrib/mod_statsdx/src/mod_stats2file.erl
+--- ejabberd-contrib.git.orig/mod_statsdx/src/mod_stats2file.erl
++++ ejabberd-contrib.git/mod_statsdx/src/mod_stats2file.erl
 @@ -15,7 +15,7 @@
  -export([start/2, stop/1, depends/2, mod_opt_type/1, mod_options/1, mod_doc/0]).
  -export([loop/5]).
@@ -338,10 +338,10 @@ Index: ejabberd-contrib/mod_statsdx/src/mod_stats2file.erl
  -include("mod_roster.hrl").
  
  -define(PROCNAME, ejabberd_mod_stats2file).
-Index: ejabberd-contrib/mod_statsdx/src/mod_statsdx.erl
+Index: ejabberd-contrib.git/mod_statsdx/src/mod_statsdx.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_statsdx/src/mod_statsdx.erl
-+++ ejabberd-contrib/mod_statsdx/src/mod_statsdx.erl
+--- ejabberd-contrib.git.orig/mod_statsdx/src/mod_statsdx.erl
++++ ejabberd-contrib.git/mod_statsdx/src/mod_statsdx.erl
 @@ -33,7 +33,7 @@
  	 user_login/1, user_logout/2]).
  
@@ -351,10 +351,10 @@ Index: ejabberd-contrib/mod_statsdx/src/mod_statsdx.erl
  -include("logger.hrl").
  -include("mod_roster.hrl").
  -include("ejabberd_http.hrl").
-Index: ejabberd-contrib/mod_webpresence/src/mod_webpresence.erl
+Index: ejabberd-contrib.git/mod_webpresence/src/mod_webpresence.erl
 ===================================================================
---- ejabberd-contrib.orig/mod_webpresence/src/mod_webpresence.erl
-+++ ejabberd-contrib/mod_webpresence/src/mod_webpresence.erl
+--- ejabberd-contrib.git.orig/mod_webpresence/src/mod_webpresence.erl
++++ ejabberd-contrib.git/mod_webpresence/src/mod_webpresence.erl
 @@ -32,7 +32,7 @@
  %% API
  -export([start_link/0]).
diff --git a/extra/ejabberd_mod_mam.spec.broken b/extra/ejabberd_mod_mam.spec.broken
new file mode 100644
index 0000000..9d66ce2
--- /dev/null
+++ b/extra/ejabberd_mod_mam.spec.broken
@@ -0,0 +1,5 @@
+author: "Gregor Uhlenheuer <kongo2002 at gmail.com>"
+category: "archive"
+summary: "Message Archive Management (XEP-0313)"
+home: "https://github.com/kongo2002/ejabberd-mod-mam/tree/master/"
+url: "git@github.com:kongo2002/ejabberd-mod-mam.git"
diff --git a/extra/ejabberd_trace.spec.broken b/extra/ejabberd_trace.spec.broken
new file mode 100644
index 0000000..4a4c911
--- /dev/null
+++ b/extra/ejabberd_trace.spec.broken
@@ -0,0 +1,5 @@
+author: "Lavrin"
+category: "admin"
+summary: "Easy tracing of connections made to ejabberd"
+home: "https://github.com/lavrin/ejabberd-trace/tree/master/"
+url: "git@github.com:lavrin/ejabberd-trace.git"
diff --git a/extra/mod_http_offline.spec.broken b/extra/mod_http_offline.spec.broken
new file mode 100644
index 0000000..7204e78
--- /dev/null
+++ b/extra/mod_http_offline.spec.broken
@@ -0,0 +1,5 @@
+author: "Rael Max"
+category: "http"
+summary: "POST offline messages to a web"
+home: "https://github.com/raelmax/mod_http_offline/tree/master/"
+url: "git@github.com:raelmax/mod_http_offline.git"
diff --git a/extra/mod_restful.spec.broken b/extra/mod_restful.spec.broken
new file mode 100644
index 0000000..9c12d4a
--- /dev/null
+++ b/extra/mod_restful.spec.broken
@@ -0,0 +1,5 @@
+author: "Jonas Ådahl <jadahl at gmail.com>"
+category: "http"
+summary: "RESTful API for ejabberd"
+home: "https://github.com/jadahl/mod_restful/tree/master/"
+url: "git@github.com:jadahl/mod_restful.git"
diff --git a/ircd/README.txt b/ircd/README.txt
new file mode 100644
index 0000000..aa2d185
--- /dev/null
+++ b/ircd/README.txt
@@ -0,0 +1,94 @@
+
+
+		***************
+		  PLEASE NOTE
+		***************
+
+	This module does NOT work
+	with ejabberd 13 or newer.
+
+		***************
+
+
+	ircd  -  IRC-to-XMPP interface
+
+	Author:
+	  Magnus Henoch
+	  xmpp:legoscia@jabber.cd.chalmers.se,
+	  mailto:henoch@dtek.chalmers.se
+	Homepage:
+	  http://www.dtek.chalmers.se/~henoch/text/ejabberd-ircd.html
+	Requirements:
+	  ejabberd trunk SVN 1631 or newer
+
+
+	DESCRIPTION
+	===========
+
+This is an IRC server frontend to ejabberd.  It supports a subset of
+the IRC protocol, allowing IRC users to use a subset of Jabber MUC
+functions.  Users log in with their username and password, just as if
+they were Jabber users.  Therefore, configuring the IRC interface to
+use an anonymous authentication backend is probably what users expect.
+Channel names are translated to MUC rooms on a particular MUC service.
+
+The most obvious missing functions in this module are operator actions
+and a command to list channels.
+
+
+	CONFIGURATION
+	=============
+
+Something like this should be inserted in the "listen" section of the
+configuration file:
+
+{listen, [
+  ...
+  {6667, ejabberd_ircd,    [{access, c2s},
+			    {host, "example.org"},
+			    {muc_host, "conference.example.org"},
+			    {encoding, "utf-8"},
+			    {mappings,
+			    [{"#esperanto", "esperanto@conference.jabber.org"}]} ]},
+  ...
+]}.
+
+Configurable module options:
+  access: ACL matching users allowed to use the IRC backend.
+  host: hostname part of the JIDs of IRC users.
+  muc_host: MUC service hosting IRC "channels".
+  encoding: encoding that IRC users are expected to use.
+  mappings: optional list of mappings from channel names to MUC rooms
+    on other MUC services.
+
+
+	AUTHENTICATION
+	==============
+
+The IRC client needs to login in ejabberd. If the 'internal' auth
+method is enabled, then the IRC client must provide the username and
+password of an existing Jabber account.
+
+If you want to allow an IRC client to join in MUC rooms without
+requiring authentication, you can enable anonyous authentication in
+ejabberd.
+
+Note that this module doesn't do SASL ANONYMOUS authentication.  This
+means that to use anonymous authentication, the "anonymous_protocol"
+option needs to be either "login_anon" or "both".
+
+For example, you can define a new Jabber virtual host used only for
+anonymous authentication by ejabberd_ircd:
+
+{hosts, ["example.org", "anonymous.example.org"]}.
+{host_config, "anonymous.example.org",
+	      [{auth_method, anonymous},
+	       {anonymous_protocol, both}]}.
+{listen, [
+  ...
+  {6667, ejabberd_ircd,    [{access, c2s},
+			    {host, "anonymous.example.org"},
+			    {muc_host, "conference.example.org"},
+			    {encoding, "utf-8"} ]},
+  ...
+]}.
diff --git a/ircd/ircd.spec.broken b/ircd/ircd.spec.broken
new file mode 100644
index 0000000..88df284
--- /dev/null
+++ b/ircd/ircd.spec.broken
@@ -0,0 +1,5 @@
+author: "Magnus Henoch <henoch at dtek.chalmers.se>"
+category: "listener"
+summary: "IRC server frontend to ejabberd"
+home: "https://github.com/processone/ejabberd-contrib/tree/master/"
+url: "git@github.com:processone/ejabberd-contrib.git"
diff --git a/ircd/src/ejabberd_ircd.erl b/ircd/src/ejabberd_ircd.erl
new file mode 100644
index 0000000..555ad86
--- /dev/null
+++ b/ircd/src/ejabberd_ircd.erl
@@ -0,0 +1,916 @@
+-module(ejabberd_ircd).
+-author('henoch@dtek.chalmers.se').
+-update_info({update, 0}).
+
+-behaviour(gen_fsm).
+
+%% External exports
+-export([start/2,
+	 start_link/2,
+	 socket_type/0]).
+
+%% gen_fsm callbacks
+-export([init/1,
+	 wait_for_nick/2,
+	 wait_for_cmd/2,
+	 handle_event/3,
+	 handle_sync_event/4,
+	 code_change/4,
+	 handle_info/3,
+	 terminate/3
+	]).
+
+%-define(ejabberd_debug, true).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+
+-define(DICT, dict).
+
+-record(state, {socket,
+		sockmod,
+		access,
+		encoding,
+		shaper,
+		host,
+		muc_host,
+		sid = none,
+		pass = "",
+		nick = none,
+		user = none,
+		%% joining is a mapping from room JIDs to nicknames
+		%% received but not yet forwarded
+		joining = ?DICT:new(),
+		joined = ?DICT:new(),
+		%% mapping certain channels to certain rooms
+		channels_to_jids = ?DICT:new(),
+		jids_to_channels = ?DICT:new()
+	       }).
+-record(channel, {participants = [],
+		  topic = ""}).
+
+-record(line, {prefix, command, params}).
+
+%-define(DBGFSM, true).
+
+-ifdef(DBGFSM).
+-define(FSMOPTS, [{debug, [trace]}]).
+-else.
+-define(FSMOPTS, []).
+-endif.
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start(SockData, Opts) ->
+    supervisor:start_child(ejabberd_ircd_sup, [SockData, Opts]).
+
+start_link(SockData, Opts) ->
+    gen_fsm:start_link(ejabberd_ircd, [SockData, Opts], ?FSMOPTS).
+
+socket_type() ->
+    raw.
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_fsm
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, StateName, StateData}          |
+%%          {ok, StateName, StateData, Timeout} |
+%%          ignore                              |
+%%          {stop, StopReason}
+%%----------------------------------------------------------------------
+init([{SockMod, Socket}, Opts]) ->
+    %iconv:start(),
+    Access = case lists:keysearch(access, 1, Opts) of
+		 {value, {_, A}} -> A;
+		 _ -> all
+	     end,
+    Shaper = case lists:keysearch(shaper, 1, Opts) of
+		 {value, {_, S}} -> S;
+		 _ -> none
+	     end,
+    Host = case lists:keysearch(host, 1, Opts) of
+	       {value, {_, H}} -> H;
+	       _ -> ?MYNAME
+	   end,
+    MucHost = case lists:keysearch(muc_host, 1, Opts) of
+		  {value, {_, M}} -> M;
+		  _ -> "conference." ++ ?MYNAME
+	      end,
+    Encoding = case lists:keysearch(encoding, 1, Opts) of
+		   {value, {_, E}} -> E;
+		   _ -> "utf-8"
+	       end,
+    ChannelMappings = case lists:keysearch(mappings, 1, Opts) of
+			  {value, {_, C}} -> C;
+			  _ -> []
+		      end,
+    {ChannelToJid, JidToChannel} =
+	lists:foldl(fun({Channel, Room}, {CToJ, JToC}) ->
+			    RoomJID = jlib:string_to_jid(Room),
+			    BareChannel = case Channel of
+					      [$#|R] -> R;
+					      _ -> Channel
+					  end,
+			    {?DICT:store(BareChannel, RoomJID, CToJ),
+			     ?DICT:store(RoomJID, BareChannel, JToC)}
+		    end, {?DICT:new(), ?DICT:new()},
+		    ChannelMappings),
+    inet:setopts(Socket, [list, {packet, line}, {active, true}]),
+    %%_ReceiverPid = start_ircd_receiver(Socket, SockMod),
+    {ok, wait_for_nick, #state{socket    = Socket,
+			       sockmod   = SockMod,
+			       access    = Access,
+			       encoding  = Encoding,
+			       shaper    = Shaper,
+			       host      = Host,
+			       muc_host  = MucHost,
+			       channels_to_jids = ChannelToJid,
+			       jids_to_channels = JidToChannel
+			      }}.
+
+handle_info({tcp, _Socket, Line}, StateName, StateData) ->
+    %DecodedLine = iconv:convert(StateData#state.encoding, "utf-8", Line),
+    DecodedLine = Line,
+    Parsed = parse_line(DecodedLine),
+    ?MODULE:StateName({line, Parsed}, StateData);
+handle_info({tcp_closed, _}, _StateName, StateData) ->
+    {stop, normal, StateData};
+handle_info({route, _, _, _} = Event, StateName, StateData) ->
+    ?MODULE:StateName(Event, StateData);
+handle_info(Info, StateName, StateData) ->
+    ?ERROR_MSG("Unexpected info: ~p", [Info]),
+    {next_state, StateName, StateData}.
+
+handle_sync_event(Event, _From, StateName, StateData) ->
+    ?ERROR_MSG("Unexpected sync event: ~p", [Event]),
+    Reply = ok,
+    {reply, Reply, StateName, StateData}.
+
+handle_event(_Event, StateName, StateData) ->
+    {next_state, StateName, StateData}.
+
+code_change(_OldVsn, StateName, StateData, _Extra) ->
+    {ok, StateName, StateData}.
+terminate(_Reason, _StateName, #state{socket = Socket, sockmod = SockMod,
+				      sid = SID, host = Host, nick = Nick,
+				      joined = JoinedDict} = State) ->
+    ?INFO_MSG("closing IRC connection for ~p", [Nick]),
+    case SID of
+	none ->
+	    ok;
+	_ ->
+	    Packet = {xmlel, <<"presence">>,
+		      [{<<"type">>, <<"unavailable">>}], []},
+	    FromJID = user_jid(State),
+	    ?DICT:map(fun(ChannelJID, _ChannelData) ->
+			      ejabberd_router:route(FromJID, ChannelJID, Packet)
+		      end, JoinedDict),
+	    ejabberd_sm:close_session_unset_presence(SID, Nick, Host, "irc", "Logged out")
+    end,
+    gen_tcp = SockMod,
+    ok = gen_tcp:close(Socket),
+    ok.
+
+
+wait_for_nick({line, #line{command = "PASS", params = Params}}, State) ->
+    ?DEBUG("in wait_for_nick", []),
+    Pass = hd(Params),
+    ?DEBUG("got password", []),
+    {next_state, wait_for_nick, State#state{pass = Pass}};
+wait_for_nick({line, #line{command = "NICK", params = Params}}, State) ->
+    ?DEBUG("in wait_for_nick", []),
+    Nick = hd(Params),
+    Pass = State#state.pass,
+    Server = State#state.host,
+    ?DEBUG("user=~p server=~p", [Nick, Server]),
+
+    JID = jlib:make_jid(list_to_binary(Nick), Server, <<"irc">>),
+    ?DEBUG("JID=~p", [JID]),
+    case JID of
+	error ->
+	    ?DEBUG("invalid nick '~p'", [Nick]),
+	    send_reply('ERR_ERRONEUSNICKNAME', [Nick, "Erroneous nickname"], State),
+	    {next_state, wait_for_nick, State};
+	_ ->
+	    case acl:match_rule(Server, State#state.access, JID) of
+		deny ->
+		    ?DEBUG("access denied for '~p'", [Nick]),
+		    send_reply('ERR_NICKCOLLISION', [Nick, "Nickname collision"], State),
+		    {next_state, wait_for_nick, State};
+		allow ->
+		    case ejabberd_auth:check_password(list_to_binary(Nick), Server, list_to_binary(Pass)) of
+			false ->
+			    ?DEBUG("auth failed for '~p'", [Nick]),
+			    send_reply('ERR_NICKCOLLISION', [Nick, "Authentication failed"], State),
+			    {next_state, wait_for_nick, State};
+			true ->
+			    ?DEBUG("good nickname '~p'", [Nick]),
+			    SID = {now(), self()},
+			    ejabberd_sm:open_session(
+			      SID, list_to_binary(Nick), Server, <<"irc">>, peerip(gen_tcp, State#state.socket)),
+			    ejabberd_sm:set_presence(SID, list_to_binary(Nick), Server, <<"irc">>,
+						     3, "undefined",
+						     [{'ip', peerip(gen_tcp, State#state.socket)}, {'conn','c2s'}, {'state',"+"}]),
+			    send_text_command("", "001", [Nick, "IRC interface of ejabberd server "++Server], State),
+			    send_reply('RPL_MOTDSTART', [Nick, "- "++binary_to_list(Server)++" Message of the day - "], State),
+			    send_reply('RPL_MOTD', [Nick, "- This is the IRC interface of the ejabberd server "++binary_to_list(Server)++"."], State),
+			    send_reply('RPL_MOTD', [Nick, "- Your full JID is "++Nick++"@"++binary_to_list(Server)++"/irc."], State),
+			    send_reply('RPL_MOTD', [Nick, "- Channel #whatever corresponds to MUC room whatever@"++binary_to_list(State#state.muc_host)++"."], State),
+			    send_reply('RPL_MOTD', [Nick, "- This IRC interface is quite immature.  You will probably find bugs."], State),
+			    send_reply('RPL_MOTD', [Nick, "- Have a good time!"], State),
+			    send_reply('RPL_ENDOFMOTD', [Nick, "End of /MOTD command"], State),
+			    {next_state, wait_for_cmd, State#state{nick = Nick, sid = SID, pass = ""}}
+		    end
+	    end
+    end;
+wait_for_nick(Event, State) ->
+    ?DEBUG("in wait_for_nick", []),
+    ?INFO_MSG("unexpected event ~p", [Event]),
+    {next_state, wait_for_nick, State}.
+
+peerip(SockMod, Socket) ->
+    IP = case SockMod of
+	     gen_tcp -> inet:peername(Socket);
+	     _ -> SockMod:peername(Socket)
+	 end,
+    case IP of
+	{ok, IPOK} -> IPOK;
+	_ -> undefined
+    end.
+
+wait_for_cmd({line, #line{command = "USER", params = [_Username, _Hostname, _Servername, _Realname]}}, State) ->
+    %% Yeah, like we care.
+    {next_state, wait_for_cmd, State};
+wait_for_cmd({line, #line{command = "JOIN", params = Params}}, State) ->
+    ?DEBUG("received JOIN ~p", [Params]),
+    {ChannelsString, KeysString} =
+	case Params of
+	    [C, K] ->
+		{C, K};
+	    [C] ->
+		{C, []}
+	end,
+    Channels = string:tokens(ChannelsString, ","),
+    Keys = string:tokens(KeysString, ","),
+    ?DEBUG("joining channels ~p", [Channels]),
+    NewState = join_channels(Channels, Keys, State),
+    ?DEBUG("joined channels ~p", [Channels]),
+    {next_state, wait_for_cmd, NewState};
+
+%% USERHOST command
+wait_for_cmd({line, #line{command = "USERHOST", params = Params}}, State) ->
+    case Params of
+        [] ->
+	    send_reply('ERR_NEEDMOREPARAMS', ["USERHOST", "Not enough parameters"], State);
+        UserParams ->
+	    Users = lists:sublist(string:tokens(UserParams, " "), 5), %% RFC 1459 specifies 5 items max
+	    lists:foreach(
+	      fun(UserSubList) ->
+		      User = lists:last(UserSubList),
+		      case ejabberd_sm:get_user_info(User, State#state.host, "irc") of
+			  offline ->
+			      send_reply('RPL_USERHOST',[State#state.nick, User++" offline"], State);
+			  [_Node, _Conn, Ip] ->
+			      {_,{{IP1,IP2,IP3,IP4}, _}} = Ip,
+			      send_reply('RPL_USERHOST',[State#state.nick, User ++ "=+" ++ integer_to_list(IP1) ++ "." ++
+							 integer_to_list(IP2) ++ "." ++ integer_to_list(IP3) ++ "." ++ integer_to_list(IP4)], State)
+		      end
+	      end, Users)
+    end,
+    {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "PART", params = [ChannelsString | MaybeMessage]}}, State) ->
+    Message = case MaybeMessage of
+		  [] -> nothing;
+		  [M] -> M
+	      end,
+    Channels = string:tokens(ChannelsString, ","),
+    NewState = part_channels(Channels, State, Message),
+    {next_state, wait_for_cmd, NewState};
+
+wait_for_cmd({line, #line{command = "PRIVMSG", params = [To, Text]}}, State) ->
+    Recipients = string:tokens(To, ","),
+    FromJID = user_jid(State),
+    lists:foreach(
+      fun(Rcpt) ->
+	      case Rcpt of
+		  [$# | Roomname] ->
+		      Packet = {xmlel, <<"message">>,
+				[{<<"type">>, <<"groupchat">>}],
+				[{xmlel, <<"body">>, [],
+				  filter_cdata(translate_action(Text))}]},
+		      ToJID = channel_to_jid(Roomname, State),
+		      ejabberd_router:route(FromJID, ToJID, Packet);
+		  _ ->
+		      case string:tokens(Rcpt, "#") of
+			  [Nick, Channel] ->
+			      Packet = {xmlel, <<"message">>,
+					[{<<"type">>, <<"chat">>}],
+					[{xmlel, <<"body">>, [],
+					  filter_cdata(translate_action(Text))}]},
+			      ToJID = channel_nick_to_jid(Nick, Channel, State),
+			      ejabberd_router:route(FromJID, ToJID, Packet);
+			  _ ->
+			      send_text_command(Rcpt, "NOTICE", [State#state.nick,
+								 "Your message to "++
+								 Rcpt++
+								 " was dropped.  "
+								 "Try sending it to "++Rcpt++
+								 "#somechannel."], State)
+		      end
+	      end
+      end, Recipients),
+    {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "PING", params = Params}}, State) ->
+    {Token, Whom} =
+	case Params of
+	    [A] ->
+		{A, ""};
+	    [A, B] ->
+		{A, B}
+	end,
+    if Whom == ""; Whom == State#state.host ->
+	    %% Ping to us
+	    send_command("", "PONG", [State#state.host, Token], State);
+       true ->
+	    %% Ping to someone else
+	    ?DEBUG("ignoring ping to ~s", [Whom]),
+	    ok
+    end,
+    {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "TOPIC", params = Params}}, State) ->
+    case Params of
+	[Channel] ->
+	    %% user asks for topic
+	    case ?DICT:find(channel_to_jid(Channel, State),
+			    State#state.joined) of
+		{ok, #channel{topic = Topic}} ->
+		    case Topic of
+			"" ->
+			    send_reply('RPL_NOTOPIC', ["No topic is set"], State);
+			_ ->
+			    send_reply('RPL_TOPIC', [Topic], State)
+		    end;
+		_ ->
+		    send_reply('ERR_NOTONCHANNEL', ["You're not on that channel"], State)
+	    end;
+	[Channel, NewTopic] ->
+	    Packet =
+		{xmlel, <<"message">>,
+		 [{<<"type">>, <<"groupchat">>}],
+		 [{xmlel, <<"subject">>, [], filter_cdata(NewTopic)}]},
+	    FromJID = user_jid(State),
+	    ToJID = channel_to_jid(Channel, State),
+	    ejabberd_router:route(FromJID, ToJID, Packet)
+    end,
+    {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "MODE", params = [ModeOf | Params]}}, State) ->
+    case ModeOf of
+	[$# | Channel] ->
+	    ChannelJid = channel_to_jid(Channel, State),
+	    Joined = ?DICT:find(ChannelJid, State#state.joined),
+	    case Joined of
+		{ok, _ChannelData} ->
+		    case Params of
+			[] ->
+			    %% This is where we could mirror some advanced MUC
+			    %% properties.
+			    %%send_reply('RPL_CHANNELMODEIS', [Channel, Modes], State);
+			    send_reply('ERR_NOCHANMODES', [Channel], State);
+			["b"] ->
+			    send_reply('RPL_ENDOFBANLIST', [Channel, "Ban list not available"], State);
+			_ ->
+			    send_reply('ERR_UNKNOWNCOMMAND', ["MODE", io_lib:format("MODE ~p not understood", [Params])], State)
+		    end;
+		_ ->
+		    send_reply('ERR_NOTONCHANNEL', [Channel, "You're not on that channel"], State)
+	    end;
+	Nick ->
+	    if Nick == State#state.nick ->
+		    case Params of
+			[] ->
+			    send_reply('RPL_UMODEIS', [], State);
+			[Flags|_] ->
+			    send_reply('ERR_UMODEUNKNOWNFLAG', [Flags, "No MODE flags supported"], State)
+		    end;
+	       true ->
+		    send_reply('ERR_USERSDONTMATCH', ["Can't change mode for other users"], State)
+	    end
+    end,
+    {next_state, wait_for_cmd, State};
+
+wait_for_cmd({line, #line{command = "QUIT"}}, State) ->
+    %% quit message is ignored for now
+    {stop, normal, State};
+
+wait_for_cmd({line, #line{command = Unknown, params = Params} = Line}, State) ->
+    ?INFO_MSG("Unknown command: ~p", [Line]),
+    send_reply('ERR_UNKNOWNCOMMAND', [Unknown, "Unknown command or arity: " ++
+				      Unknown ++ "/" ++ integer_to_list(length(Params))], State),
+    {next_state, wait_for_cmd, State};
+
+wait_for_cmd({route, From, _To, {xmlel, <<"presence">>, Attrs, Els} = El}, State) ->
+    ?DEBUG("Received a Presence ~p ~p ~p", [From, _To, El]),
+    Type = xml:get_attr_s("type", Attrs),
+    FromRoom = jlib:jid_remove_resource(From),
+    FromNick = binary_to_list(From#jid.resource),
+
+    Channel = jid_to_channel(From, State),
+    MyNick = State#state.nick,
+    IRCSender = make_irc_sender(FromNick, FromRoom, State),
+
+    Joining = ?DICT:find(FromRoom, State#state.joining),
+    Joined = ?DICT:find(FromRoom, State#state.joined),
+    ?DEBUG("JoinState ~p ~p ~p", [Joining, Joined, Type]),
+    case {Joining, Joined, Type} of
+	{{ok, BufferedNicks}, _, <<"">>} ->
+            ?DEBUG("BufferedNicks ~p", [BufferedNicks]),
+	    case BufferedNicks of
+		[] ->
+		    %% If this is the first presence, tell the
+		    %% client that it's joining.
+                    ?DEBUG("Sending Command ~p ~p", [IRCSender, Channel]),
+		    send_command(IRCSender, "JOIN", [Channel], State),
+                    ?DEBUG("Command Sent", []);
+		_ ->
+		    ok
+	    end,
+
+            ?DEBUG("Getting NewRole", []),
+	    NewRole = case find_el("x", ?NS_MUC_USER, Els) of
+			  nothing ->
+			      "";
+			  XMucEl ->
+			      xml:get_path_s(XMucEl, [{elem, "item"}, {attr, "role"}])
+		      end,
+            ?DEBUG("NewRole ~p", [NewRole]),
+	    NewBufferedNicks = [{FromNick, NewRole} | BufferedNicks],
+	    ?DEBUG("~s is present in ~s.  we now have ~p.",
+		   [FromNick, Channel, NewBufferedNicks]),
+	    %% We receive our own presence last.  XXX: there
+	    %% are some status codes here.  See XEP-0045,
+	    %% section 7.1.3.
+	    NewState =
+		case FromNick of
+		    MyNick ->
+			send_reply('RPL_NAMREPLY',
+				   [MyNick, "=",
+				    Channel,
+				    lists:append(
+				      lists:map(
+					fun({Nick, Role}) ->
+						case Role of
+						    "moderator" ->
+							"@";
+						    "participant" ->
+							"+";
+						    _ ->
+							""
+						end ++ Nick ++ " "
+					end, NewBufferedNicks))],
+				   State),
+			send_reply('RPL_ENDOFNAMES',
+				   [Channel,
+				    "End of /NAMES list"],
+				   State),
+			NewJoiningDict = ?DICT:erase(FromRoom, State#state.joining),
+			ChannelData = #channel{participants = NewBufferedNicks},
+			NewJoinedDict = ?DICT:store(FromRoom, ChannelData, State#state.joined),
+			State#state{joining = NewJoiningDict,
+				    joined = NewJoinedDict};
+		    _ ->
+			NewJoining = ?DICT:store(FromRoom, NewBufferedNicks, State#state.joining),
+			State#state{joining = NewJoining}
+		end,
+	    {next_state, wait_for_cmd, NewState};
+	{{ok, _BufferedNicks}, _, <<"error">>} ->
+	    NewState =
+		case FromNick of
+		    MyNick ->
+			%% we couldn't join the room
+			{ReplyCode, ErrorDescription} =
+			    case xml:get_subtag(El, "error") of
+				{xmlel, _, _, _} = ErrorEl ->
+				    {ErrorName, ErrorText} = parse_error(ErrorEl),
+				    {case ErrorName of
+					 "forbidden" -> 'ERR_INVITEONLYCHAN';
+					 _ -> 'ERR_NOSUCHCHANNEL'
+				     end,
+				     if is_list(ErrorText) ->
+					     ErrorName ++ ": " ++ ErrorText;
+					true ->
+					     ErrorName
+				     end};
+				_ ->
+				    {'ERR_NOSUCHCHANNEL', "Unknown error"}
+			    end,
+			send_reply(ReplyCode, [Channel, ErrorDescription], State),
+
+			NewJoiningDict = ?DICT:erase(FromRoom, State#state.joining),
+			State#state{joining = NewJoiningDict};
+		    _ ->
+			?ERROR_MSG("ignoring presence of type ~s from ~s while joining room",
+				   [Type, jlib:jid_to_string(From)]),
+			State
+		end,
+	    {next_state, wait_for_cmd, NewState};
+	%% Presence in a channel we have already joined
+	{_, {ok, _}, <<"">>} ->
+	    %% Someone enters
+	    send_command(IRCSender, "JOIN", [Channel], State),
+	    {next_state, wait_for_cmd, State};
+	{_, {ok, _}, _} ->
+	    %% Someone leaves
+	    send_command(IRCSender, "PART", [Channel], State),
+	    {next_state, wait_for_cmd, State};
+	_ ->
+	    ?INFO_MSG("unexpected presence from ~s", [jlib:jid_to_string(From)]),
+	    {next_state, wait_for_cmd, State}
+    end;
+
+wait_for_cmd({route, From, _To, {xmlel, <<"message">>, Attrs, Els} = El}, State) ->
+    ?DEBUG("Got a Message! ~p ~p ~p", [From, _To, El]),
+    Type = xml:get_attr_s(<<"type">>, Attrs),
+    case Type of
+	<<"groupchat">> ->
+            ?DEBUG("It's a groupchat", []),
+	    ChannelJID = jlib:jid_remove_resource(From),
+	    case ?DICT:find(ChannelJID, State#state.joined) of
+		{ok, #channel{} = ChannelData} ->
+		    FromChannel = jid_to_channel(From, State),
+		    FromNick = binary_to_list(From#jid.resource),
+		    Subject = xml:get_path_s(El, [{elem, <<"subject">>}, cdata]),
+		    Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+                    ?DEBUG("Message Data ~p ~p", [Subject, Body]),
+		    XDelay = lists:any(fun({xmlel, <<"x">>, XAttrs, _}) ->
+					       xml:get_attr_s(<<"xmlns">>, XAttrs) == ?NS_DELAY;
+					  (_) ->
+					       false
+				       end, Els),
+                    ?DEBUG("XDelay ~p", [XDelay]),
+		    if
+			Subject /= <<"">> ->
+                            ?DEBUG("Cleaning Subject!", []),
+			    CleanSubject = lists:map(fun($\n) ->
+							     $\ ;
+							(C) -> C
+						     end, binary_to_list(Subject)),
+                            ?DEBUG("CleanSubject ~p", [CleanSubject]),
+                            IRCSender = make_irc_sender(From, State),
+                            ?DEBUG("IRCSender ~p", [IRCSender]),
+			    send_text_command(IRCSender,
+					      "TOPIC", [FromChannel, CleanSubject], State),
+			    NewChannelData = ChannelData#channel{topic = CleanSubject},
+			    NewState = State#state{joined = ?DICT:store(jlib:jid_remove_resource(From), NewChannelData, State#state.joined)},
+			    {next_state, wait_for_cmd, NewState};
+			not XDelay, FromNick == State#state.nick ->
+			    %% there is no message echo in IRC.
+			    %% we let the backlog through, though.
+                            ?DEBUG("Don't care about it", []),
+			    {next_state, wait_for_cmd, State};
+			true ->
+                            ?DEBUG("Send it to someone!", []),
+			    BodyLines = string:tokens(binary_to_list(Body), "\n"),
+			    lists:foreach(
+			      fun(Line) ->
+				      Line1 =
+					  case Line of
+					      [$/, $m, $e, $  | Action] ->
+						  [1]++"ACTION "++Action++[1];
+					      _ ->
+						  Line
+					  end,
+				      send_text_command(make_irc_sender(From, State),
+							"PRIVMSG", [FromChannel, Line1], State)
+			      end, BodyLines),
+			    {next_state, wait_for_cmd, State}
+		    end;
+		error ->
+		    ?ERROR_MSG("got message from ~s without having joined it",
+			       [jlib:jid_to_string(ChannelJID)]),
+		    {next_state, wait_for_cmd, State}
+	    end;
+	<<"error">> ->
+	    MucHost = State#state.muc_host,
+	    ErrorFrom =
+		case From of
+		    #jid{lserver = MucHost,
+			 luser = Room,
+			 lresource = ""} ->
+			[$#|Room];
+		    #jid{lserver = MucHost,
+			 luser = Room,
+			 lresource = Nick} ->
+			Nick++"#"++Room;
+		    #jid{} ->
+			%% ???
+			jlib:jid_to_string(From)
+		end,
+	    %% I think this should cover all possible combinations of
+	    %% XMPP and non-XMPP error messages...
+	    ErrorText =
+		error_to_string(xml:get_subtag(El, <<"error">>)),
+	    send_text_command("", "NOTICE", [State#state.nick,
+					     "Message to "++ErrorFrom++" bounced: "++
+					     ErrorText], State),
+	    {next_state, wait_for_cmd, State};
+	_ ->
+	    ChannelJID = jlib:jid_remove_resource(From),
+	    case ?DICT:find(ChannelJID, State#state.joined) of
+		{ok, #channel{}} ->
+		    FromNick = binary_to_list(From#jid.lresource)++jid_to_channel(From, State),
+		    Body = xml:get_path_s(El, [{elem, <<"body">>}, cdata]),
+		    BodyLines = string:tokens(Body, "\n"),
+		    lists:foreach(
+		      fun(Line) ->
+			      Line1 =
+				  case Line of
+				      [$/, $m, $e, $  | Action] ->
+					  [1]++"ACTION "++Action++[1];
+				      _ ->
+					  Line
+				  end,
+			      send_text_command(FromNick, "PRIVMSG", [State#state.nick, Line1], State)
+		      end, BodyLines),
+		    {next_state, wait_for_cmd, State};
+	       _ ->
+		    ?INFO_MSG("unexpected message from ~s", [jlib:jid_to_string(From)]),
+		    {next_state, wait_for_cmd, State}
+	    end
+    end;
+
+wait_for_cmd(Event, State) ->
+    ?INFO_MSG("unexpected event ~p", [Event]),
+    {next_state, wait_for_cmd, State}.
+
+join_channels([], _, State) ->
+    State;
+join_channels(Channels, [], State) ->
+    join_channels(Channels, [none], State);
+join_channels([Channel | Channels], [Key | Keys],
+	      #state{nick = Nick} = State) ->
+    Packet =
+	{xmlel, <<"presence">>, [],
+	 [{xmlel, <<"x">>, [{<<"xmlns">>, ?NS_MUC}],
+	   case Key of
+	       none ->
+		   [];
+	       _ ->
+		   [{xmlel, <<"password">>, [], filter_cdata(Key)}]
+	   end}]},
+    ?DEBUG("joining channel nick=~p channel=~p state=~p", [Nick, Channel, State]),
+    From = user_jid(State),
+    ?DEBUG("1 ~p", [From]),
+    To = channel_nick_to_jid(Nick, Channel, State),
+    ?DEBUG("2 ~p", [To]),
+    Room = jlib:jid_remove_resource(To),
+    ?DEBUG("3 ~p", [Room]),
+    ejabberd_router:route(From, To, Packet),
+    ?DEBUG("4", []),
+    NewState = State#state{joining = ?DICT:store(Room, [], State#state.joining)},
+    ?DEBUG("5 ~p", [NewState]),
+    join_channels(Channels, Keys, NewState).
+
+part_channels([], State, _Message) ->
+    State;
+part_channels([Channel | Channels], State, Message) ->
+    Packet =
+	{xmlel, <<"presence">>,
+	 [{<<"type">>, <<"unavailable">>}],
+	 case Message of
+	    nothing -> [];
+	    _ -> [{xmlel, <<"status">>, [],
+		  [{xmlcdata, Message}]}]
+	 end},
+    From = user_jid(State),
+    To = channel_nick_to_jid(State#state.nick, Channel, State),
+    ejabberd_router:route(From, To, Packet),
+    RoomJID = channel_to_jid(Channel, State),
+    NewState = State#state{joined = ?DICT:erase(RoomJID, State#state.joined)},
+    part_channels(Channels, NewState, Message).
+
+parse_line(Line) ->
+    {Line1, LastParam} =
+	case string:str(Line, " :") of
+	    0 ->
+		{Line, []};
+	    Index ->
+		{string:substr(Line, 1, Index - 1),
+		 [string:substr(Line, Index + 2) -- "\r\n"]}
+	end,
+    Tokens = string:tokens(Line1, " \r\n"),
+    {Prefix, Tokens1} =
+	case Line1 of
+	    [$: | _] ->
+		{hd(Tokens), tl(Tokens)};
+	    _ ->
+		{none, Tokens}
+	end,
+    [Command | Params] = Tokens1,
+    UCCommand = upcase(Command),
+    #line{prefix = Prefix, command = UCCommand, params = Params ++ LastParam}.
+
+upcase([]) ->
+    [];
+upcase([C|String]) ->
+    [if $a =< C, C =< $z ->
+	     C - ($a - $A);
+	true ->
+	     C
+     end | upcase(String)].
+
+%% sender
+
+send_line(Line, #state{sockmod = SockMod, socket = Socket, encoding = Encoding}) ->
+    ?DEBUG("sending ~s", [Line]),
+    gen_tcp = SockMod,
+    %EncodedLine = iconv:convert("utf-8", Encoding, Line),
+    EncodedLine = Line,
+    ok = gen_tcp:send(Socket, [EncodedLine, 13, 10]).
+
+send_command(Sender, Command, Params, State) ->
+    send_command(Sender, Command, Params, State, false).
+
+%% Some IRC software require commands with text to have the text
+%% quoted, even it's not if not necessary.
+send_text_command(Sender, Command, Params, State) ->
+    send_command(Sender, Command, Params, State, true).
+
+send_command(Sender, Command, Params, State, AlwaysQuote) ->
+    ?DEBUG("SendCommand ~p ~p ~p", [Sender, Command, Params]),
+    Prefix = case Sender of
+		 "" ->
+		     [$: | binary_to_list(State#state.host)];
+		 _ ->
+		     [$: | Sender]
+	     end,
+    ParamString = make_param_string(Params, AlwaysQuote),
+    send_line(Prefix ++ " " ++ Command ++ ParamString, State).
+
+send_reply(Reply, Params, State) ->
+    Number = case Reply of
+		 'ERR_UNKNOWNCOMMAND' ->
+		     "421";
+		 'ERR_ERRONEUSNICKNAME' ->
+		     "432";
+		 'ERR_NICKCOLLISION' ->
+		     "436";
+		 'ERR_NOTONCHANNEL' ->
+		     "442";
+		 'ERR_NOCHANMODES' ->
+		     "477";
+		 'ERR_UMODEUNKNOWNFLAG' ->
+		     "501";
+		 'ERR_USERSDONTMATCH' ->
+		     "502";
+		 'RPL_UMODEIS' ->
+		     "221";
+		 'RPL_CHANNELMODEIS' ->
+		     "324";
+		 'RPL_NAMREPLY' ->
+		     "353";
+		 'RPL_ENDOFNAMES' ->
+		     "366";
+		 'RPL_BANLIST' ->
+		     "367";
+		 'RPL_ENDOFBANLIST' ->
+		     "368";
+		 'RPL_NOTOPIC' ->
+		     "331";
+		 'RPL_TOPIC' ->
+		     "332";
+		 'RPL_MOTD' ->
+		     "372";
+		 'RPL_MOTDSTART' ->
+		     "375";
+		 'RPL_ENDOFMOTD' ->
+		     "376"
+	     end,
+    send_text_command("", Number, Params, State).
+
+make_param_string([], _) -> "";
+make_param_string([LastParam], AlwaysQuote) ->
+    case {AlwaysQuote, LastParam, lists:member($\ , LastParam)} of
+	{true, _, _} ->
+	    " :" ++ LastParam;
+	{_, _, true} ->
+	    " :" ++ LastParam;
+	{_, [$:|_], _} ->
+	    " :" ++ LastParam;
+	{_, _, _} ->
+	    " " ++ LastParam
+    end;
+make_param_string([Param | Params], AlwaysQuote) ->
+    case lists:member($\ , Param) of
+	false ->
+	    " " ++ Param ++ make_param_string(Params, AlwaysQuote)
+    end.
+
+find_el(Name, NS, [{xmlel, N, Attrs, _} = El|Els]) ->
+    XMLNS = xml:get_attr_s("xmlns", Attrs),
+    case {Name, NS} of
+	{N, XMLNS} ->
+	    El;
+	_ ->
+	    find_el(Name, NS, Els)
+    end;
+find_el(_, _, []) ->
+    nothing.
+
+channel_to_jid([$#|Channel], State) ->
+    channel_to_jid(Channel, State);
+channel_to_jid(Channel, #state{muc_host = MucHost,
+			       channels_to_jids = ChannelsToJids}) ->
+    case ?DICT:find(Channel, ChannelsToJids) of
+	{ok, RoomJID} -> RoomJID;
+	_ -> jlib:make_jid(list_to_binary(Channel), MucHost, <<"">>)
+    end.
+
+channel_nick_to_jid(Nick, [$#|Channel], State) ->
+    channel_nick_to_jid(Nick, Channel, State);
+channel_nick_to_jid(Nick, Channel, #state{muc_host = MucHost,
+					 channels_to_jids = ChannelsToJids}) ->
+    case ?DICT:find(Channel, ChannelsToJids) of
+	{ok, RoomJID} -> jlib:jid_replace_resource(RoomJID, list_to_binary(Nick));
+	_ -> jlib:make_jid(list_to_binary(Channel), MucHost, list_to_binary(Nick))
+    end.
+
+jid_to_channel(#jid{user = Room} = RoomJID,
+	       #state{jids_to_channels = JidsToChannels}) ->
+    case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
+	{ok, Channel} -> [$#|binary_to_list(Channel)];
+	_ -> [$#|binary_to_list(Room)]
+    end.
+
+make_irc_sender(Nick, #jid{luser = Room} = RoomJID,
+		#state{jids_to_channels = JidsToChannels}) ->
+    case ?DICT:find(jlib:jid_remove_resource(RoomJID), JidsToChannels) of
+	{ok, Channel} -> Nick++"!"++Nick++"@"++binary_to_list(Channel);
+	_ -> Nick++"!"++Nick++"@"++binary_to_list(Room)
+    end.
+make_irc_sender(#jid{lresource = Nick} = JID, State) ->
+    make_irc_sender(binary_to_list(Nick), JID, State).
+
+user_jid(#state{nick = Nick, host = Host}) ->
+    jlib:make_jid(list_to_binary(Nick), Host, <<"irc">>).
+
+filter_cdata(Msg) ->
+    [{xmlcdata, filter_message(Msg)}].
+
+filter_message(Msg) ->
+    lists:filter(
+      fun(C) ->
+	      if (C < 32) and
+		 %% Add color support, but break XML: (see https://support.process-one.net/browse/EJAB-1097 )
+		 %% (C /= 3) and
+		 (C /= 9) and
+		 (C /= 10) and
+		 (C /= 13) ->
+		      false;
+		 true -> true
+	      end
+      end, Msg).
+
+translate_action(Msg) ->
+    case Msg of
+	[1, $A, $C, $T, $I, $O, $N, $  | Action] ->
+	    "/me "++Action;
+	_ ->
+	    Msg
+    end.
+
+parse_error({xmlel, "error", _ErrorAttrs, ErrorEls} = ErrorEl) ->
+    ErrorTextEl = xml:get_subtag(ErrorEl, "text"),
+    ErrorName =
+	case ErrorEls -- [ErrorTextEl] of
+	    [{xmlel, ErrorReason, _, _}] ->
+		ErrorReason;
+	    _ ->
+		"unknown error"
+	end,
+    ErrorText =
+	case ErrorTextEl of
+	    {xmlel, _, _, _} ->
+		xml:get_tag_cdata(ErrorTextEl);
+	    _ ->
+		nothing
+    end,
+    {ErrorName, ErrorText}.
+
+error_to_string({xmlel, "error", _ErrorAttrs, _ErrorEls} = ErrorEl) ->
+    case parse_error(ErrorEl) of
+	{ErrorName, ErrorText} when is_list(ErrorText) ->
+	    ErrorName ++ ": " ++ ErrorText;
+	{ErrorName, _} ->
+	    ErrorName
+    end;
+error_to_string(_) ->
+    "unknown error".
diff --git a/mod_archive/COPYING b/mod_archive/COPYING
new file mode 100644
index 0000000..30404ce
--- /dev/null
+++ b/mod_archive/COPYING
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
diff --git a/mod_archive/README.txt b/mod_archive/README.txt
new file mode 100644
index 0000000..a744b2c
--- /dev/null
+++ b/mod_archive/README.txt
@@ -0,0 +1,84 @@
+
+
+		***************
+		  PLEASE NOTE
+		***************
+
+	Those modules do NOT work
+	with ejabberd 13 or newer.
+
+		***************
+
+
+	mod_archive - Message Archiving (XEP-0136)
+
+There are three different modules, the main difference between them is the storage method:
+	- mod_archive uses Mnesia
+	- mod_archive_sql uses PostgreSQL
+	- mod_archive_odbc uses MySQL or SQLite3
+
+As of today (2008-03-15) mod_archive_odbc is the most complete and maintained.
+
+And another module is used to view archive onlines
+	- mod_archive_webview  webviewer for mod_archive_odbc
+
+
+	MOD_ARCHIVE
+	===========
+
+Author: Olivier Goffart <ogoffart at kde.org>
+
+This module does support almost all the XEP-0136 version 0.6 except otr (off-the-record).
+
+Features
+ - Automatic archiving
+ - User may enable/disable automatic archiving for one contact or globally
+ - Manual archiving
+ - Retrieve or remove archive
+ - XEP-0059
+
+Not Supported
+ - Off the record
+ - Groupchats message
+
+Options
+ - save_default: true or false: whether or not messages should be saved by default
+ - session_duration: The time in seconds before a session timeout (for a collection). The default value is 30 minutes.
+
+Support of XEP-136 on Jabber clients
+ - JWChat: Implemented, but does not work, since it implements an old version. An update on JWChat is expected in the mid-term.
+ - Kopete: Planned for the mid-term.
+
+
+
+	MOD_ARCHIVE_SQL
+	===============
+
+Author: Alexey Shchepin
+Based in mod_archive, author: Olivier Goffart <ogoffart at kde.org>
+
+
+
+	MOD_ARCHIVE_ODBC
+	================
+
+Author: Alexander Tsvyashchenko
+Based in mod_archive, author: Olivier Goffart <ogoffart at kde.org>
+Based in mod_archive_sql, author: Alexey Shchepin
+
+For a detailed documentation about this module, please refer to
+http://endl.ch/content/mod_archive_odbc-release
+
+
+	MOD_ARCHIVE_WEBVIEW
+	===================
+Author: Olivier Goffart
+
+This module woks with the database of mod_archive_odbc
+
+Edit ejabberd.cfg and add the HTTP and module definitions: {["archive"], mod_archive_webview} to the list of request handler
+{listen, [ {5280, ejabberd_http, [     %...
+         {request_handlers, [{["archive"], mod_archive_webview}
+    ]} ]} ]}.
+    
+then go on http://your.server.com:5280/archive
diff --git a/mod_archive/mod_archive.spec.broken b/mod_archive/mod_archive.spec.broken
new file mode 100644
index 0000000..bddc57d
--- /dev/null
+++ b/mod_archive/mod_archive.spec.broken
@@ -0,0 +1,5 @@
+author: "Olivier Goffart <ogoffart at kde.org>"
+category: "archive"
+summary: "Supports almost all the XEP-0136 version 0.6 except otr"
+home: "https://github.com/processone/ejabberd-contrib/tree/master/"
+url: "git@github.com:processone/ejabberd-contrib.git"
diff --git a/mod_archive/priv/msgs/mod_archive_webview.pot b/mod_archive/priv/msgs/mod_archive_webview.pot
new file mode 100644
index 0000000..d3a28d2
--- /dev/null
+++ b/mod_archive/priv/msgs/mod_archive_webview.pot
@@ -0,0 +1,226 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: 2.1.x\n"
+"X-Language: Language Name\n"
+"Last-Translator: Translator name and contact method\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: mod_archive_webview.erl:69 mod_archive_webview.erl:83
+#: mod_archive_webview.erl:171
+msgid "Config"
+msgstr ""
+
+#: mod_archive_webview.erl:70 mod_archive_webview.erl:84
+msgid "Global Settings"
+msgstr ""
+
+#: mod_archive_webview.erl:71 mod_archive_webview.erl:85
+msgid "Specific Contact Settings"
+msgstr ""
+
+#: mod_archive_webview.erl:72
+msgid "Advanced settings"
+msgstr ""
+
+#: mod_archive_webview.erl:86
+msgid "Simple settings"
+msgstr ""
+
+#: mod_archive_webview.erl:93
+msgid "Contact List"
+msgstr ""
+
+#: mod_archive_webview.erl:102 mod_archive_webview.erl:112
+msgid "Chat with "
+msgstr ""
+
+#: mod_archive_webview.erl:112
+msgid " : "
+msgstr ""
+
+#: mod_archive_webview.erl:112
+msgid " on "
+msgstr ""
+
+#: mod_archive_webview.erl:116
+msgid "Edit subject: "
+msgstr ""
+
+#: mod_archive_webview.erl:118
+msgid "Ok"
+msgstr ""
+
+#: mod_archive_webview.erl:120
+msgid "Do you realy want to delete this chat"
+msgstr ""
+
+#: mod_archive_webview.erl:139 mod_archive_webview.erl:143
+#: mod_archive_webview.erl:173 mod_archive_webview.erl:369
+msgid "Search"
+msgstr ""
+
+#: mod_archive_webview.erl:146
+msgid "Archives viewer"
+msgstr ""
+
+#: mod_archive_webview.erl:149
+msgid "404 File not found"
+msgstr ""
+
+#: mod_archive_webview.erl:159
+msgid " - ejabberd Web Archive Viewer"
+msgstr ""
+
+#: mod_archive_webview.erl:168
+msgid "Archives Viewer"
+msgstr ""
+
+#: mod_archive_webview.erl:172
+msgid "Browse"
+msgstr ""
+
+#: mod_archive_webview.erl:235
+msgid "Disable or enable automatic archiving globaly: "
+msgstr ""
+
+#: mod_archive_webview.erl:236 mod_archive_webview.erl:239
+msgid "--Server Default--"
+msgstr ""
+
+#: mod_archive_webview.erl:236 mod_archive_webview.erl:239
+#: mod_archive_webview.erl:256 mod_archive_webview.erl:269
+#: mod_archive_webview.erl:283
+msgid "Disabled"
+msgstr ""
+
+#: mod_archive_webview.erl:236 mod_archive_webview.erl:239
+#: mod_archive_webview.erl:256 mod_archive_webview.erl:269
+#: mod_archive_webview.erl:283
+msgid "Enabled"
+msgstr ""
+
+#: mod_archive_webview.erl:238
+msgid "Default for contact not specified bellow : "
+msgstr ""
+
+#: mod_archive_webview.erl:240
+msgid "Default expiration time: "
+msgstr ""
+
+#: mod_archive_webview.erl:241
+msgid "(number of seconds before deleting message, '-1' = server default)"
+msgstr ""
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:258
+msgid "--Undefined--"
+msgstr ""
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:260
+msgid "Concede"
+msgstr ""
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:261
+msgid "Forbid"
+msgstr ""
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:263
+msgid "Prefer"
+msgstr ""
+
+#: mod_archive_webview.erl:256 mod_archive_webview.erl:269
+msgid "--Default--"
+msgstr ""
+
+#: mod_archive_webview.erl:256
+msgid "Save: "
+msgstr ""
+
+#: mod_archive_webview.erl:257
+msgid "Expire: "
+msgstr ""
+
+#: mod_archive_webview.erl:258
+msgid "Otr: "
+msgstr ""
+
+#: mod_archive_webview.erl:259
+msgid "Approve"
+msgstr ""
+
+#: mod_archive_webview.erl:262
+msgid "Oppose"
+msgstr ""
+
+#: mod_archive_webview.erl:264
+msgid "Require"
+msgstr ""
+
+#: mod_archive_webview.erl:265
+msgid "Auto Method: "
+msgstr ""
+
+#: mod_archive_webview.erl:266
+msgid "Local Method: "
+msgstr ""
+
+#: mod_archive_webview.erl:267
+msgid "Manual Method: "
+msgstr ""
+
+#: mod_archive_webview.erl:268
+msgid "Auto Save "
+msgstr ""
+
+#: mod_archive_webview.erl:270
+msgid "Modify"
+msgstr ""
+
+#: mod_archive_webview.erl:280
+msgid "Auto archive"
+msgstr ""
+
+#: mod_archive_webview.erl:280
+msgid "Expire"
+msgstr ""
+
+#: mod_archive_webview.erl:280
+msgid "JID"
+msgstr ""
+
+#: mod_archive_webview.erl:283
+msgid "Default"
+msgstr ""
+
+#: mod_archive_webview.erl:352
+msgid "With: "
+msgstr ""
+
+#: mod_archive_webview.erl:360
+msgid "From: "
+msgstr ""
+
+#: mod_archive_webview.erl:361 mod_archive_webview.erl:364
+msgid " (date in SQL format,  may be empty)"
+msgstr ""
+
+#: mod_archive_webview.erl:363
+msgid "To: "
+msgstr ""
+
+#: mod_archive_webview.erl:366
+msgid "Search keyword: "
+msgstr ""
+
+#: mod_archive_webview.erl:407
+msgid "No matches"
+msgstr ""
+
+#: mod_archive_webview.erl:419
+msgid "Previous"
+msgstr ""
+
+#: mod_archive_webview.erl:420
+msgid "Next"
+msgstr ""
diff --git a/mod_archive/priv/msgs/pl.mod_archive_webview.po b/mod_archive/priv/msgs/pl.mod_archive_webview.po
new file mode 100644
index 0000000..07c0ad6
--- /dev/null
+++ b/mod_archive/priv/msgs/pl.mod_archive_webview.po
@@ -0,0 +1,226 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: 2.1.x\n"
+"X-Language: Language Name\n"
+"Last-Translator: Andrzej Smyk\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: mod_archive_webview.erl:69 mod_archive_webview.erl:83
+#: mod_archive_webview.erl:171
+msgid "Config"
+msgstr "Konfiguracja"
+
+#: mod_archive_webview.erl:70 mod_archive_webview.erl:84
+msgid "Global Settings"
+msgstr "Ustawienia globalne"
+
+#: mod_archive_webview.erl:71 mod_archive_webview.erl:85
+msgid "Specific Contact Settings"
+msgstr "Specyficzne ustawienia kontaktu"
+
+#: mod_archive_webview.erl:72
+msgid "Advanced settings"
+msgstr "Ustawienia zaawansowane"
+
+#: mod_archive_webview.erl:86
+msgid "Simple settings"
+msgstr "Ustawienia podstawowe"
+
+#: mod_archive_webview.erl:93
+msgid "Contact List"
+msgstr "Lista kontaktów"
+
+#: mod_archive_webview.erl:102 mod_archive_webview.erl:112
+msgid "Chat with "
+msgstr "Rozmowa z "
+
+#: mod_archive_webview.erl:112
+msgid " : "
+msgstr " : "
+
+#: mod_archive_webview.erl:112
+msgid " on "
+msgstr " na "
+
+#: mod_archive_webview.erl:116
+msgid "Edit subject: "
+msgstr "Edycja tematu: "
+
+#: mod_archive_webview.erl:118
+msgid "Ok"
+msgstr ""
+
+#: mod_archive_webview.erl:120
+msgid "Do you realy want to delete this chat"
+msgstr "Czy na pewno chcesz usunąć ten chat"
+
+#: mod_archive_webview.erl:139 mod_archive_webview.erl:143
+#: mod_archive_webview.erl:173 mod_archive_webview.erl:369
+msgid "Search"
+msgstr "Szukaj"
+
+#: mod_archive_webview.erl:146
+msgid "Archives viewer"
+msgstr "Przeglądarka archiwów"
+
+#: mod_archive_webview.erl:149
+msgid "404 File not found"
+msgstr "404 Pliku nie znaleziono"
+
+#: mod_archive_webview.erl:159
+msgid " - ejabberd Web Archive Viewer"
+msgstr ""
+
+#: mod_archive_webview.erl:168
+msgid "Archives Viewer"
+msgstr "Przeglądarka Archiwów"
+
+#: mod_archive_webview.erl:172
+msgid "Browse"
+msgstr "Przeglądaj"
+
+#: mod_archive_webview.erl:235
+msgid "Disable or enable automatic archiving globaly: "
+msgstr "Włącz lub wyłącz automatyczne archiwizowanie globalnie: "
+
+#: mod_archive_webview.erl:236 mod_archive_webview.erl:239
+msgid "--Server Default--"
+msgstr "--Domyślne ustawienia serwera--"
+
+#: mod_archive_webview.erl:236 mod_archive_webview.erl:239
+#: mod_archive_webview.erl:256 mod_archive_webview.erl:269
+#: mod_archive_webview.erl:283
+msgid "Disabled"
+msgstr "Włączone"
+
+#: mod_archive_webview.erl:236 mod_archive_webview.erl:239
+#: mod_archive_webview.erl:256 mod_archive_webview.erl:269
+#: mod_archive_webview.erl:283
+msgid "Enabled"
+msgstr "Wyłączone"
+
+#: mod_archive_webview.erl:238
+msgid "Default for contact not specified bellow : "
+msgstr "Domyślnie dla kontaktu nie zaznaczonego poniżej : "
+
+#: mod_archive_webview.erl:240
+msgid "Default expiration time: "
+msgstr "Domyślny czas wygaśnięcia: "
+
+#: mod_archive_webview.erl:241
+msgid "(number of seconds before deleting message, '-1' = server default)"
+msgstr "(liczba sekund przed wykasowaniem wiadomości, '-1' = domyślne ustawienie serwera"
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:258
+msgid "--Undefined--"
+msgstr "--Niezdefiniowane--"
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:260
+msgid "Concede"
+msgstr "Ustępuje"
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:261
+msgid "Forbid"
+msgstr "Zabrania"
+
+#: mod_archive_webview.erl:254 mod_archive_webview.erl:263
+msgid "Prefer"
+msgstr "Preferuje"
+
+#: mod_archive_webview.erl:256 mod_archive_webview.erl:269
+msgid "--Default--"
+msgstr "--Domyślnie--"
+
+#: mod_archive_webview.erl:256
+msgid "Save: "
+msgstr "Zachowaj: "
+
+#: mod_archive_webview.erl:257
+msgid "Expire: "
+msgstr "Upływa: "
+
+#: mod_archive_webview.erl:258
+msgid "Otr: "
+msgstr ""
+
+#: mod_archive_webview.erl:259
+msgid "Approve"
+msgstr "Akceptuje"
+
+#: mod_archive_webview.erl:262
+msgid "Oppose"
+msgstr "Oponuje"
+
+#: mod_archive_webview.erl:264
+msgid "Require"
+msgstr "Wymaga"
+
+#: mod_archive_webview.erl:265
+msgid "Auto Method: "
+msgstr "Metoda automatyczna: "
+
+#: mod_archive_webview.erl:266
+msgid "Local Method: "
+msgstr "Metoda lokalna: "
+
+#: mod_archive_webview.erl:267
+msgid "Manual Method: "
+msgstr "Metoda manualna: "
+
+#: mod_archive_webview.erl:268
+msgid "Auto Save "
+msgstr "Automatyczne zachowanie "
+
+#: mod_archive_webview.erl:270
+msgid "Modify"
+msgstr "Modyfikuj"
+
+#: mod_archive_webview.erl:280
+msgid "Auto archive"
+msgstr "Autoarchiwizacja"
+
+#: mod_archive_webview.erl:280
+msgid "Expire"
+msgstr "Upływa"
+
+#: mod_archive_webview.erl:280
+msgid "JID"
+msgstr "JID"
+
+#: mod_archive_webview.erl:283
+msgid "Default"
+msgstr "Domyślnie"
+
+#: mod_archive_webview.erl:352
+msgid "With: "
+msgstr "Z: "
+
+#: mod_archive_webview.erl:360
+msgid "From: "
+msgstr "Od: "
+
+#: mod_archive_webview.erl:361 mod_archive_webview.erl:364
+msgid " (date in SQL format,  may be empty)"
+msgstr ""
+
+#: mod_archive_webview.erl:363
+msgid "To: "
+msgstr "Do: "
+
+#: mod_archive_webview.erl:366
+msgid "Search keyword: "
+msgstr "Szukaj słowa: "
+
+#: mod_archive_webview.erl:407
+msgid "No matches"
+msgstr "Brak wyników"
+
+#: mod_archive_webview.erl:419
+msgid "Previous"
+msgstr "Poprzednie"
+
+#: mod_archive_webview.erl:420
+msgid "Next"
+msgstr "Następne"
diff --git a/mod_archive/src/mod_archive.erl b/mod_archive/src/mod_archive.erl
new file mode 100644
index 0000000..f4c8dcf
--- /dev/null
+++ b/mod_archive/src/mod_archive.erl
@@ -0,0 +1,1076 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_archive.erl
+%%% Author  : Olivier Goffart  <ogoffar@kde.org>
+%%% Purpose : Message Archiving  (JEP-0136)
+%%% Created : 19 Aug 2006
+%%%----------------------------------------------------------------------
+
+%% Version 0.0.1  2006-08-19
+%% Version 0.0.2  2006-08-21
+%% Version 0.0.3  2006-08-22
+%% Version 0.0.4  2006-09-10  (RSM JEP-0059 v0.13   JEP-0136 v0.6 with RSM)
+
+
+%% Options:
+%%  save_default -> true | false      if messages are stored by default or not
+%%  session_duration ->  time in secondes before the timeout of a session
+
+
+-module(mod_archive).
+-author('ogoffart@kde.org').
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+-export([start_link/2, start/2, stop/1,
+	 remove_user/2, send_packet/3, receive_packet/4,
+         process_iq/3, process_local_iq/3,
+	 get_disco_features/5]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+	 terminate/2, code_change/3]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+
+-record(state, {host, storages, save_default, session_duration}).
+
+-define(PROCNAME, ejabberd_mod_archive).
+-define(NS_ARCHIVE,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns").
+-define(NS_ARCHIVE_MANAGE,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns-manage").
+-define(NS_ARCHIVE_PREF,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns-pref").
+-define(NS_ARCHIVE_MANUAL,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns-manual").
+-define(INFINITY, calendar:datetime_to_gregorian_seconds({{2038,1,19},{0,0,0}})).
+
+-define(MYDEBUG(Format, Args),
+	io:format("D(~p:~p:~p) : " ++ Format ++ "~n",
+		  [calendar:local_time(), ?MODULE, ?LINE] ++ Args)).
+
+
+%NOTE  i was not sure what format to adopt for archive_option.    otr_list  is unused
+-record(archive_options,
+	{us,
+	 default = unset,
+	 save_list = [],
+	 nosave_list = [],
+	 otr_list = []}).
+
+%-record(archive_options, {usj, us, jid, type, value}).
+
+-record(archive_message,
+	{usjs,
+	 us,
+	 jid,
+	 start,
+	 message_list = [],
+	 subject = ""}).
+
+-record(msg, {direction, secs, body}).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    ChildSpec =
+	{Proc,
+	 {?MODULE, start_link, [Host, Opts]},
+	 temporary,
+	 1000,
+	 worker,
+	 [?MODULE]},
+    supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:call(Proc, stop),
+    supervisor:delete_child(ejabberd_sup, Proc).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%%                         {ok, State, Timeout} |
+%%                         ignore               |
+%%                         {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Host, Opts]) ->
+    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+    SaveDefault = gen_mod:get_opt(save_default, Opts, false),
+    SessionDuration = gen_mod:get_opt(session_duration, Opts, 1300),
+    mnesia:create_table(archive_options,
+			[{disc_copies, [node()]},
+			 {attributes, record_info(fields, archive_options)}]),
+    mnesia:create_table(archive_message,
+			[{disc_copies, [node()]},
+			 {attributes, record_info(fields, archive_message)}]),
+%    mnesia:add_table_index(archive_options, us),
+    mnesia:add_table_index(archive_message, us),
+    ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
+    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ARCHIVE, ?MODULE, process_iq, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_ARCHIVE, ?MODULE, process_local_iq, IQDisc),
+    ejabberd_hooks:add(user_send_packet, Host, ?MODULE, send_packet, 90),
+    ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 90),
+    ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_disco_features, 99),
+    {ok, #state{host = Host,
+		storages = [],
+		save_default = SaveDefault,
+		session_duration = SessionDuration}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%%                                      {reply, Reply, State, Timeout} |
+%%                                      {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, Reply, State} |
+%%                                      {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call(get_save_default, _From, State) ->
+    {reply, State#state.save_default, State};
+handle_call(stop, _From, State) ->
+    {stop, normal, ok, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({addlog, Direction, LUser, LServer, JID, P}, State) ->
+    Storages = State#state.storages,
+    NewStorages =
+	case should_store_jid(LUser, LServer, JID,
+			      State#state.save_default) of
+	    false ->
+		Storages;
+	    true ->
+		case parse_message(P) of
+		    ignore ->
+			Storages;
+		    Body ->
+			catch do_log(Storages, LUser, LServer, JID,
+				     Direction, Body,
+				     State#state.session_duration)
+		end
+	end,
+    {noreply, State#state{storages = NewStorages}};
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%%                                       {noreply, State, Timeout} |
+%%                                       {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, State) ->
+    Host = State#state.host,
+    ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_ARCHIVE),
+    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ARCHIVE),
+    ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, send_packet, 90),
+    ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 90),
+    ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_disco_features, 99),
+    ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%% Workaround the fact that if the client send <iq type='get'>
+%% it end up like <iq type='get' from='u@h' to = 'u@h'>
+process_iq(From, To, IQ) ->
+    #iq{sub_el = SubEl} = IQ,
+    #jid{lserver = LServer, luser = LUser} = To,
+    #jid{luser = FromUser} = From,
+    case {LUser, LServer, lists:member(LServer, ?MYHOSTS)} of
+	{FromUser, _, true} ->
+	    process_local_iq(From, To, IQ);
+	{"", _, true} ->
+	    process_local_iq(From, To, IQ);
+	{"", "", _} ->
+	    process_local_iq(From, To, IQ);
+	_ ->
+	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+    end.
+
+process_local_iq(From, To, #iq{sub_el = SubEl} = IQ) ->
+    case lists:member(From#jid.lserver, ?MYHOSTS) of
+	false ->
+	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+	true ->
+	    {xmlelement, Name, _Attrs, _Els} = SubEl,
+	    case Name of
+		"pref" -> process_local_iq_save(From, To, IQ);
+		"auto" -> process_local_iq_auto(From, To, IQ);
+		%%"otr" -> process_local_iq_otr(From, To, IQ);
+		"list" -> process_local_iq_list(From, To, IQ);
+		"retrieve" -> process_local_iq_retrieve(From, To, IQ);
+		"save" -> process_local_iq_store(From, To, IQ);
+		"remove" -> process_local_iq_remove(From, To, IQ);
+		_ -> IQ#iq{type = error,
+			   sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
+            end
+    end.
+
+
+remove_user(User, Server) ->
+    LUser = jlib:nodeprep(User),
+    LServer = jlib:nameprep(Server),
+    US = {LUser, LServer},
+    F = fun() ->
+		lists:foreach(
+		  fun(R) ->
+			  mnesia:delete_object(R)
+		  end,
+		  mnesia:index_read(archive_message, US, #archive_message.us)),
+		mnesia:delete({archive_options, US})
+        end,
+    mnesia:transaction(F).
+
+get_disco_features(Acc, _From, _To, "", _Lang) ->
+    Features =
+	case Acc of
+	    {result, I} -> I;
+	    _ -> []
+	end,
+    {result, Features ++ [?NS_ARCHIVE_MANAGE,
+			  ?NS_ARCHIVE_PREF,
+			  ?NS_ARCHIVE_MANUAL]};
+
+get_disco_features(Acc, _From, _To, _Node, _Lang) ->
+    Acc.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3 Automated archiving
+%%
+
+send_packet(From, To, P) ->
+    Host = From#jid.lserver,
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:cast(Proc, {addlog, to, From#jid.luser, Host, To, P}).
+
+receive_packet(_JID, From, To, P) ->
+    Host = To#jid.lserver,
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:cast(Proc, {addlog, from, To#jid.luser, Host, From, P}).
+
+
+% parce a message and return the body string if sicessfull,  return  ignore if the message should not be stored
+parse_message({xmlelement, "message", _, _} = Packet) ->
+    case xml:get_subtag(Packet, "body") of
+         false ->  ignore;
+         Body_xml ->
+            case xml:get_tag_attr_s("type", Packet) of
+                "groupchat" -> ignore;
+                _ ->  xml:get_tag_cdata(Body_xml)
+             end
+    end;
+parse_message(_) -> ignore.
+
+% archive the message Body    return the list of new Storages
+%  Storages:  a list of open storages key (usjs)
+%  LUser, LServer :  the local user's information
+%  Jid : the contact's jid
+%  Body : the message body
+do_log(Storages, LUser, LServer, Jid, Direction, Body, Session_Duration) ->
+    NStorages = smart_find_storage(LUser, LServer, Jid, Storages, get_timestamp() + Session_Duration),
+    [{Tm, {_, _, _, Start} = Key} | _] = NStorages,
+    Message = #msg{direction=Direction, secs=(Tm-Start), body = Body},
+    mnesia:transaction(fun() ->
+        NE = case mnesia:read({archive_message, Key}) of
+                [] ->
+                    #archive_message{usjs= Key,
+                                      us = {LUser, LServer},
+                                      jid = jlib:jid_tolower(jlib:jid_remove_resource(Jid)),
+                                      start = Tm,
+                                      message_list = [Message]};
+                [E] -> E#archive_message{message_list=lists:append(E#archive_message.message_list, [Message])}
+          end,
+          mnesia:write(NE)
+        end),
+    NStorages.
+
+%find a storage for Jid and move it on the begin on the storage list,  if none are found, a new storage is created, old storages element are removed
+smart_find_storage(LUser, LServer, Jid, [C | Tail], TimeStampLimit) ->
+    NGid=jlib:jid_remove_resource(jlib:jid_tolower(Jid)),
+    case C of
+        {_, {LUser, LServer, NGid, _} = St} ->
+                [{get_timestamp(), St} | Tail];
+        {Tm, _}  ->
+            if  Tm > TimeStampLimit ->
+                    smart_find_storage(LUser, LServer, Jid, [], TimeStampLimit);
+                true ->
+                    [UJ | NT] = smart_find_storage(LUser, LServer, Jid, Tail, TimeStampLimit),
+                    [UJ | [C | NT]]
+            end
+    end;
+
+
+smart_find_storage(LUser, LServer, Jid, [], _Limit) ->
+    Tm = get_timestamp(),
+    [{Tm, {LUser, LServer, jlib:jid_tolower(jlib:jid_remove_resource(Jid)), Tm}}].
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3.1 Preferences
+%%
+
+process_local_iq_save(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    Result = case Type of
+        set ->
+            {xmlelement, _Name, _Attrs, Els} = SubEl,
+            process_save_set(From#jid.luser, From#jid.lserver, Els);
+        get ->
+            process_save_get(From#jid.luser, From#jid.lserver)
+        end,
+    case Result of
+        {result, R} ->
+            IQ#iq{type = result, sub_el = [R]};
+        ok ->
+            broadcast_iq(From, IQ#iq{type = set, sub_el=[SubEl]}),
+            IQ#iq{type = result, sub_el = []};
+        {error, E} ->
+            IQ#iq{type = error, sub_el = [SubEl, E]};
+        _ ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+    end.
+
+
+
+
+% return {error, xmlelement} or {result, xmlelement}
+process_save_get(LUser, LServer) ->
+    case catch mnesia:dirty_read(archive_options, {LUser, LServer}) of
+        {'EXIT', _Reason} ->
+            {error, ?ERR_INTERNAL_SERVER_ERROR};
+        [] ->
+            {result,
+	     {xmlelement, "pref", [{"xmlns", ?NS_ARCHIVE}],
+	      default_element(LServer)}};
+        [#archive_options{default = Default,
+			  save_list = SaveList,
+			  nosave_list = NoSaveList}] ->
+            LItems = lists:append(
+		       lists:map(fun(J) ->
+					 {xmlelement, "item",
+					  [{"jid", jlib:jid_to_string(J)},
+					   {"save","body"}],
+					  []}
+				 end, SaveList),
+		       lists:map(fun(J) ->
+					 {xmlelement, "item",
+					  [{"jid", jlib:jid_to_string(J)},
+					   {"save","false"}],
+					  []}
+				 end, NoSaveList)),
+            DItem = case Default of
+                        true ->			% TODO: <auto/>
+                            [{xmlelement, "default", [{"save", "body"}], []}];
+                        false ->
+                            [{xmlelement, "default", [{"save", "false"}], []}];
+                        _ ->
+                            default_element(LServer)
+                    end,
+            {result, {xmlelement, "save", [{"xmlns", ?NS_ARCHIVE}], DItem ++ LItems}}
+    end.
+
+%return the <default .../> element
+default_element(Host) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    AutoSave = gen_server:call(Proc, get_save_default),
+    SaveAttr = if
+		   AutoSave -> "true";
+		   true -> "false"
+	       end,
+    [{xmlelement, "default", [{"save", "false"}, {"otr", "forbid"}], []},
+     {xmlelement, "auto", [{"save", SaveAttr}], []}].
+
+
+% return {error, xmlelement} or {result, xmlelement} or  ok
+process_save_set(LUser, LServer, Elms) ->
+    F = fun() ->
+		NE = case mnesia:read({archive_options, {LUser, LServer}}) of
+			 [] ->
+			     #archive_options{us = {LUser, LServer}};
+			 [E] ->
+			     E
+		     end,
+		SNE = transaction_parse_save_elem(NE, Elms),
+		case SNE of
+		    {error, _} -> SNE;
+		    _ -> mnesia:write(SNE)
+		end
+	end,
+    case mnesia:transaction(F) of
+        {atomic, {error, _} = Error} ->
+            Error;
+        {atomic, _} ->
+            ok;
+        _ ->
+	    {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+
+% transaction_parse_save_elem(archive_options, ListOfXmlElement) -> #archive_options
+%  parse the list of xml element, and modify the given archive_option
+transaction_parse_save_elem(Options, [{xmlelement, "default", Attrs, _} | Tail]) ->
+    V = case xml:get_attr_s("save", Attrs) of
+            "true" -> true;
+            "false" -> false;
+            _ -> unset
+        end,
+    transaction_parse_save_elem(Options#archive_options{default = V}, Tail);
+
+transaction_parse_save_elem(Options, [{xmlelement, "item", Attrs, _}  | Tail]) ->
+    case jlib:string_to_jid(xml:get_attr_s("jid", Attrs)) of
+        error -> {error, ?ERR_JID_MALFORMED};
+        #jid{luser = LUser, lserver = LServer, lresource = LResource} ->
+            JID = {LUser, LServer, LResource},
+            case xml:get_attr_s("save", Attrs) of
+                "body" ->
+                    transaction_parse_save_elem(
+		      Options#archive_options{
+			save_list = [JID | lists:delete(JID, Options#archive_options.save_list)],
+			nosave_list = lists:delete(JID, Options#archive_options.nosave_list)
+		       }, Tail);
+                "false" ->
+                    transaction_parse_save_elem(
+		      Options#archive_options{
+			save_list = lists:delete(JID, Options#archive_options.save_list),
+			nosave_list = [JID | lists:delete(JID, Options#archive_options.nosave_list)]
+		       }, Tail);
+                _ ->
+                    transaction_parse_save_elem(
+		      Options#archive_options{
+			save_list = lists:delete(JID, Options#archive_options.save_list),
+			nosave_list = lists:delete(JID, Options#archive_options.nosave_list)
+		       }, Tail)
+	    end
+    end;
+
+transaction_parse_save_elem(Options, []) ->  Options;
+transaction_parse_save_elem(Options, [_ | Tail]) ->
+    transaction_parse_save_elem(Options,  Tail).
+
+
+broadcast_iq(#jid{luser = User, lserver = Server}, IQ) ->
+    Fun = fun(Resource) ->
+        ejabberd_router:route(
+                    jlib:make_jid("", Server, ""),
+                    jlib:make_jid(User, Server, Resource),
+                    jlib:iq_to_xml(IQ#iq{id="push"}))
+        end,
+    lists:foreach(Fun, ejabberd_sm:get_user_resources(User,Server)).
+
+
+
+process_local_iq_auto(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    Result =
+	case Type of
+	    set ->
+		{xmlelement, _Name, Attrs, _Els} = SubEl,
+		Auto = case xml:get_attr_s("save", Attrs) of
+			   "true" -> true;
+			   "false" -> false;
+			   _ -> unset
+		       end,
+		case Auto of
+		    unset ->
+			{error, ?ERR_BAD_REQUEST};
+		    _ ->
+			LUser = From#jid.luser,
+			LServer = From#jid.lserver,
+			F = fun() ->
+				    Opts =
+					case mnesia:read({archive_options,
+							   {LUser, LServer}}) of
+					     [] ->
+						 #archive_options{us = {LUser, LServer}};
+					     [E] ->
+						 E
+					 end,
+				    mnesia:write(Opts#archive_options{
+						   default = Auto})
+			    end,
+			mnesia:transaction(F),
+			{result, []}
+		end;
+	    get ->
+		{error, ?ERR_BAD_REQUEST}
+        end,
+    case Result of
+        {result, R} ->
+            IQ#iq{type = result, sub_el = R};
+        {error, E} ->
+            IQ#iq{type = error, sub_el = [SubEl, E]};
+        _ ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3.1 Off-the-Record Mode
+%%
+
+%TODO
+
+
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Utility function
+
+% Return true if LUser@LServer should log message for the contact JID
+should_store_jid(LUser, LServer, Jid, Service_Default) ->
+    case catch mnesia:dirty_read(archive_options, {LUser, LServer}) of
+        [#archive_options{default = Default, save_list = Save_list, nosave_list = Nosave_list}] ->
+            Jid_t = jlib:jid_tolower(Jid),
+            Jid_b = jlib:jid_remove_resource(Jid_t),
+            A = lists:member(Jid_t, Save_list),
+            B = lists:member(Jid_t, Nosave_list),
+            C = lists:member(Jid_b, Save_list),
+            D = lists:member(Jid_b, Nosave_list),
+            if  A -> true;
+                B -> false;
+                C -> true;
+                D -> false;
+                Default == true -> true;
+                Default == false -> false;
+                true -> Service_Default
+            end;
+        _ -> Service_Default
+    end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   4. Manual Archiving
+%%
+
+
+process_local_iq_store(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        get ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        set ->
+            case parse_store_element (LUser, LServer, SubEl) of
+                {error, E} ->  IQ#iq{type = error, sub_el = [SubEl, E]};
+                Collection ->
+                     case mnesia:transaction(fun() -> mnesia:write(Collection) end) of
+                        {atomic, _} ->
+                            IQ#iq{type = result, sub_el=[]};
+                        _ ->
+                            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+                        end
+            end
+    end.
+
+% return a #archive_message   StoreElem is an xmlelement, or return  {error, E}
+parse_store_element(LUser, LServer,
+		    {xmlelement, "save", _ChatAttrs, ChatSubEls}) ->
+    case xml:remove_cdata(ChatSubEls) of
+	[{xmlelement, "chat", Attrs, SubEls}] ->
+	    case index_from_argument(LUser, LServer, Attrs)  of
+		{error, E} -> {error, E};
+		{LUser, LServer, Jid, Start} = Index ->
+		    Messages = parse_store_element_sub(SubEls),
+		    #archive_message{usjs = Index,
+				     us = {LUser, LServer},
+				     jid = Jid,
+				     start = Start,
+				     subject = xml:get_attr_s("subject", Attrs),
+				     message_list = Messages}
+	    end;
+	_ ->
+	    {error, ?ERR_BAD_REQUEST}
+    end.
+
+% TODO: utc attribute, catch list_to_integer errors
+
+parse_store_element_sub([{xmlelement, Dir, _, _}  = E | Tail]) ->
+    [#msg{direction = list_to_atom(Dir),
+	  secs = list_to_integer(xml:get_tag_attr_s("secs", E)),
+	  body = xml:get_tag_cdata(xml:get_subtag(E,"body"))} |
+     parse_store_element_sub(Tail)];
+
+parse_store_element_sub([]) -> [];
+parse_store_element_sub([_ | Tail]) -> parse_store_element_sub(Tail).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   5. Archive Management
+%%
+
+
+process_local_iq_list(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        set ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        get ->
+            {xmlelement, _, Attrs, SubEls} = SubEl,
+            RSM = parse_rsm(SubEls),
+            ?MYDEBUG("RSM Results: ~p ~n", [RSM]),
+            Result = case parse_root_argument(Attrs) of
+			 {error, E} -> {error, E};
+			 {interval, Start, Stop} ->
+			     get_list(LUser, LServer, Start, Stop, '_');
+			 {interval, Start, Stop, Jid} ->
+			     get_list(LUser, LServer, Start, Stop, Jid);
+			 {index, Jid, Start} ->
+			     get_list(LUser, LServer, Start, infinity, Jid);
+			 _ -> {error, ?ERR_BAD_REQUEST}
+		     end,
+            case Result of
+                {ok, Items} ->
+                    FunId = fun(El) -> ?MYDEBUG("FunId  ~p  ~n", [El]),  integer_to_list(element(5,El)) end,
+                    FunCompare = fun(Id, El) ->
+					 Id2 = list_to_integer(FunId(El)),
+					 Id1 = list_to_integer(Id),
+					 if Id1 == Id2 -> equal;
+					    Id1 > Id2 -> greater;
+					    Id1 < Id2 -> smaller
+					 end
+				 end,
+                    case catch execute_rsm(RSM, lists:keysort(5, Items), FunId,FunCompare)  of
+                        {error, R} ->
+                            IQ#iq{type = error, sub_el = [SubEl, R]};
+                        {'EXIT', Errr} ->
+                            ?MYDEBUG("INTERNAL ERROR  ~p  ~n", [Errr]),
+                            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+                        {RSM_Elem, Items2} ->
+                            Zero = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+                            Fun = fun(A) ->
+					  Seconds= A#archive_message.start - Zero,
+					  Start2 =  jlib:now_to_utc_string({Seconds div 1000000, Seconds rem 1000000, 0}),
+					  Args0 = [{"with", jlib:jid_to_string(A#archive_message.jid)}, {"start", Start2}],
+					  Args = case  A#archive_message.subject of
+						     "" -> Args0;
+						     Subject -> [{"subject",Subject} | Args0]
+						 end,
+					  {xmlelement, "chat", Args, []}
+				  end,
+                            IQ#iq{type = result, sub_el = [{xmlelement, "list", [{"xmlns", ?NS_ARCHIVE}], lists:append(lists:map(Fun, Items2),[RSM_Elem])}]}
+                    end;
+                {error, R} ->
+                    IQ#iq{type = error, sub_el = [SubEl, R]}
+            end
+    end.
+
+
+
+
+process_local_iq_retrieve(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        set ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        get ->
+            {xmlelement, _, Attrs, SubEls} = SubEl,
+            RSM = parse_rsm(SubEls),
+            case index_from_argument(LUser, LServer, Attrs)  of
+                {error, E} ->
+		    IQ#iq{type = error, sub_el = [SubEl, E]};
+                Index ->
+                    case retrieve_collection(Index, RSM) of
+			{error, Err} ->
+			    IQ#iq{type = error, sub_el = [SubEl, Err]};
+			Store ->
+			    IQ#iq{type = result, sub_el = [Store]}
+                    end
+                end
+        end.
+
+
+retrieve_collection(Index, RSM) ->
+    case get_collection(Index) of
+        {error, E} ->
+            {error, E};
+        A ->
+            Zero = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+            Seconds = A#archive_message.start-Zero,
+            Start2 =  jlib:now_to_utc_string({Seconds div 1000000, Seconds rem 1000000, 0}),
+            Args0 = [{"xmlns", ?NS_ARCHIVE}, {"with", jlib:jid_to_string(A#archive_message.jid)}, {"start", Start2}],
+            Args = case A#archive_message.subject of
+		       "" -> Args0;
+		       Subject -> [{"subject", Subject} | Args0]
+		   end,
+            case catch execute_rsm(RSM, A#archive_message.message_list, index, index)  of
+                {error, R} ->
+                    {error, R};
+                {'EXIT', _} ->
+                    {error, ?ERR_INTERNAL_SERVER_ERROR};
+                {RSM_Elem, Items} ->
+                    Format_Fun =
+			fun(Elem) ->
+				{xmlelement,  atom_to_list(Elem#msg.direction),
+				 [{"secs", integer_to_list(Elem#msg.secs)}],
+				 [{xmlelement, "body", [], [{xmlcdata,  Elem#msg.body}]}]}
+			end,
+                    {xmlelement, "chat", Args, lists:append(lists:map(Format_Fun, Items), [RSM_Elem])}
+            end
+    end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   5.3 Removing a Collection
+%%
+
+
+process_local_iq_remove(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        get ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        set ->
+            {xmlelement, _,  Attrs, _} = SubEl,
+            Result = case parse_root_argument(Attrs) of
+                {error, E} ->  IQ#iq{type = error, sub_el = [SubEl, E]};
+                {interval, Start, Stop} -> process_remove_interval(LUser, LServer, Start, Stop, '_');
+                {interval, Start, Stop, Jid} -> process_remove_interval(LUser, LServer, Start, Stop, Jid);
+                {index, Jid, Start} -> process_remove_index({LUser, LServer, Jid, Start})
+            end,
+            case Result of
+                {error, Ee} ->  IQ#iq{type = error, sub_el = [SubEl, Ee]};
+                ok -> IQ#iq{type = result, sub_el=[]}
+            end
+        end.
+
+process_remove_index(Index) ->
+    case mnesia:transaction(fun() -> mnesia:delete({archive_message, Index})  end) of
+         {atomic, _} ->
+            ok;
+         {aborted, _} ->
+           {error, ?ERR_ITEM_NOT_FOUND}
+    end.
+
+process_remove_interval(LUser, LServer, Start, End, With) ->
+    Fun = fun() ->
+                Pat = #archive_message{usjs= '_',  us = {LUser, LServer}, jid= With,
+                                    start='$1', message_list='_', subject = '_'},
+                Guard = [{'>=', '$1', Start},{'<', '$1', End}],
+
+                lists:foreach(fun(R) ->  mnesia:delete_object(R)    end,
+                     mnesia:select(archive_message, [{Pat, Guard, ['$_']}]))
+            end,
+
+    case mnesia:transaction(Fun) of
+        {atomic, _} ->
+            ok;
+        {aborted, _} ->
+            {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+
+
+% return {ok, [{#archive_message}]} or {error, xmlelement}
+% With is a tuple Jid,  or '_'
+get_list(LUser, LServer, Start, End, With) ->
+    case mnesia:transaction(fun() ->
+                               Pat = #archive_message{usjs= '_',  us = {LUser, LServer}, jid= With,
+                                                        start='$1', message_list='_', subject = '_'},
+                               Guard = [{'>=', '$1', Start},{'<', '$1', End}],
+                                mnesia:select(archive_message, [{Pat, Guard, ['$_']}])
+                            end) of
+         {atomic, Result} ->
+                  {ok, Result};
+         {aborted, _} ->
+           {error, ?ERRT_INTERNAL_SERVER_ERROR("",  "plop")}
+    end.
+
+
+% Index is  {LUser, LServer, With, Start}
+get_collection(Index) ->
+    case catch mnesia:dirty_read(archive_message, Index) of
+        {'EXIT', _Reason} ->
+            {error, ?ERR_INTERNAL_SERVER_ERROR};
+        [] ->
+            {error, ?ERR_ITEM_NOT_FOUND};
+        [C] -> C
+    end.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Utility
+
+
+% return either  {error, Err}  or {LUser, LServer, Jid, Start}
+index_from_argument(LUser, LServer,  Attrs) ->
+    case parse_root_argument(Attrs) of
+        {error, E} ->  {error, E};
+        {index, Jid, Start} -> {LUser, LServer, Jid, Start};
+        _ -> {error, ?ERR_BAD_REQUEST}
+    end.
+
+%parse commons arguments of root elements
+parse_root_argument(Attrs) ->
+    case parse_root_argument_aux(Attrs, {undefined, undefined, undefined}) of
+        {error, E} -> {error, E};
+        {{ok,Jid}, {ok,Start}, undefined}   ->  {index, Jid, Start};
+        {{ok,Jid}, undefined, undefined}   ->  {interval, 0, ?INFINITY, Jid};
+        {{ok,Jid}, {ok,Start}, {ok,Stop}}   ->  {interval, Start, Stop, Jid};
+        {undefined, {ok,Start}, {ok,Stop}}   ->  {interval, Start, Stop};
+        {undefined, undefined, undefined}   ->  {interval, 0, ?INFINITY};
+        _ -> {error,  ?ERR_BAD_REQUEST}
+    end.
+
+parse_root_argument_aux([{"with", JidStr} | Tail], {_, AS, AE}) ->
+    case jlib:string_to_jid(JidStr) of
+        error -> {error, ?ERR_JID_MALFORMED};
+        JidS ->
+            Jid = jlib:jid_tolower(JidS),
+            parse_root_argument_aux(Tail, {{ok, Jid}, AS, AE})
+    end;
+parse_root_argument_aux([{"start", Str} | Tail], {AW, _, AE}) ->
+    case jlib:datetime_string_to_timestamp(Str) of
+        undefined -> {error, ?ERR_BAD_REQUEST};
+        No ->
+            Val = calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(No)),
+            parse_root_argument_aux(Tail, {AW, {ok, Val}, AE})
+    end;
+parse_root_argument_aux([{"end", Str} | Tail], {AW, AS, _}) ->
+    case jlib:datetime_string_to_timestamp(Str) of
+        undefined -> {error, ?ERR_BAD_REQUEST};
+        No ->
+            Val = calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(No)),
+            parse_root_argument_aux(Tail, {AW, AS, {ok, Val}})
+    end;
+parse_root_argument_aux([_ | Tail], A) ->
+    parse_root_argument_aux(Tail, A);
+parse_root_argument_aux([], A) ->  A.
+
+
+
+get_timestamp() ->
+    calendar:datetime_to_gregorian_seconds(calendar:universal_time()).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%  Result Set Management (JEP-0059)
+%
+% USAGE:
+%   RSM = parce_rsm(Xmlelement)
+%   case execute_rsm(RSM, List,  GetIdFun, IdCompareFun) of
+%      {error, E} -> ....;
+%      {RSMElement, Items} ->
+%           SubElements = lists:append(lists:map(Format_Fun, Items), [RSMElement]),
+%           ...;
+%   end
+
+-define(MY_NS_RSM, "http://jabber.org/protocol/rsm").
+
+
+
+
+%  {Start, Max, Order} | error |  none  % | count
+
+parse_rsm([A | Tail]) ->
+    ?MYDEBUG("parse RSM elem ~p ", [A]),
+    case A of
+        {xmlelement, _,  Attrs1, _} ->
+            case xml:get_attr_s("xmlns", Attrs1) of
+                ?MY_NS_RSM ->
+                    parse_rsm(A);
+                HEPO ->
+                    ?MYDEBUG("HEPO ~p ", [HEPO]),
+                    parse_rsm(Tail)
+            end;
+        _ ->
+            parse_rsm(Tail)
+    end;
+parse_rsm([]) ->
+    none;
+
+% parse_rsm({xmlelement, "count", _, _}) ->
+%     count;
+
+parse_rsm({xmlelement, "set", _, SubEls}) ->
+    parse_rsm_aux(SubEls, {0, infinity, normal});
+
+parse_rsm(_) ->
+    error.
+
+parse_rsm_aux([{xmlelement, "max", _Attrs, Contents} | Tail], Acc) ->
+    case catch list_to_integer(xml:get_cdata(Contents)) of
+        P when is_integer(P) ->
+            case Acc of
+                {Start, infinity, Order} ->
+                    parse_rsm_aux(Tail, {Start, P, Order});
+                _ ->
+                    error
+            end;
+        HEPO ->
+            ?MYDEBUG("<max> Not an INTEGER ~p ", [HEPO]),
+            error
+    end;
+
+parse_rsm_aux([{xmlelement, "index", _Attrs, Contents} | Tail], Acc) ->
+    case catch list_to_integer(xml:get_cdata(Contents)) of
+        P when is_integer(P) ->
+            case Acc of
+                {0, Max, normal} ->
+                    parse_rsm_aux(Tail, {P, Max, normal});
+                _ ->
+                    error
+            end;
+        _ ->
+            error
+    end;
+
+parse_rsm_aux([{xmlelement, "after", _Attrs, Contents} | Tail], Acc) ->
+    case Acc of
+        {0, Max, normal} ->
+            parse_rsm_aux(Tail, {{id, xml:get_cdata(Contents)}, Max, normal});
+        _ ->
+            error
+    end;
+
+
+parse_rsm_aux([{xmlelement, "before", _Attrs, Contents} | Tail], Acc) ->
+    case Acc of
+        {0, Max, normal} ->
+            case xml:get_cdata(Contents) of
+                [] ->
+                    parse_rsm_aux(Tail, {0, Max, reversed});
+                CD ->
+                    parse_rsm_aux(Tail, {{id, CD}, Max, reversed})
+            end;
+        _ ->
+            error
+    end;
+
+parse_rsm_aux([_ | Tail], Acc) ->
+    parse_rsm_aux(Tail, Acc);
+parse_rsm_aux([], Acc) ->
+    Acc.
+
+%  RSM = {Start, Max, Order}
+%  GetId = fun(Elem) -> Id
+%  IdCompare = fun(Id, Elem) -> equal | greater | smaller
+%
+%  ->  {RSMElement, List} | {error, ErrElement}
+
+execute_rsm(RSM, List, GetId, IdCompare) ->
+    ?MYDEBUG("execute_rsm RSM ~p  ~n", [RSM]),
+    case execute_rsm_aux(RSM, List, IdCompare, 0) of
+        none ->
+            {{xmlcdata, ""}, List};
+%         count ->
+%             {{xmlelement, "count", [{"xmlns", ?MY_NS_RSM}], [{xmlcdata,  integer_to_list(length(List))}]}, []};
+        {error, E} ->
+            {error, E};
+        {_, []} ->
+              {{xmlelement, "set", [{"xmlns", ?MY_NS_RSM}], [{xmlelement, "count", [], [{xmlcdata,  integer_to_list(length(List))}]}]}, []};
+        {Index, L} ->
+            case GetId of
+                index ->
+                    {make_rsm(Index, integer_to_list(Index), integer_to_list(Index+length(L)),length(List)), L};
+                _ ->
+                    {make_rsm(Index, GetId(hd(L)), GetId(lists:last(L)),length(List)), L}
+            end
+    end.
+
+
+% execute_rsm_aux(count, _List, _, _) ->
+%      count;
+
+execute_rsm_aux(none, _List, _, _) ->
+    none;
+
+execute_rsm_aux(error, _List, _, _) ->
+    {error, ?ERR_BAD_REQUEST};
+
+execute_rsm_aux({S, M, reversed}, List, IdFun, Acc) ->
+    {NewFun,NewS} = case IdFun of
+        index ->
+            {index,
+                case S of
+                    {id, IdentIndex} ->
+                        integer_to_list(length(List) - list_to_integer(IdentIndex));
+                    _ -> S
+                end};
+        _ ->
+            {fun(Index, Elem) ->
+                    case IdFun(Index, Elem) of
+                        equal -> equal;
+                        greater -> smaller;
+                        smaller -> greater;
+                        O -> O
+                    end
+                end,
+               S}
+        end,
+    {Index, L2} =  execute_rsm_aux({NewS,M,normal}, lists:reverse(List), NewFun, 0),
+    {Acc + length(List) - Index - length(L2), lists:reverse(L2)};
+
+execute_rsm_aux({{id,I}, M, normal}, List,  index,  Acc) ->
+    execute_rsm_aux({list_to_integer(I), M, normal}, List,  index,  Acc);
+
+execute_rsm_aux({{id,I}, M, normal} = RSM, [E | Tail],  IdFun,  Acc) ->
+    case IdFun(I, E) of
+        smaller ->
+            execute_rsm_aux(RSM, Tail,  IdFun,  Acc + 1);
+         _ ->
+            execute_rsm_aux({0, M, normal}, [E | Tail],  IdFun, Acc)
+    end;
+
+execute_rsm_aux({{id,_}, _, normal}, [], _, Acc) ->
+    {Acc, []};
+
+execute_rsm_aux({0, infinity, normal}, List, _, Acc) ->
+    {Acc, List};
+
+execute_rsm_aux({_, 0, _}, _, _, Acc) ->
+    {Acc, []};
+
+execute_rsm_aux({S, M, _}, List, _, Acc)  when  is_integer(S) and is_integer(M) ->
+    ?MYDEBUG("execute_rsm_aux  sublist  ~p  ~n", [{S,M,List,Acc}]),
+    {Acc + S, lists:sublist(List, S+1,M)}.
+
+make_rsm(FirstIndex, FirstId, LastId, Count) ->
+    {xmlelement, "set", [{"xmlns", ?MY_NS_RSM}], [
+        {xmlelement, "first", [{"index", integer_to_list(FirstIndex)}], [{xmlcdata,  FirstId}]},
+        {xmlelement, "last", [], [{xmlcdata,  LastId}]},
+        {xmlelement, "count", [], [{xmlcdata,  integer_to_list(Count)}]}]}.
diff --git a/mod_archive/src/mod_archive_odbc.erl b/mod_archive/src/mod_archive_odbc.erl
new file mode 100644
index 0000000..c91784f
--- /dev/null
+++ b/mod_archive/src/mod_archive_odbc.erl
@@ -0,0 +1,2571 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_archive_odbc.erl
+%%% Author  : Olivier Goffart <ogoffar@kde.org> (origial mnesia version),
+%%%           Alexey Shchepin <alexey@process-one.net> (PostgreSQL version),
+%%%           Alexander Tsvyashchenko <ejabberd@ndl.kiev.ua> (ODBC version)
+%%% Purpose : Message Archiving using SQL DB (JEP-0136)
+%%% Created : 19 Aug 2006 by Olivier Goffart <ogoffar@kde.org>
+%%% Version : 1.0.1
+%%% Id      : $Id$
+%%%----------------------------------------------------------------------
+
+%%%----------------------------------------------------------------------
+
+%% Options:
+%%
+%% default_auto_save -> true | false - is auto-save turned on by default or not;
+%%     if true, default 'save' attribute will be set to 'body'.
+%%
+%% enforce_default_auto_save -> true | false - is auto-save default mode
+%%     enforced or not; if true, requests to change it are discarded.
+%%
+%%  default_expire -> default time in seconds before collections are wiped out -
+%%      or infinity atom.
+%%
+%%  enforce_min_expire -> minimal time in seconds before collections are wiped out
+%%      that the user is allowed to set - or infinity atom.
+%%
+%%  enforce_max_expire -> maximal time in seconds before collections are wiped out
+%%      that the user is allowed to set - or infinity atom.
+%%
+%%  replication_expire -> time in seconds before 'removed' replication
+%%      information if wiped out or infinity atom to disable.
+%%
+%%  session_duration -> time in secondes before the timeout of a session.
+%%
+%%  wipeout_interval -> time in seconds between wipeout runs or infinity atom
+%%                      to disable.
+%%
+%%
+%% Please note that according to XEP-136 only the following auto_save
+%% combinations are valid:
+%%
+%% 1) default_auto_save = true, enforce_default_auto_save = true
+%% 2) default_auto_save = false, enforce_default_auto_save = false
+%%
+%% Implementation will happily work with any combination of these,
+%% though - for example, for personal ejabberd server, until all clients
+%% support XEP-136, combination default_auto_save = true,
+%% enforce_default_auto_save = false is quite logical, while for some
+%% public ejabberd server with lots of users and shortage of disk space
+%% default_auto_save = false, enforce_default_auto_save = true might be
+%% desirable.
+%%
+%% Default values:
+%% - default_auto_save = false
+%% - enforce_default_auto_save = false
+%% - default_expire = infinity
+%% - enforce_min_expire = 0
+%% - enforce_max_expire = infinity
+%% - replication_expire = 31536000 (= 1 year)
+%% - session_duration = 1800
+%% - wipeout_interval = 86400 (= 1 day)
+
+
+-module(mod_archive_odbc).
+-author('ogoffart@kde.org').
+-author('alexey@process-one.net').
+-author('ejabberd@ndl.kiev.ua').
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+-export([start_link/2, start/2, stop/1,
+         remove_user/2,
+         send_packet/3,
+         receive_packet/3,
+         receive_packet/4,
+         process_iq/3, process_local_iq/3,
+         get_disco_features/5]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+         terminate/2, code_change/3]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+
+-record(state, {host,
+                sessions,
+                session_duration}).
+
+-define(PROCNAME, ejabberd_mod_archive_odbc).
+-define(NS_ARCHIVE,
+        "http://www.xmpp.org/extensions/xep-0136.html#ns").
+-define(NS_ARCHIVE_AUTO,
+        "http://www.xmpp.org/extensions/xep-0136.html#ns-auto").
+-define(NS_ARCHIVE_MANAGE,
+        "http://www.xmpp.org/extensions/xep-0136.html#ns-manage").
+-define(NS_ARCHIVE_PREF,
+        "http://www.xmpp.org/extensions/xep-0136.html#ns-pref").
+-define(NS_ARCHIVE_MANUAL,
+        "http://www.xmpp.org/extensions/xep-0136.html#ns-manual").
+-define(INFINITY, calendar:datetime_to_gregorian_seconds({{2038,1,19},{0,0,0}})).
+
+%% Should be OK for most of modern DBs, I hope ...
+-define(MAX_QUERY_LENGTH, 32768).
+
+-define(MYDEBUG(Format, Args),
+        io:format("D(~p:~p:~p) : " ++ Format ++ "~n",
+                  [calendar:local_time(), ?MODULE, ?LINE] ++ Args)).
+
+-record(archive_jid_prefs,
+        {us,
+         jid,
+         save = undefined,
+         expire = undefined,
+         otr = undefined}).
+
+-record(archive_global_prefs,
+        {us,
+         save = undefined,
+         expire = undefined,
+         otr = undefined,
+         method_auto = undefined,
+         method_local = undefined,
+         method_manual = undefined,
+         auto_save = undefined}).
+
+-record(archive_collection,
+        {id,
+         us,
+         jid,
+         utc,
+         change_by,
+         change_utc,
+         deleted,
+         subject = "",
+         prev = [],
+         next = [],
+         thread = "",
+         crypt = false,
+         extra = ""}).
+
+-record(archive_message,
+        {id,
+         coll_id,
+         utc,
+         direction,
+         body,
+         name = ""}).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    ChildSpec =
+        {Proc,
+         {?MODULE, start_link, [Host, Opts]},
+         permanent,
+         1000,
+         worker,
+         [?MODULE]},
+    supervisor:start_child(ejabberd_sup, ChildSpec).
+%% ejabberd-1.x compatibility code
+%% NOTE: keepalive is not supported in ejabberd 1.x, so
+%% you'll either need to turn off connections timeout in DB
+%% configuration or invent smth else ...
+%%    ChildSpecODBC =
+%%         {gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
+%%         {ejabberd_odbc_sup, start_link, [Host]},
+%%         permanent,
+%%         infinity,
+%%         supervisor,
+%%         [ejabberd_odbc_sup]},
+%%    supervisor:start_child(ejabberd_sup, ChildSpecODBC).
+%% EOF ejabberd-1.x compatibility code
+
+stop(Host) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:call(Proc, stop),
+    supervisor:delete_child(ejabberd_sup, Proc).
+%% ejabberd-1.x compatibility code
+%%    ProcODBC = gen_mod:get_module_proc(Host, ejabberd_odbc_sup),
+%%    gen_server:call(ProcODBC, stop),
+%%    supervisor:delete_child(ejabberd_sup, ProcODBC).
+%% EOF ejabberd-1.x compatibility code
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%%                         {ok, State, Timeout} |
+%%                         ignore               |
+%%                         {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Host, Opts]) ->
+    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+    SessionDuration = gen_mod:get_opt(session_duration, Opts, 1800),
+    WipeOutInterval = gen_mod:get_opt(wipeout_interval, Opts, 86400),
+    ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
+    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ARCHIVE, ?MODULE, process_iq, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_ARCHIVE, ?MODULE, process_local_iq, IQDisc),
+    ejabberd_hooks:add(user_send_packet, Host, ?MODULE, send_packet, 90),
+    ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 90),
+    ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, receive_packet, 35),
+    ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_disco_features, 99),
+    timer:send_interval(1000 * SessionDuration div 2, clean_sessions),
+    case WipeOutInterval of
+        infinity -> [];
+        N when is_integer(N) -> timer:send_interval(1000 * N, wipeout_collections)
+    end,
+    {ok, #state{host = Host,
+                sessions = dict:new(),
+                session_duration = SessionDuration}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%%                                      {reply, Reply, State, Timeout} |
+%%                                      {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, Reply, State} |
+%%                                      {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call(stop, _From, State) ->
+    {stop, normal, ok, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({addlog, Type, Direction, LUser, LServer, LResource, JID, Thread, Subject, Nick, Body}, State) ->
+    Sessions = State#state.sessions,
+    NewSessions =
+        case should_store_jid({LUser, LServer}, JID) of
+            false ->
+                Sessions;
+            true when Type == "groupchat", Direction == to ->
+                Sessions;
+            true ->
+                do_log(Sessions, LUser, LServer, LResource, JID,
+                       Type, Direction, Thread, Subject, Nick, Body,
+                       State#state.session_duration)
+        end,
+    {noreply, State#state{sessions = NewSessions}};
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%%                                       {noreply, State, Timeout} |
+%%                                       {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(clean_sessions, State) ->
+    Sessions = State#state.sessions,
+    Timeout = State#state.session_duration,
+    TS = get_timestamp(),
+    F = fun(_, Value)->
+		dict:filter(fun(_, {_Start, Last, _, _}) ->
+				    TS - Last =< Timeout
+			    end, Value)
+	end,
+    FilteredSessions = dict:map(F, Sessions),
+    NewSessions = dict:filter(fun(_Key, Value) ->
+				      dict:fetch_keys(Value) /= []
+                              end, FilteredSessions),
+    {noreply, State#state{sessions = NewSessions}};
+
+handle_info(wipeout_collections, State) ->
+    expire_collections(State#state.host),
+    {noreply, State};
+
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, State) ->
+    Host = State#state.host,
+    ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_ARCHIVE),
+    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ARCHIVE),
+    ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, send_packet, 90),
+    ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 90),
+    ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, receive_packet, 35),
+    ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_disco_features, 99),
+    ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%% Workaround the fact that if the client send <iq type='get'>
+%% it end up like <iq type='get' from='u@h' to = 'u@h'>
+process_iq(From, To, IQ) ->
+    #iq{sub_el = SubEl} = IQ,
+    #jid{lserver = LServer, luser = LUser} = To,
+    #jid{luser = FromUser} = From,
+    case {LUser, LServer, lists:member(LServer, ?MYHOSTS)} of
+        {FromUser, _, true} ->
+            process_local_iq(From, To, IQ);
+        {"", _, true} ->
+            process_local_iq(From, To, IQ);
+        {"", "", _} ->
+            process_local_iq(From, To, IQ);
+        _ ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+    end.
+
+process_local_iq(From, To, #iq{sub_el = SubEl} = IQ) ->
+    case lists:member(From#jid.lserver, ?MYHOSTS) of
+        false ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        true ->
+            {xmlelement, Name, _Attrs, _Els} = SubEl,
+            F = fun() ->
+			case Name of
+			    "pref" -> process_local_iq_pref(From, To, IQ);
+			    "auto" -> process_local_iq_auto(From, To, IQ);
+			    "list" -> process_local_iq_list(From, To, IQ);
+			    "retrieve" -> process_local_iq_retrieve(From, To, IQ);
+			    "save" -> process_local_iq_save(From, To, IQ);
+			    "remove" -> process_local_iq_remove(From, To, IQ);
+			    "modified" -> process_local_iq_modified(From, To, IQ);
+			    _ -> IQ#iq{type = error,
+				       sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
+			end
+		end,
+	    %% All IQ processing functions should return either {result, xmlelement} or
+	    %% {error, xmlelement} - other returns mean smth is seriously wrong
+	    %% with the code itself.
+            case catch F() of
+                {result, R} ->
+                    IQ#iq{type = result, sub_el = R};
+                {error, Err} ->
+                    IQ#iq{type = error,
+                          sub_el = [SubEl, Err]};
+                {'EXIT', Ex} ->
+                    ?ERROR_MSG("catched unhandled exception: ~p", [Ex]),
+                    IQ#iq{type = error,
+                          sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+                Res ->
+                    ?ERROR_MSG("unexpected result: ~p", [Res]),
+                    IQ#iq{type = error,
+                          sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+            end
+    end.
+
+
+remove_user(User, Server) ->
+    LUser = jlib:nodeprep(User),
+    LServer = jlib:nameprep(Server),
+    US = {LUser, LServer},
+    SUS = get_us_escaped(US),
+    F = fun() ->
+                run_sql_query(
+		  ["delete from archive_jid_prefs "
+		   "where us = ", SUS]),
+                run_sql_query(
+		  ["delete from archive_global_prefs "
+		   "where us = ", SUS]),
+                run_sql_query(
+		  ["delete from archive_collections "
+		   "where us = ", SUS])
+        end,
+    run_sql_transaction(LServer, F).
+
+get_disco_features(Acc, _From, _To, "", _Lang) ->
+    Features =
+        case Acc of
+            {result, I} -> I;
+            _ -> []
+        end,
+    {result, Features ++ [?NS_ARCHIVE_MANAGE,
+                          ?NS_ARCHIVE_AUTO,
+                          ?NS_ARCHIVE_PREF,
+                          ?NS_ARCHIVE_MANUAL]};
+
+get_disco_features(Acc, _From, _To, _Node, _Lang) ->
+    Acc.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3 Automated archiving
+%%
+
+send_packet(From, To, Packet) ->
+    add_log(to, From#jid.luser, From#jid.lserver, From#jid.lresource, To, Packet).
+
+receive_packet(From, To, Packet) ->
+    add_log(from, To#jid.luser, To#jid.lserver, To#jid.lresource, From, Packet).
+
+receive_packet(_JID, From, To, Packet) ->
+    receive_packet(From, To, Packet).
+
+add_log(Direction, LUser, LServer, LResource, JID, Packet) ->
+    case parse_message(Packet) of
+        {Type, Thread, Subject, Nick, Body}  ->
+            Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
+            gen_server:cast(
+              Proc, {addlog, Type, Direction, LUser, LServer, LResource, JID, Thread, Subject, Nick, Body});
+	_ ->
+            ok
+    end.
+
+%% Parse the message and return {Thread, Subject, Body} strings if successful
+parse_message({xmlelement, "message", _, _} = Packet) ->
+    case xml:get_tag_attr_s("type", Packet) of
+        Type when Type == "";
+                  Type == "normal";
+                  Type == "chat";
+                  Type == "groupchat" ->
+            case xml:get_subtag(Packet, "body") of
+                false ->
+		    "";
+                _ ->
+                    {Type,
+                     xml:get_path_s(Packet, [{elem, "thread"}, cdata]),
+                     xml:get_path_s(Packet, [{elem, "subject"}, cdata]),
+                     xml:get_path_s(Packet, [{elem, "nick"}, cdata]),
+                     xml:get_path_s(Packet, [{elem, "body"}, cdata])}
+            end;
+        _ ->
+            ""
+    end;
+parse_message(_) ->
+    "".
+
+%% archive the message Body    return new Sessions
+%%  Sessions:  a dict of open sessions
+%%  LUser, LServer :  the local user's information
+%%  Jid : the contact's jid
+%%  Body : the message body
+do_log(Sessions, LUser, LServer, LResource, JID, Type, Direction, Thread, Subject,
+       Nick, Body, SessionDuration) ->
+    LowJID = jlib:jid_tolower(JID),
+    {_, _, Resource} = LowJID,
+    F = fun() ->
+                {NewSessions, _Start, TS, CID, NewRes} =
+		    find_storage(LUser, LServer, LowJID, Thread, Sessions,
+				 SessionDuration, Type),
+                LJID = jlib:jid_tolower(jlib:make_jid(LUser, LServer, LResource)),
+                update_collection_partial(CID, LServer, Thread, Subject, NewRes, LJID, TS),
+                M = #archive_message{coll_id = CID,
+                                     utc = TS,
+                                     direction = Direction,
+                                     name =
+                                         if Type == "groupchat" ->
+                                             if Nick /= "" ->
+					         Nick;
+                                                true ->
+                                                 Resource
+                                                end;
+                                            true -> ""
+                                         end,
+                                     body = Body},
+                store_message(LServer, M),
+                NewSessions
+        end,
+    case run_sql_transaction(LServer, F) of
+        {error, Err} ->
+            ?ERROR_MSG("error when performing automated archiving: ~p", [Err]),
+            Sessions;
+        R -> %?MYDEBUG("successfull automated archiving: ~p", [R]),
+	    R
+    end.
+
+find_storage(LUser, LServer, JID, Thread, Sessions, Timeout, Type) ->
+    %%
+    %% In fact there's small problem with resources: we can send the message
+    %% to recepient without specifying resource (typically, when sending the first
+    %% message), or with it (for subsequent ones). On the other hand,
+    %% remote sender will always (?) use resource when sending us the message.
+    %%
+    %% This means that we either should strip resouce completely from JID when
+    %% creating the key (which is easy, but not nice, as then all messages will be
+    %% put into single collection without treating resources at all), or use more
+    %% intelligent approach to match the appropriate collection to our message.
+    %%
+    %% Additionally we'd like to use "thread" to differentiate between different
+    %% conversations, to put them into different collections.
+    %%
+    %% Here is the approach we use:
+    %%
+    %% 1) There's two levels key schema: first key is JID without resource, second-level
+    %%    key is the thread. If thread is not present, {no_thread, Resource} is used
+    %%    instead.
+    %% 2) If thread is specified in the message - just use both-levels keys normally,
+    %%    reusing existing collections if there's a match or creating new one if no
+    %%    matching collection found.
+    %% 3) Otherwise use first-level key to get all sub-items, then
+    %%      * If resource IS specified: search for matching resource:
+    %%        - if found - use it.
+    %%        - if not, search for sub-item with empty resource. If found, use it
+    %%          and rewrite its resource to ours, notifying the caller to store collection.
+    %%          If not - create new one.
+    %%      * If resource IS NOT specified: use the most recent sub-item or
+    %%        create new if none exists, notifying the caller about change of
+    %%        resource, if needed.
+    %%
+    {_, _, ResourceIn} = JID,
+    %% Assume empty Resource for groupchat messages so that they're recorded
+    %% to the same collection.
+    Resource = if Type == "groupchat" -> ""; true -> ResourceIn end,
+    Key1 = {LUser, LServer, jlib:jid_remove_resource(JID)},
+    TS = get_timestamp(),
+    case dict:find(Key1, Sessions) of
+        error ->
+            new_dict_answer(Key1, TS, Thread, Resource, Sessions);
+        {ok, Val1} ->
+            if Thread /= "" ->
+		    case dict:find(Thread, Val1) of
+			error ->
+			    new_dict_answer(Key1, TS, Thread, Resource, Sessions);
+			{ok, Val2} ->
+			    updated_dict_answer(Key1, Val2, TS, Thread,
+						Resource, Sessions, Timeout)
+		    end;
+	       true ->
+		    if Resource /= "" ->
+			    case dict:find({no_thread, Resource}, Val1) of
+				{ok, Val2} ->
+				    updated_dict_answer(Key1, Val2, TS, Thread,
+							Resource, Sessions, Timeout);
+				error ->
+				    case dict:find({no_thread, ""}, Val1) of
+					error ->
+					    new_dict_answer(Key1, TS, Thread, Resource,
+							    Sessions);
+					{ok, Val2} ->
+					    updated_dict_answer(Key1, Val2, TS,
+								Thread, Resource, Sessions, Timeout)
+				    end
+			    end;
+		       true ->
+			    F = fun(_, Value, {_, MaxLast, _, _} = OldVal) ->
+					{_, Last, _, CurRes} = Value,
+					if ((Type /= "groupchat") and (Last > MaxLast)) or
+                                           ((Type == "groupchat") and (CurRes == "")) ->
+                                                Value;
+					   true -> OldVal
+					end
+				end,
+			    case dict:fold(F, {-1, -1, null, ""}, Val1) of
+				{_, -1, _, _} ->
+				    new_dict_answer(Key1, TS, Thread, Resource,
+						    Sessions);
+				{_, _, _, Res} = Val2 ->
+				    updated_dict_answer(Key1, Val2, TS, Thread,
+							Res, Sessions, Timeout)
+			    end
+		    end
+            end
+    end.
+
+updated_dict(Key1, Start, TS, CID, Thread, Resource, Sessions) ->
+    Val1 = case dict:find(Key1, Sessions) of
+               error -> dict:new();
+               {ok, V} -> V
+           end,
+    Key2 = if Thread /= "" -> Thread; true -> {no_thread, Resource} end,
+    Val2 = {Start, TS, CID, Resource},
+    NVal1 = dict:store(Key2, Val2, Val1),
+    dict:store(Key1, NVal1, Sessions).
+
+updated_dict_answer(Key1, {Start, Last, CID, _OldRes}, TS, Thread, Resource,
+                    Sessions, Timeout) ->
+    if TS - Last > Timeout ->
+	    new_dict_answer(Key1, TS, Thread, Resource, Sessions);
+       true ->
+	    {updated_dict(Key1, Start, TS, CID, Thread, Resource, Sessions),
+	     Start, TS, CID, Resource}
+    end.
+
+new_dict_answer({LUser, LServer, JID} = Key1, TS, Thread, Resource, Sessions) ->
+    CID = get_collection_id({LUser, LServer, JID, TS}),
+    {updated_dict(Key1, TS, TS, CID, Thread, Resource, Sessions),
+     TS, TS, CID, Resource}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3.1 Preferences
+%%
+
+process_local_iq_pref(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    Result = case Type of
+		 set ->
+		     {xmlelement, _Name, _Attrs, Els} = SubEl,
+		     process_save_set(From#jid.luser, From#jid.lserver, Els);
+		 get ->
+		     process_save_get(From#jid.luser, From#jid.lserver)
+	     end,
+    case Result of
+        ok ->
+            broadcast_iq(From, IQ#iq{type = set, sub_el=[SubEl]}),
+            {result, []};
+        R -> R
+    end.
+
+%% returns {error, xmlelement} or {result, xmlelement}
+process_save_get(LUser, LServer) ->
+    F =
+        fun() ->
+		%% Request prefs for all of JIDs
+		LItems =
+		    lists:map(
+		      fun(J) ->
+			      jid_prefs_to_xml(J)
+		      end, get_all_jids_prefs({LUser, LServer})),
+		GPrefs = get_global_prefs({LUser, LServer}),
+		DGPrefs = default_global_prefs({LUser, LServer}),
+		UnSet =
+		    if (GPrefs#archive_global_prefs.save /= undefined) or
+		       (GPrefs#archive_global_prefs.expire /= undefined) or
+		       (GPrefs#archive_global_prefs.otr /= undefined) -> "false";
+		       true -> "true"
+		    end,
+		DItem = global_prefs_to_xml(GPrefs, DGPrefs, UnSet),
+		{result, [{xmlelement, "pref", [{"xmlns", ?NS_ARCHIVE}], DItem ++ LItems}]}
+        end,
+    run_sql_transaction(LServer, F).
+
+jid_prefs_to_xml(Pref) ->
+    Save = Pref#archive_jid_prefs.save,
+    Expire = Pref#archive_jid_prefs.expire,
+    OTR = Pref#archive_jid_prefs.otr,
+    {xmlelement, "item",
+     [{"jid", jlib:jid_to_string(Pref#archive_jid_prefs.jid)}] ++
+     if Save /= undefined ->
+	     [{"save", atom_to_list(Save)}];
+	true ->
+	     []
+     end  ++
+     if Expire /= undefined, Expire /= infinity ->
+	     [{"expire", integer_to_list(Expire)}];
+	true ->
+	     []
+     end ++
+     if OTR /= undefined ->
+	     [{"otr", atom_to_list(OTR)}];
+	true ->
+	     []
+     end, []}.
+
+global_prefs_to_xml(GPrefs, DGPrefs, UnSet) ->
+    Prefs = list_to_tuple(
+	      lists:zipwith(
+		fun(Item1, Item2) ->
+			if Item1 /= undefined -> Item1;
+			   true -> Item2
+			end
+		end,
+		tuple_to_list(GPrefs),
+		tuple_to_list(DGPrefs))),
+    Expire = Prefs#archive_global_prefs.expire,
+    [{xmlelement, "default",
+      [{"save", atom_to_list(Prefs#archive_global_prefs.save)}] ++
+      if Expire /= infinity -> [{"expire", integer_to_list(Expire)}]; true -> [] end ++
+      [{"otr", atom_to_list(Prefs#archive_global_prefs.otr)},
+       {"unset", UnSet}],
+      []},
+     {xmlelement, "method",
+      [{"type", "auto"},
+       {"use", atom_to_list(Prefs#archive_global_prefs.method_auto)}],
+      []},
+     {xmlelement, "method",
+      [{"type", "local"},
+       {"use", atom_to_list(Prefs#archive_global_prefs.method_local)}],
+      []},
+     {xmlelement, "method",
+      [{"type", "manual"},
+       {"use", atom_to_list(Prefs#archive_global_prefs.method_manual)}],
+      []},
+     {xmlelement, "auto",
+      [{"save", atom_to_list(Prefs#archive_global_prefs.auto_save)}],
+      []}].
+
+%% Returns the archive_global_prefs record filled with default values
+default_global_prefs({_, LServer} = US) ->
+    DefaultAutoSave = gen_mod:get_module_opt(LServer, ?MODULE, default_auto_save, false),
+    DefaultExpire = gen_mod:get_module_opt(LServer, ?MODULE, default_expire, infinity),
+    #archive_global_prefs{us = US,
+                          save = if DefaultAutoSave -> body; true -> false end,
+                          expire = DefaultExpire,
+                          method_auto = if DefaultAutoSave -> prefer; true -> concede end,
+                          method_local = concede,
+                          method_manual = if DefaultAutoSave -> concede; true -> prefer end,
+                          auto_save = DefaultAutoSave,
+                          otr = forbid}.
+
+
+%% Return {error, xmlelement} or ok
+process_save_set(LUser, LServer, Elems) ->
+    F =
+        fun() ->
+		US = {LUser, LServer},
+		GPrefs = get_global_prefs(US),
+		GPrefs1 = GPrefs#archive_global_prefs{us = US},
+		parse_save_elem(GPrefs1, Elems),
+		ok
+        end,
+    run_sql_transaction(LServer, F).
+
+parse_save_elem(GPrefs, [{xmlelement, "default", Attrs, _} | Tail]) ->
+    {Save, Expire, OTR} = get_main_prefs_from_attrs(Attrs),
+    GPrefs1 = GPrefs#archive_global_prefs{save = Save, expire = Expire, otr = OTR},
+    parse_save_elem(GPrefs1, Tail);
+
+parse_save_elem(GPrefs, [{xmlelement, "method", Attrs, _} | Tail]) ->
+    Use =
+        case xml:get_attr_s("use", Attrs) of
+            "concede" -> concede;
+            "forbid" -> forbid;
+            "prefer" -> prefer;
+            "" -> undefined;
+            _ -> throw({error, ?ERR_BAD_REQUEST})
+        end,
+    GPrefs1 =
+        case xml:get_attr_s("type", Attrs) of
+            "auto" -> GPrefs#archive_global_prefs{method_auto = Use};
+            "local" -> GPrefs#archive_global_prefs{method_local = Use};
+            "manual" -> GPrefs#archive_global_prefs{method_manual = Use};
+            "" -> GPrefs;
+            _ -> throw({error, ?ERR_BAD_REQUEST})
+        end,
+    parse_save_elem(GPrefs1, Tail);
+
+parse_save_elem(GPrefs, [{xmlelement, "auto", Attrs, _} | Tail]) ->
+    GPrefs1 =
+        case xml:get_attr_s("save", Attrs) of
+            "true" -> GPrefs#archive_global_prefs{auto_save = true};
+            "false" -> GPrefs#archive_global_prefs{auto_save = false};
+            _ -> throw({error, ?ERR_BAD_REQUEST})
+        end,
+    parse_save_elem(GPrefs1, Tail);
+
+parse_save_elem(GPrefs, [{xmlelement, "item", Attrs, _}  | Tail]) ->
+    case jlib:string_to_jid(xml:get_attr_s("jid", Attrs)) of
+        error -> throw({error, ?ERR_JID_MALFORMED});
+        JID ->
+            LJID = jlib:jid_tolower(JID),
+            {Save, Expire, OTR} = get_main_prefs_from_attrs(Attrs),
+            Prefs = #archive_jid_prefs{us = GPrefs#archive_global_prefs.us,
+                                       jid = LJID,
+                                       save = Save,
+                                       expire = Expire,
+                                       otr = OTR},
+            store_jid_prefs(Prefs)
+    end,
+    parse_save_elem(GPrefs, Tail);
+
+parse_save_elem(GPrefs, []) ->
+    store_global_prefs(GPrefs);
+
+parse_save_elem(GPrefs, [_ | Tail]) ->
+    parse_save_elem(GPrefs,  Tail).
+
+get_main_prefs_from_attrs(Attrs) ->
+    Save =
+        case xml:get_attr_s("save", Attrs) of
+            "body" -> body;
+            "false" -> false;
+            "" -> undefined;
+            _ -> throw({error, ?ERR_BAD_REQUEST})
+        end,
+    Expire =
+        case xml:get_attr_s("expire", Attrs) of
+            "" -> undefined;
+            N -> case catch list_to_integer(N) of
+                     NR when is_integer(NR) -> NR;
+                     _ -> throw({eror, ?ERR_BAD_REQUEST})
+                 end
+        end,
+    OTR =
+        case xml:get_attr_s("otr", Attrs) of
+            "" -> undefined;
+            V -> list_to_atom(V)
+        end,
+    {Save, Expire, OTR}.
+
+
+broadcast_iq(#jid{luser = User, lserver = Server}, IQ) ->
+    Fun = fun(Resource) ->
+		  ejabberd_router:route(
+                    jlib:make_jid("", Server, ""),
+                    jlib:make_jid(User, Server, Resource),
+                    jlib:iq_to_xml(IQ#iq{id="push"}))
+	  end,
+    lists:foreach(Fun, ejabberd_sm:get_user_resources(User,Server)).
+
+
+process_local_iq_auto(From, _To, #iq{type = Type, sub_el = SubEl}) ->
+    case Type of
+        set ->
+            {xmlelement, _Name, Attrs, _Els} = SubEl,
+            Auto =
+                case xml:get_attr_s("save", Attrs) of
+                    "true" -> true;
+                    "false" -> false;
+                    _ -> throw({error, ?ERR_BAD_REQUEST})
+                end,
+            LUser = From#jid.luser,
+            LServer = From#jid.lserver,
+            F =
+                fun() ->
+			US = {LUser, LServer},
+			GPrefs = get_global_prefs(US),
+			GPrefs1 = GPrefs#archive_global_prefs{us = US, auto_save = Auto},
+			store_global_prefs(GPrefs1),
+			{result, []}
+                end,
+            run_sql_transaction(LServer, F);
+        get ->
+            throw({error, ?ERR_BAD_REQUEST})
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Utility function
+
+%% Return true if LUser@LServer should log message for the contact JID
+should_store_jid({_, LServer} = US, JID) ->
+    F =
+        fun() ->
+		GPrefs = get_global_prefs(US),
+		case GPrefs#archive_global_prefs.auto_save of
+		    false ->
+			false;
+		    true ->
+			should_store_jid_full_check(US, JID);
+		    undefined ->
+			DGPrefs = default_global_prefs(US),
+			if
+			    DGPrefs#archive_global_prefs.auto_save == false ->
+				false;
+			    true ->
+				should_store_jid_full_check(US, JID)
+			end
+		end
+        end,
+    case run_sql_transaction(LServer, F) of
+        {error, Err} -> ?ERROR_MSG("should_store_jid failed: ~p", [Err]), false;
+        R -> R
+    end.
+
+should_store_jid_full_check(US, JID) ->
+    {User, Server, Res} = jlib:jid_tolower(JID),
+    Prefs1 = get_jid_prefs(US, {User, Server, Res}),
+    Save1 = Prefs1#archive_jid_prefs.save,
+    Save2 =
+        if Save1 == undefined ->
+		Prefs2 = get_jid_prefs(US, {User, Server, ""}),
+		Prefs2#archive_jid_prefs.save;
+           true -> Save1
+        end,
+    Save3 =
+        if Save2 == undefined ->
+		Prefs3 = get_jid_prefs(US, {"", Server, ""}),
+		Prefs3#archive_jid_prefs.save;
+           true -> Save2
+        end,
+    case Save3 of
+        false -> false;
+        _ -> true
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   4. Manual Archiving
+%%
+
+
+process_local_iq_save(From, _To, #iq{type = Type, sub_el = SubEl}) ->
+    #jid{luser = LUser, lserver = LServer, lresource = LResource} = From,
+    case Type of
+        get ->
+            throw({error, ?ERR_NOT_ALLOWED});
+        set ->
+            {C, Msgs} = parse_store_element(LUser, LServer, SubEl),
+            F =
+                fun() ->
+			CID = get_collection_id({LUser, LServer,
+						 C#archive_collection.jid,
+						 C#archive_collection.utc}),
+			C1 = get_collection_by_id(CID),
+			LJID = jlib:jid_tolower(jlib:make_jid(LUser, LServer, LResource)),
+			C2 = C#archive_collection{id = CID,
+						  change_by = LJID,
+						  change_utc = get_timestamp()},
+			C3 =
+			    list_to_tuple(
+			      lists:zipwith(
+                                fun(ValOld, ValNew) ->
+					case ValNew of
+					    undefined -> ValOld;
+					    _ -> ValNew
+					end
+                                end,
+                                tuple_to_list(C1), tuple_to_list(C2))),
+			store_collection(C3),
+			store_messages(LServer, CID, Msgs),
+			{result, []}
+                end,
+            run_sql_transaction(LServer, F)
+    end.
+
+%% return a {#archive_collection, list of #archive_message} or {error, xmlelement}
+parse_store_element(LUser, LServer,
+                    {xmlelement, "save", _ChatAttrs, ChatSubEls}) ->
+    case xml:remove_cdata(ChatSubEls) of
+        [{xmlelement, "chat", Attrs, SubEls} = SubEl] ->
+            {LUser, LServer, Jid, Start} = link_from_argument(LUser, LServer, SubEl),
+            Extra = xml:get_subtag(SubEl, "x"),
+            C = #archive_collection{us = {LUser, LServer},
+                                    jid = Jid,
+                                    utc = Start,
+                                    prev = get_link_as_list(SubEls, "previous"),
+                                    next = get_link_as_list(SubEls, "next"),
+                                    subject = case xml:get_attr("subject", Attrs) of
+                                                  {value, Val} -> Val;
+                                                  false -> undefined
+                                              end,
+                                    thread = case xml:get_attr("thread", Attrs) of
+						 {value, Val} -> Val;
+						 false -> undefined
+                                             end,
+                                    extra =
+				    if Extra /= false -> encode_extra(Extra);
+				       true -> undefined
+				    end},
+            Messages = parse_store_element_sub(SubEls, Start),
+            {C, Messages};
+        _ ->
+            throw({error, ?ERR_BAD_REQUEST})
+    end.
+
+parse_store_element_sub([{xmlelement, Dir, _, _}  = E | Tail], Start)
+  when Dir == "from";
+       Dir == "to";
+       Dir == "note" ->
+    UTC =
+	case xml:get_tag_attr_s("secs", E) of
+	    "" ->
+		case xml:get_tag_attr_s("utc", E) of
+		    "" -> throw({error, ?ERR_BAD_REQUEST});
+		    Val -> get_seconds_from_datetime_string(Val)
+		end;
+	    Secs ->
+		Start +
+		    case list_to_integer(Secs) of
+			N when is_integer(N) -> N;
+			_ -> throw({error, ?ERR_BAD_REQUEST})
+		    end
+	end,
+    Body = if Dir == "note" -> xml:get_tag_cdata(E);
+              true -> xml:get_tag_cdata(xml:get_subtag(E,"body"))
+           end,
+    [#archive_message{direction = list_to_atom(Dir),
+		      utc = UTC,
+		      body = Body,
+		      name = xml:get_tag_attr_s("name", E)} |
+     parse_store_element_sub(Tail, Start)];
+
+parse_store_element_sub([], _) -> [];
+
+parse_store_element_sub([_ | Tail], Start) -> parse_store_element_sub(Tail, Start).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   5. Archive Management
+%%
+
+
+process_local_iq_list(From, _To, #iq{type = Type, sub_el = SubEl}) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        set ->
+            throw({error, ?ERR_NOT_ALLOWED});
+        get ->
+            {xmlelement, _, _, SubEls} = SubEl,
+            RSM = parse_rsm(SubEls),
+            F = fun() ->
+			{interval, Start, Stop, JID} = parse_root_argument(SubEl),
+			Req = get_combined_req(Start, Stop, RSM),
+			{ok, Items, RSM_Elem} = get_collections_links(LUser, LServer, Req, JID),
+			{result, [{xmlelement, "list",
+				   [{"xmlns", ?NS_ARCHIVE}],
+				   lists:append(
+				     lists:map(
+                                       fun(C) ->
+					       collection_link_to_xml("chat", C)
+                                       end, Items),
+				     RSM_Elem)}]}
+                end,
+            run_sql_transaction(LServer, F)
+    end.
+
+
+process_local_iq_retrieve(From, _To, #iq{type = Type, sub_el = SubEl}) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        set ->
+            throw({error, ?ERR_NOT_ALLOWED});
+        get ->
+            {xmlelement, _, _, SubEls} = SubEl,
+            RSM = parse_rsm(SubEls),
+            F = fun() ->
+			Link = link_from_argument(LUser, LServer, SubEl),
+			Store = retrieve_collection_and_msgs(Link, RSM),
+			{result, Store}
+                end,
+            run_sql_transaction(LServer, F)
+    end.
+
+
+retrieve_collection_and_msgs(Link, RSM) ->
+    C = get_collection(Link),
+    {ok, Items, RSM_Elem} = get_messages(C, RSM),
+    {_, _, Attrs, SubEls} = collection_to_xml(C),
+    [{xmlelement, "chat", Attrs,
+      lists:append([SubEls,
+                    lists:map(
+		      fun(M) ->
+			      message_to_xml(M, C#archive_collection.utc)
+		      end, Items),
+                    RSM_Elem])}].
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   5.3 Removing a Collection
+%%
+
+
+process_local_iq_remove(From, _To, #iq{type = Type, sub_el = SubEl}) ->
+    #jid{luser = LUser, lserver = LServer, lresource = LResource} = From,
+    case Type of
+        get ->
+            throw({error, ?ERR_NOT_ALLOWED});
+        set ->
+            {xmlelement, _,  _, _} = SubEl,
+            {interval, Start, Stop, Jid} = parse_root_argument(SubEl),
+            process_remove_interval(LUser, LServer, LResource, Start, Stop, Jid)
+    end.
+
+process_remove_interval(LUser, LServer, LResource, Start, End, With) ->
+    SUS = get_us_escaped({LUser, LServer}),
+    WithCond = case With of
+                   undefined ->
+                       "";
+                   JID ->
+                       {SUser, SServer, SResource} = get_jid_escaped(JID),
+                       SServerNonEmpty = is_non_empty(SServer),
+                       SUserNonEmpty = is_non_empty(SUser),
+                       SResourceNonEmpty = is_non_empty(SResource),
+                       [if SServerNonEmpty == true -> ["with_server = ", SServer, " "];
+                           true -> ""
+                        end,
+                        if SUserNonEmpty == true -> [" and with_user = ", SUser, " "];
+                           true -> ""
+                        end,
+                        if SResourceNonEmpty == true -> [" and with_resource = ", SResource, " "];
+                           true -> ""
+                        end]
+               end,
+    TimeCond =
+        case {Start, End} of
+            {undefined, undefined} ->
+                "";
+            {undefined, _} ->
+                SEnd = encode_timestamp(End),
+                ["and utc < ", SEnd, " "];
+            {_, undefined} ->
+                SStart = encode_timestamp(Start),
+                ["and utc = ", SStart, " "];
+            _ ->
+                SStart = encode_timestamp(Start),
+                SEnd = encode_timestamp(End),
+                ["and utc >= ", SStart, " "
+                 "and utc < ", SEnd, " "]
+	end,
+    F =
+        fun() ->
+		WhereCond =
+		    ["where us = ", SUS, " ",
+		     "and deleted = 0 ",
+		     TimeCond,
+		     if WithCond /= "" -> ["and ", WithCond];
+			true -> ""
+		     end],
+		TS = get_timestamp(),
+		LJID = jlib:jid_tolower(jlib:make_jid(LUser, LServer, LResource)),
+		case jlib:tolower(gen_mod:get_module_opt(LServer, ?MODULE, database_type, "")) of
+		    %% MySQL has severe limitations for triggers: they cannot update the same table
+		    %% they're invoked for, so we have to do that here.
+		    %% However, yet another limitation is that in UPDATE MySQL cannot use the same table
+		    %% in subquery which is being updated - so we have to cheat here, see
+		    %% http://www.xaprb.com/blog/2006/06/23/how-to-select-from-an-update-target-in-mysql/
+		    "mysql" ->
+			run_sql_query(
+			  ["update archive_collections "
+			   "set next_id = NULL "
+			   "where next_id in "
+			   "(select id from "
+			   "(select id from archive_collections ",
+			   WhereCond, ") as x)"]),
+			run_sql_query(
+			  ["update archive_collections "
+			   "set prev_id = NULL "
+			   "where prev_id in "
+			   "(select id from "
+			   "(select id from archive_collections ",
+			   WhereCond, ") as x)"]);
+		    _ -> ok % Nothing to be done, all work should be done by trigger
+		end,
+		case run_sql_query(
+		       ["update archive_collections "
+			"set deleted = 1, "
+			"subject = '', "
+			"thread = '', "
+			"extra = '', "
+			"prev_id = NULL, "
+			"next_id = NULL, "
+			"change_by = ", get_jid_full_escaped(LJID), ", "
+			"change_utc = ", encode_timestamp(TS), " ",
+			WhereCond]) of
+		    {deleted, 0} -> throw({error, ?ERR_ITEM_NOT_FOUND});
+		    Res -> Res
+		end,
+		{result, []}
+        end,
+    run_sql_transaction(LServer, F).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   10. Replication
+%%
+
+
+process_local_iq_modified(From, _To, #iq{type = Type, sub_el = SubEl}) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        set ->
+            {error, ?ERR_NOT_ALLOWED};
+        get ->
+            {xmlelement, _, _, SubEls} = SubEl,
+            RSM = parse_rsm(SubEls),
+            F =
+                fun() ->
+			{interval, Start, Stop, _} = parse_root_argument(SubEl),
+			{{range, {Start1, _}, {_, _}, _}, _} = RSM,
+			StartPresent = xml:get_tag_attr_s("start", SubEl) == "",
+			{ok, Items, RSM_Item} =
+			    if not is_integer(Start1), StartPresent ->
+				    get_modified_legacy(LUser, LServer, RSM);
+			       true ->
+				    Req = get_combined_req(Start, Stop, RSM),
+				    get_modified(LUser, LServer, Req)
+			    end,
+			{result, [{xmlelement, "modified",
+				   [{"xmlns", ?NS_ARCHIVE}],
+				   lists:append(
+				     lists:map(
+				       fun(AC) ->
+					       change_to_xml(AC)
+				       end, Items),
+				     RSM_Item)}]}
+                end,
+            run_sql_transaction(LServer, F)
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% X.x Utility functions to interact with the database
+
+%%
+%% The design is as follows:
+%%
+%% * For collections:
+%%   1) When get_collection_id is called, ID of this collection from database is
+%%      returned - if needed, the entity is created first using the information
+%%      supplied in get_collection_id call (which is only partial).
+%%   2) After having ID, you can call store_collection function to actually
+%%      put meaningful values into it.
+%%
+%% * For messages:
+%%     As messages are always created, only store_message is supported.
+%%
+%% * For prefs:
+%%     Just store_ functions are provided, as the info is the key on its own.
+%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% get_collection_id related functions
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%
+%% Adds new collection or returns ID of existing one.
+%%
+get_collection_id({LUser, LServer, JID, Start}) ->
+    SUS = get_us_escaped({LUser, LServer}),
+    SJID = get_jid_escaped(JID),
+    {SUser, SServer, SResource} = SJID,
+    SUTC = encode_timestamp(Start),
+    case get_collection_id_raw(SUS, SJID, SUTC) of
+	%% Collection is present already - just return its ID
+        ID when is_integer(ID) -> ID;
+        _ ->
+	    %% Insert new collection.
+            InsVals = [SUS, ",",
+                       SUser, ",",
+                       SServer, ",",
+                       SResource, ",",
+                       SUTC, ",",
+                       "0"],
+            run_sql_query(["insert into archive_collections"
+                           "(us, with_user, with_server, with_resource, utc, deleted) "
+                           "values(", InsVals, ")"]),
+            case get_last_inserted_id(LServer, "archive_collections") of
+                error -> get_collection_id_raw(SUS, SJID, SUTC);
+                ID -> ID
+            end
+    end.
+
+get_collection_id_raw(SUS, {SUser, SServer, SResource}, SUTC) ->
+    case run_sql_query(["select id from archive_collections "
+                        "where us = ", SUS, " "
+                        "and with_user = ", SUser, " "
+                        "and with_server = ", SServer," ",
+                        "and with_resource = ", SResource, " "
+                        "and utc = ", SUTC]) of
+        {selected, _, Rs} when Rs /= [] ->
+	    {ID} = lists:last(lists:sort(Rs)),
+	    decode_integer(ID);
+        _ -> {error, ?ERR_BAD_REQUEST}
+    end.
+
+%%
+%% The following functions deal with links that can be present in collections.
+%%
+get_link_as_list([{xmlelement, Tag, Attrs, _} | _], Name)
+  when Tag == Name ->
+    if Attrs /= [] ->
+	    {jlib:jid_tolower(jlib:string_to_jid(xml:get_attr_s("with", Attrs))),
+	     get_seconds_from_datetime_string(xml:get_attr_s("start", Attrs))};
+       true ->
+	    []
+    end;
+
+get_link_as_list([], _) -> undefined;
+
+get_link_as_list([_ | Tail], Name) -> get_link_as_list(Tail, Name).
+
+get_collection_link_id({LUser, LServer}, {With, Start}) ->
+    get_collection_id({LUser, LServer, With, Start});
+
+get_collection_link_id({_, _}, _) -> null.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Store functions.
+%% These functions update collections, messages or preferences respectively
+%% that exist already in database (or, for prefs, possibly creating them).
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+store_collection(C) ->
+    US = C#archive_collection.us,
+    {_, LServer} = US,
+    JID = C#archive_collection.jid,
+    %% We assume that the only part of JID store_collection can change
+    %% is resource, after figuring out conversation's recipient actual resource.
+    %% Currently it's not needed anymore as update_collection_partial() is provided,
+    %% but we still leave it here just in case.
+    {_, _, SResource} = get_jid_escaped(JID),
+    SCHUTC = encode_timestamp(C#archive_collection.change_utc),
+    SByJID = get_jid_full_escaped(C#archive_collection.change_by),
+    CPrevID = get_collection_link_id(US, C#archive_collection.prev),
+    CNextID = get_collection_link_id(US, C#archive_collection.next),
+    SSubject = escape_str(LServer, C#archive_collection.subject),
+    SThread = escape_str(LServer, C#archive_collection.thread),
+    SExtra = escape_str(LServer, C#archive_collection.extra),
+    CollVals = ["with_resource = ", SResource, ", "
+                "deleted = 0, "
+                "change_by = ", SByJID, ", "
+                "change_utc = ", SCHUTC, ", "
+                "prev_id = ", escape(CPrevID), ", "
+                "next_id = ", escape(CNextID), ", "
+                "subject = ", SSubject, ", "
+                "thread = ", SThread, ", "
+                "extra = ", SExtra],
+    run_sql_query(["update archive_collections set ",
+                   CollVals, " where id = ",
+                   escape(C#archive_collection.id)]).
+
+%%
+%% partial collection update, useful for quick update when autosaving
+%%
+update_collection_partial(CID, LServer, Thread, Subject, NewRes, LJID, TS) ->
+    SResource = escape(NewRes),
+    SByJID = get_jid_full_escaped(LJID),
+    SCHUTC = encode_timestamp(TS),
+    SSubject = escape_str(LServer, Subject),
+    SThread = escape_str(LServer, Thread),
+    CollVals = ["with_resource = ", SResource, ", "
+                "change_by = ", SByJID, ", "
+                "change_utc = ", SCHUTC, ", "
+                "subject = ", SSubject, ", "
+                "thread = ", SThread],
+    run_sql_query(["update archive_collections set ",
+                   CollVals, " where id = ",
+                   escape(CID)]).
+
+
+%% store_message is somewhat special as it is never called for existing message -
+%% therefore we can optimize it by using only one INSERT command, unlike
+%% collections and changes, where we have to do SELECT -> [INSERT -> SELECT] -> UPDATE
+store_message(LServer, Msg) ->
+    run_sql_query([get_store_msg_header(), get_message_values_stmt(LServer, Msg)]).
+
+%%
+%% Stores multiple messages using multiple insert SQL operator - for those RDBMS'es that
+%% support this syntax
+%%
+store_messages(LServer, CID, Msgs) ->
+    case jlib:tolower(gen_mod:get_module_opt(LServer, ?MODULE, database_type, "")) of
+        "sqlite" ->
+	    %% Single inserts
+            lists:map(
+	      fun(Msg) ->
+		      Msg1 = Msg#archive_message{coll_id = CID},
+		      store_message(LServer, Msg1)
+	      end, Msgs);
+        _ ->
+	    %% Multiple inserts
+            Header = get_store_msg_header(),
+            Res =
+                lists:foldl(
+		  fun(Msg, AccIn) ->
+			  Msg1 = Msg#archive_message{coll_id = CID},
+			  Values = get_message_values_stmt(LServer, Msg1),
+			  if AccIn == "" ->
+				  Header ++ Values;
+			     true ->
+				  Len = lists:flatlength(AccIn) + lists:flatlength(Values),
+				  if Len < ?MAX_QUERY_LENGTH ->
+					  AccIn ++ "," ++ Values;
+				     true ->
+					  run_sql_query(AccIn),
+					  Header ++ Values
+				  end
+			  end
+		  end,
+		  "", Msgs),
+            if Res /= "" ->
+		    run_sql_query(Res);
+               true -> ok
+            end
+    end.
+
+get_store_msg_header() ->
+    "insert into archive_messages(coll_id, utc, dir, name, body) values".
+
+get_message_values_stmt(LServer, Msg) ->
+    SDirection = escape(case Msg#archive_message.direction of
+                            to -> 1;
+                            from -> 0;
+                            note -> 2
+                        end),
+    SName = escape_str(LServer, Msg#archive_message.name),
+    SBody = escape_str(LServer, Msg#archive_message.body),
+    ["(", escape(Msg#archive_message.coll_id), ", ",
+     encode_timestamp(Msg#archive_message.utc), ", ",
+     SDirection, ", ",
+     SName, ", ",
+     SBody, ")"].
+
+%% store global prefs, either creating them or updating existing ones.
+store_global_prefs(GPrefs) ->
+    US = GPrefs#archive_global_prefs.us,
+    {_, LServer} = US,
+    validate_global_prefs(LServer,
+                          GPrefs#archive_global_prefs.auto_save,
+                          GPrefs#archive_global_prefs.save,
+                          GPrefs#archive_global_prefs.expire),
+    SPrefs = escape_global_prefs(GPrefs),
+    Fields = ["save", "expire", "otr",
+              "method_auto", "method_local", "method_manual",
+              "auto_save"],
+    SUS = get_us_escaped(US),
+    case run_sql_query(["select us from archive_global_prefs "
+                        "where us = ", SUS]) of
+        {selected, _, Rs} when Rs /= [] ->
+            run_sql_query(["update archive_global_prefs set ",
+                           put_commas(combine_names_vals(Fields, SPrefs)),
+                           " where us = ", SUS]);
+        _ ->
+            run_sql_query(["insert into archive_global_prefs("
+                           "us, ", put_commas(Fields), ") "
+                           "values(", SUS, ", ", put_commas(SPrefs), ")"])
+    end.
+
+escape_global_prefs(GPrefs) ->
+    escape_common_prefs(GPrefs#archive_global_prefs.save,
+                        GPrefs#archive_global_prefs.expire,
+                        GPrefs#archive_global_prefs.otr) ++
+	lists:map(
+	  fun(V) ->
+		  case V of
+		      undefined -> "null";
+		      prefer -> "0";
+		      concede -> "1";
+		      forbid -> "2";
+		      _ -> throw({error, ?ERR_BAD_REQUEST})
+		  end
+	  end,
+	  [GPrefs#archive_global_prefs.method_auto,
+	   GPrefs#archive_global_prefs.method_local,
+	   GPrefs#archive_global_prefs.method_manual]) ++
+	[case GPrefs#archive_global_prefs.auto_save of
+	     true -> "1";
+	     false -> "0";
+	     undefined -> "null";
+	     _ -> throw({error, ?ERR_BAD_REQUEST})
+	 end].
+
+%% store jid prefs, either creating them or updating existing ones.
+store_jid_prefs(Prefs) ->
+    US = Prefs#archive_jid_prefs.us,
+    {_, LServer} = US,
+    validate_common_prefs(LServer,
+                          Prefs#archive_jid_prefs.save,
+                          Prefs#archive_jid_prefs.expire),
+    SPrefs = escape_jid_prefs(Prefs),
+    Fields = ["save", "expire", "otr"],
+    SUS = get_us_escaped(US),
+    {SUser, SServer, SRes} = get_jid_escaped(Prefs#archive_jid_prefs.jid),
+    case run_sql_query(["select us from archive_jid_prefs "
+                        "where us = ", SUS, " "
+                        "and with_user = ", SUser, " "
+                        "and with_server = ", SServer, " "
+                        "and with_resource = ", SRes]) of
+        {selected, _, Rs} when Rs /= [] ->
+            run_sql_query(["update archive_jid_prefs set ",
+                           put_commas(combine_names_vals(Fields, SPrefs)),
+                           " where us = ", SUS, " "
+                           "and with_user = ", SUser, " "
+                           "and with_server = ", SServer, " "
+                           "and with_resource = ", SRes]);
+        _ ->
+            run_sql_query(["insert into archive_jid_prefs("
+                           "us, with_user, with_server, with_resource, ",
+                           put_commas(Fields), ") "
+                           "values(", SUS, ", ",
+                           SUser, ", ",
+                           SServer, ", ",
+                           SRes, ", ",
+                           put_commas(SPrefs), ")"])
+    end.
+
+escape_jid_prefs(Prefs) ->
+    escape_common_prefs(Prefs#archive_jid_prefs.save,
+                        Prefs#archive_jid_prefs.expire,
+                        Prefs#archive_jid_prefs.otr).
+
+validate_global_prefs(LServer, AutoSave, Save, Expire) ->
+    DefAutoSave = gen_mod:get_module_opt(LServer, ?MODULE, default_auto_save, false),
+    EnforceDefAutoSave = gen_mod:get_module_opt(LServer, ?MODULE, enforce_default_auto_save, false),
+    %% Should we enforce our default auto save policy?
+    %% User is trying to change auto_save to the option other than enforced.
+    if EnforceDefAutoSave and (DefAutoSave /= AutoSave) and (AutoSave /= undefined) ->
+	    throw({error, ?ERR_FEATURE_NOT_IMPLEMENTED});
+       true -> ok
+    end,
+    validate_common_prefs(LServer, Save, Expire).
+
+validate_common_prefs(LServer, Save, Expire) ->
+    DefAutoSave = gen_mod:get_module_opt(LServer, ?MODULE, default_auto_save, false),
+    EnforceDefAutoSave = gen_mod:get_module_opt(LServer, ?MODULE, enforce_default_auto_save, false),
+    %% Should we enforce our default auto save policy?
+    if EnforceDefAutoSave and
+       %% auto-save=true is enforced but user is trying to put "save" element to smth other
+       %% than body (thus effectively turning saving off).
+       (DefAutoSave and (Save /= body) and (Save /= undefined)) ->
+	    throw({error, ?ERR_FEATURE_NOT_IMPLEMENTED});
+       true -> ok
+    end,
+    EnforceMinExpire = gen_mod:get_module_opt(LServer, ?MODULE, enforce_min_expire, 0),
+    if (Expire /= undefined) and (Expire /= infinity) and
+       ((EnforceMinExpire == infinity) or (Expire < EnforceMinExpire)) ->
+	    throw({error, ?ERR_FEATURE_NOT_IMPLEMENTED});
+       true -> ok
+    end,
+    EnforceMaxExpire = gen_mod:get_module_opt(LServer, ?MODULE, enforce_max_expire, infinity),
+    if (EnforceMaxExpire /= infinity) and (Expire /= undefined) and
+       ((Expire == infinity) or (Expire > EnforceMaxExpire)) ->
+	    throw({error, ?ERR_FEATURE_NOT_IMPLEMENTED});
+       true -> ok
+    end.
+
+escape_common_prefs(Save, Expire, OTR) ->
+    [case Save of
+         body -> "1";
+         false -> "0";
+         undefined -> "null";
+         _ -> throw({error, ?ERR_FEATURE_NOT_IMPLEMENTED})
+     end,
+     case Expire of
+         infinity -> "null";
+         undefined -> "null";
+         N -> integer_to_list(N)
+     end,
+     case OTR of
+         undefined -> "null";
+         approve -> "0";
+         concede -> "1";
+         forbid -> "2";
+         oppose -> "3";
+         prefer -> "4";
+         require -> "5";
+         _ -> throw({error, ?ERR_BAD_REQUEST})
+     end].
+
+put_commas(Vals) ->
+    lists:foldl(
+      fun(V, AccIn) ->
+	      if AccIn /= "" -> AccIn ++ ", " ++ V;
+		 true -> AccIn ++ V
+	      end
+      end,
+      "", Vals).
+
+combine_names_vals(Names, Vals) ->
+    lists:zipwith(
+      fun(Name, Val) ->
+	      [Name, " = ", Val]
+      end, Names, Vals).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% This function should return the last inserted auto-generated ID,
+%% if supported by database. If not - second lookup will be performed
+%% to fetch new ID. Typically this should be safe, although, probably,
+%% slightly slower.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+get_last_inserted_id(LServer, Table) ->
+    case jlib:tolower(gen_mod:get_module_opt(LServer, ?MODULE, database_type, "")) of
+        "mysql" -> {selected, _, [{ID}]} = run_sql_query(["select LAST_INSERT_ID()"]),
+                   decode_integer(ID);
+        "sqlite" -> {selected, _, [{ID}]} = run_sql_query(["select last_insert_rowid()"]),
+		    decode_integer(ID);
+	"pgsql" -> {selected, _, [{ID}]} = run_sql_query(["select currval('",
+                                                          Table, "_id_seq')"]), decode_integer(ID);
+        _ ->
+            error
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Helper functions to deal with RSM and main commands restrictions.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_combined_req(Start, End, {{index, Index}, Max}) ->
+    {{{index, Index}, Max}, Start, End};
+
+get_combined_req(Start, End, {{range, {RStart, StartID}, {REnd, EndID}, Order}, Max}) ->
+    StartLarger = timestamp_to_integer(Start) > timestamp_to_integer(RStart),
+    StartLink = if StartLarger ->
+			{Start, undefined};
+                   true ->
+			{RStart, StartID}
+                end,
+    EndSmaller = timestamp_to_integer(End) < timestamp_to_integer(REnd),
+    EndLink = if EndSmaller ->
+		      {End, undefined};
+                 true ->
+		      {REnd, EndID}
+              end,
+    {{{range, StartLink, EndLink, Order}, Max}, Start, End};
+
+get_combined_req(Start, End, []) ->
+    {{{range, {Start, undefined}, {End, undefined}, normal}, undefined}, Start, End};
+
+get_combined_req(_, _, _) ->
+    throw({error, ?ERR_BAD_REQUEST}).
+
+reverse_items_if_needed(Items, {{range, {_, _}, {_, _}, reversed}, _}) -> lists:reverse(Items);
+reverse_items_if_needed(Items, _) -> Items.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Helper functions with common code for SQL queries
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_request_part_times(UTCField, {Start, StartID}, {End, EndID}) ->
+    %% If IDs are specified - always use strict comparisons, as "non-strictness" will be added by IDs.
+    %% If start is from command attribute - it should be ">=" according to XEP-136.
+    %% However, if we're called for "modified" - use ">", not ">=".
+    GTS = if UTCField == "change_utc"; StartID /= undefined -> ">"; true -> ">=" end,
+    SStart = encode_timestamp(Start),
+    SEnd = encode_timestamp(End),
+    StartCond = [UTCField, " ", GTS, " ", SStart, " "],
+    EndCond = [UTCField, " < ", SEnd, " "],
+    [if StartID == undefined ->
+	     ["and ", StartCond, " "];
+        true ->
+	     ["and (", StartCond, " or (", UTCField, " = ", SStart, " "
+	      "and id > ", escape(StartID), ")) "] end,
+     if EndID == undefined ->
+	     ["and ", EndCond, " "];
+        true ->
+	     ["and (", EndCond, " or (", UTCField, " = ", SEnd, " "
+	      "and id < ", escape(EndID), ")) "] end].
+
+get_request_part_range(UTCField, {{range, {Start, StartID}, {End, EndID}, Order}, Max}) ->
+    [get_request_part_times(UTCField, {Start, StartID}, {End, EndID}),
+     "order by ", UTCField, " ",
+     if Order == reversed -> "desc, "; true -> ", " end,
+     "id ",
+     if Order == reversed -> "desc "; true -> "" end,
+     if Max /= undefined -> ["limit ", escape(Max)]; true -> "" end];
+
+get_request_part_range(UTCField, {{index, Index}, Max}) ->
+    ["order by ", UTCField, ", id ",
+     if Max /= undefined -> ["limit ", escape(Max)]; true -> "" end,
+     "offset ", escape(Index)].
+
+
+%%
+%% This function returns collections links that satisfy request restrictions
+%%
+get_collections_links(LUser, LServer, {RSM, Start, End}, JID) ->
+    SUS = get_us_escaped({LUser, LServer}),
+    SJID = if JID /= undefined -> get_jid_escaped(JID); true -> {undefined, undefined, undefined} end,
+    Count = get_collections_links_count(SUS, SJID, {Start, undefined}, {End, undefined}),
+    if Count == 0 ->
+	    {ok, [], []};
+       true ->
+	    Links = get_collections_links_query(SUS, SJID, RSM),
+	    CHTime = get_datetime_string_from_seconds(get_collections_links_change_time(SUS, SJID, Start, End)),
+	    if Links == [] ->
+		    {ok, [], make_rsm(undefined, undefined, undefined, CHTime, Count)};
+	       true ->
+		    Links1 = reverse_items_if_needed(Links, RSM),
+		    [{FirstID, _FirstJID, FirstUTC} | _] = Links1,
+		    {LastID, _LastJID, LastUTC} = lists:last(Links1),
+		    FirstIndex = get_collections_links_count(SUS, SJID, {Start, undefined},
+							     {FirstUTC, FirstID}),
+		    {ok, Links1, make_rsm(FirstIndex,
+					  make_rsm_range_item(FirstUTC, FirstID),
+					  make_rsm_range_item(LastUTC, LastID),
+					  CHTime,
+					  Count)}
+	    end
+    end.
+
+get_collections_link_req_where(SUS, {SUser, SServer, SResource}, AlsoDeleted) ->
+    ServerNonEmpty = is_non_empty(SServer),
+    UserNonEmpty = is_non_empty(SUser),
+    ResNonEmpty = is_non_empty(SResource),
+    ["where us = ", SUS, " ",
+     if AlsoDeleted -> "";
+        true -> "and deleted = 0 "
+     end,
+     if ServerNonEmpty == true -> ["and with_server = ", SServer, " "]; true -> "" end,
+     if UserNonEmpty == true -> ["and with_user = ", SUser, " "]; true -> "" end,
+     if ResNonEmpty == true -> ["and with_resource = ", SResource, " "]; true -> "" end].
+
+get_collections_links_count(SUS, SJID, Start, End) ->
+    get_collections_links_count_tmpl(SUS, SJID, "utc", false, Start, End).
+
+get_collections_links_count_tmpl(SUS, SJID, Field, AlsoDeleted, {Start, StartID}, {End, EndID}) ->
+    {selected, _, [{Count}]} =
+        run_sql_query(["select count(*) from archive_collections ",
+                       get_collections_link_req_where(SUS, SJID, AlsoDeleted),
+                       get_request_part_times(Field, {Start, StartID}, {End, EndID})]),
+    decode_integer(Count).
+
+get_collections_links_query(SUS, SJID, RSM) ->
+    case run_sql_query(["select id, with_user, with_server, with_resource, utc "
+                        "from archive_collections ",
+                        get_collections_link_req_where(SUS, SJID, false),
+                        get_request_part_range("utc", RSM)]) of
+        {selected, _, Rs} -> get_collections_links_list(Rs)
+    end.
+
+get_collections_links_change_time(SUS, SJID, Start, End) ->
+    case run_sql_query(["select max(change_utc) from archive_collections ",
+                        get_collections_link_req_where(SUS, SJID, false),
+                        get_request_part_times("utc", {Start, undefined}, {End, undefined})]) of
+        {selected, _, [{CHTime}]} -> decode_timestamp(CHTime)
+    end.
+
+get_collections_links_list(CLs) ->
+    lists:map(fun(CL) -> get_collection_link_from_query_result(CL) end, CLs).
+
+get_collection_link_from_query_result({ID, User, Server, Resource, UTC}) ->
+    %% We do not create a full-blown record here as we do not have enough info - and
+    %% just do not need it.
+    {decode_integer(ID),
+     jlib:jid_tolower(jlib:make_jid(User, Server, Resource)),
+     decode_timestamp(UTC)}.
+
+collection_link_to_xml(Name, {_, JID, UTC}) -> collection_link_to_xml(Name, {JID, UTC});
+
+collection_link_to_xml(Name, {JID, UTC}) ->
+    {xmlelement, Name,
+     [{"with", jlib:jid_to_string(JID)},
+      {"start", get_datetime_string_from_seconds(UTC)}],
+     []};
+
+collection_link_to_xml(_, _) -> [].
+
+
+%%
+%% This function returns full collection given its link
+%% If several collections exist (which would violate XEP-136,
+%% but seems to be still possible, though highly unlikely)
+%% it returns the first one of them.
+%%
+get_collection({LUser, LServer, JID, Start}) ->
+    SUS = get_us_escaped({LUser, LServer}),
+    {SUser, SServer, SRes} = get_jid_escaped(JID),
+    SUTC = encode_timestamp(Start),
+    case run_sql_query(["select * "
+                        "from archive_collections "
+                        "where us = ", SUS, " "
+                        "and deleted = 0 "
+                        "and with_user = ", SUser, " "
+                        "and with_server = ", SServer, " "
+                        "and with_resource = ", SRes, " "
+                        "and utc = ", SUTC]) of
+        {selected, _, [C | _]} -> get_collection_from_query_result(C);
+        _ -> throw({error, ?ERR_ITEM_NOT_FOUND})
+    end.
+
+get_collection_by_id(CID) ->
+    {selected, _, [C | _]} = run_sql_query(["select * "
+                                            "from archive_collections "
+                                            "where id = ", escape(CID)]),
+    get_collection_from_query_result(C).
+
+get_collection_from_query_result({CID, PrevId, NextId, US, User, Server, Resource, UTC,
+                                  ChBy, ChUTC, Deleted, Subject, Thread, Crypt, Extra}) ->
+    #archive_collection{id = decode_integer(CID),
+                        us = get_us_separated(US),
+                        jid = jlib:jid_tolower(jlib:make_jid(User, Server, Resource)),
+                        utc = decode_timestamp(UTC),
+                        prev = get_collection_link_by_id(decode_integer(PrevId)),
+                        next = get_collection_link_by_id(decode_integer(NextId)),
+                        change_by = case ChBy of
+                                        null -> {undefined, undefined, undefined};
+                                        R -> jlib:jid_tolower(jlib:string_to_jid(R))
+                                    end,
+                        change_utc = case ChUTC of
+                                         null -> undefined;
+                                         R -> decode_timestamp(R)
+                                     end,
+                        deleted = case decode_integer(Deleted) of
+                                      0 -> false;
+                                      1 -> true;
+                                      _ -> throw({error, ?ERR_INTERNAL_SERVER_ERROR})
+                                  end,
+                        subject = case Subject of
+                                      null -> undefined;
+                                      R -> R
+                                  end,
+                        thread = case Thread of
+                                     null -> undefined;
+                                     R -> R
+                                 end,
+                        crypt = case decode_integer(Crypt) of
+                                    null -> false;
+                                    R -> R == 1
+                                end,
+                        extra = case Extra of
+                                    null -> undefined;
+                                    R -> R
+                                end}.
+
+get_collection_link_by_id(null) -> [];
+
+get_collection_link_by_id(CID) ->
+    {selected, _, [{_, User, Server, Resource, UTC} | _]} =
+        run_sql_query(["select id, with_user, with_server, with_resource, utc "
+                       "from archive_collections "
+                       "where id = ", escape(CID)]),
+    {jlib:jid_tolower(jlib:make_jid(User, Server, Resource)), decode_timestamp(UTC)}.
+
+collection_to_xml(C) ->
+    PrevLink = collection_link_to_xml("previous", C#archive_collection.prev),
+    NextLink = collection_link_to_xml("next", C#archive_collection.next),
+    PrevXML = if PrevLink /= [] -> [PrevLink]; true -> [] end,
+    NextXML = if NextLink /= [] -> [NextLink]; true -> [] end,
+    ExtraNonEmpty = is_non_empty(C#archive_collection.extra),
+    ExtraXML = if ExtraNonEmpty == true -> [decode_extra(C#archive_collection.extra)]; true -> [] end,
+    {xmlelement, "chat",
+     lists:append([
+		   [{"with", jlib:jid_to_string(C#archive_collection.jid)}],
+		   [{"start", get_datetime_string_from_seconds(C#archive_collection.utc)}],
+		   if C#archive_collection.subject /= "",
+		      C#archive_collection.subject /= undefined ->
+			   [{"subject", C#archive_collection.subject}];
+		      true ->
+			   []
+		   end,
+		   if C#archive_collection.thread /= "",
+		      C#archive_collection.thread /= undefined ->
+			   [{"thread", C#archive_collection.thread}];
+		      true ->
+			   []
+		   end,
+		   if C#archive_collection.crypt -> [{"crypt", "true"}];
+		      true -> []
+		   end]),
+     lists:append([
+		   PrevXML,
+		   NextXML,
+		   ExtraXML])}.
+
+
+%%
+%% This function returns messages that satisfy request restrictions
+%%
+get_messages(C, RSM) ->
+    CID = C#archive_collection.id,
+    Count = get_messages_count(CID, {0, undefined}, {infinity, undefined}),
+    if Count == 0 ->
+	    {ok, [], []};
+       true ->
+	    Msgs = get_messages_query(CID, RSM),
+	    CHTime = get_datetime_string_from_seconds(C#archive_collection.change_utc),
+	    if Msgs == [] ->
+		    {ok, [], make_rsm(undefined, undefined, undefined, CHTime, Count)};
+	       true ->
+		    Msgs1 = reverse_items_if_needed(Msgs, RSM),
+		    [FirstMsg | _] = Msgs1,
+		    LastMsg = lists:last(Msgs1),
+		    {FirstUTC, FirstID} = {FirstMsg#archive_message.utc, FirstMsg#archive_message.id},
+		    {LastUTC, LastID} = {LastMsg#archive_message.utc, LastMsg#archive_message.id},
+		    FirstIndex = get_messages_count(CID, {0, undefined}, {FirstUTC, FirstID}),
+		    {ok, Msgs1, make_rsm(FirstIndex,
+					 make_rsm_range_item(FirstUTC, FirstID),
+					 make_rsm_range_item(LastUTC, LastID),
+					 CHTime,
+					 Count)}
+	    end
+    end.
+
+get_messages_count(CID, {Start, StartID}, {End, EndID}) ->
+    {selected, _, [{Count}]} =
+        run_sql_query(["select count(*) from archive_messages "
+                       "where coll_id = ", escape(CID), " ",
+                       get_request_part_times("utc", {Start, StartID}, {End, EndID})]),
+    decode_integer(Count).
+
+get_messages_query(CID, RSM) ->
+    case run_sql_query(["select * from archive_messages "
+                        "where coll_id = ", escape(CID), " ",
+                        get_request_part_range("utc", RSM)]) of
+        {selected, _, Rs} -> get_messages_list(Rs)
+    end.
+
+get_message_from_query_result({MID, CID, UTC, Dir, Body, Name}) ->
+    #archive_message{id = decode_integer(MID),
+                     coll_id = decode_integer(CID),
+                     utc = decode_timestamp(UTC),
+                     direction = case decode_integer(Dir) of
+                                     0 -> from;
+                                     1 -> to;
+                                     2 -> note
+                                 end,
+                     body = Body,
+                     name = Name}.
+
+get_messages_list(Msgs) ->
+    lists:map(fun(Msg) -> get_message_from_query_result(Msg) end, Msgs).
+
+message_to_xml(M, Start) ->
+    Dir = atom_to_list(M#archive_message.direction),
+    Secs = M#archive_message.utc - Start,
+    {xmlelement, Dir,
+     lists:append([
+		   if Dir == "note"; Secs < 0 ->
+			   UTCStr = get_datetime_string_from_seconds(M#archive_message.utc),
+			   [{"utc", UTCStr}];
+		      true -> [{"secs", integer_to_list(Secs)}]
+		   end,
+		   if M#archive_message.name /= "" -> [{"name", M#archive_message.name}];
+		      true -> []
+		   end]),
+     [if Dir == "note" -> {xmlcdata, M#archive_message.body};
+	 true -> {xmlelement, "body", [], [{xmlcdata, M#archive_message.body}]}
+      end]}.
+
+%%
+%% This function returns modifications that satisfy request restrictions.
+%%
+get_modified(LUser, LServer, {RSM, Start, End}) ->
+    SUS = get_us_escaped({LUser, LServer}),
+    Count = get_modified_count(SUS, {Start, undefined}, {End, undefined}),
+    if Count == 0 -> {ok, [], []};
+       true ->
+	    Changes = get_modified_raw(SUS, RSM),
+	    MaxCHTime = get_datetime_string_from_seconds(get_modified_max_change_time(SUS, Start, End)),
+	    if Changes == [] ->
+		    {ok, [], make_rsm(undefined, undefined, undefined, MaxCHTime, Count)};
+	       true ->
+		    Changes1 = reverse_items_if_needed(Changes, RSM),
+		    [FirstCH | _] = Changes1,
+		    LastCH = lists:last(Changes1),
+		    FirstUTC = FirstCH#archive_collection.change_utc,
+		    FirstID = FirstCH#archive_collection.id,
+		    LastUTC = LastCH#archive_collection.change_utc,
+		    LastID = LastCH#archive_collection.id,
+		    FirstIndex = get_modified_count(SUS, {Start, undefined},
+						    {FirstUTC, FirstID}),
+		    {ok, Changes1, make_rsm(FirstIndex,
+					    make_rsm_range_item(FirstUTC, FirstID),
+					    make_rsm_range_item(LastUTC, LastID),
+					    MaxCHTime,
+					    Count)}
+	    end
+    end.
+
+get_modified_count(SUS, Start, End) ->
+    get_collections_links_count_tmpl(SUS, {undefined, undefined, undefined},
+                                     "change_utc", true, Start, End).
+
+get_modified_raw(SUS, RSM) ->
+    {selected, _, Rs} =
+        run_sql_query(["select id, us, change_by, with_user, with_server, with_resource, "
+                       "utc, change_utc, deleted from archive_collections ",
+                       get_collections_link_req_where(SUS, {undefined, undefined, undefined}, true),
+                       get_request_part_range("change_utc", RSM)]),
+    lists:map(fun(Change) -> get_change_from_query_result(Change) end, Rs).
+
+get_modified_max_change_time(SUS, Start, End) ->
+    {selected, _, [{CHTime}]} =
+        run_sql_query(["select max(change_utc) ",
+                       "from archive_collections ",
+                       get_collections_link_req_where(SUS, {undefined, undefined, undefined}, true),
+                       get_request_part_times("change_utc", {Start, undefined}, {End, undefined})]),
+    decode_timestamp(CHTime).
+
+%%
+%% This is implementation of replication as specified in XEP-136. As the whole concept
+%% is broken (see below) you should not use it, it is provided onlt for compliance with
+%% the XEP.
+%%
+%% !!! NOTE !!! : poor decision about "after" usage in replication in XEP-136 breaks
+%% down things if there are several changes with the same time and RSM request stops
+%% somewhere between them - there's no way to get all remaining items.
+%%
+get_modified_legacy(LUser, LServer, {{range, {Start, undefined}, {_, _}, _}, Max}) ->
+    SUS = get_us_escaped({LUser, LServer}),
+    Secs = get_seconds_from_datetime_string(Start),
+    Count = get_modified_count(SUS, {Secs, undefined}, {infinity, undefined}),
+    if Count == 0 -> {ok, [], []};
+       true ->
+	    Changes = get_modified_raw(SUS, {{range, {Secs, undefined}, {infinity, undefined}, normal}, Max}),
+	    MaxCHTime = get_datetime_string_from_seconds(get_modified_max_change_time(SUS, Secs, infinity)),
+	    if Changes == [] ->
+		    {ok, [], make_rsm(undefined, undefined, undefined, MaxCHTime, Count)};
+	       true ->
+		    %% We do not check for reversing changes here - we do not process "before" in
+		    %% legacy mode anyway.
+		    [FirstCH | _] = Changes,
+		    LastCH = lists:last(Changes),
+		    FirstUTC = FirstCH#archive_collection.change_utc,
+		    LastUTC = LastCH#archive_collection.change_utc,
+		    FirstIndex = get_modified_count(SUS, {Secs, undefined},
+						    {FirstUTC, undefined}),
+		    {ok, Changes, make_rsm(FirstIndex,
+					   get_datetime_string_from_seconds(FirstUTC),
+					   get_datetime_string_from_seconds(LastUTC),
+					   MaxCHTime,
+					   Count)}
+	    end
+    end.
+
+get_change_from_query_result({CID, US, By, User, Server, Resource, UTC, CHUTC, Deleted}) ->
+    #archive_collection{id = decode_integer(CID),
+                        us = get_us_separated(US),
+                        change_by = jlib:jid_tolower(jlib:string_to_jid(By)),
+                        jid = jlib:jid_tolower(jlib:make_jid(User, Server, Resource)),
+                        utc = decode_timestamp(UTC),
+                        deleted = decode_integer(Deleted),
+                        change_utc = decode_timestamp(CHUTC)}.
+
+change_to_xml(C) ->
+    CHType =
+        case C#archive_collection.deleted of
+            1 -> "removed";
+            0 -> "changed"
+        end,
+    {xmlelement, CHType,
+     [{"with", jlib:jid_to_string(C#archive_collection.jid)},
+      {"start", get_datetime_string_from_seconds(C#archive_collection.utc)},
+      {"by", jlib:jid_to_string(C#archive_collection.change_by)}], []}.
+
+
+
+%%
+%% Preferences-related retrieval functions
+%%
+
+get_global_prefs(US) ->
+    SUS = get_us_escaped(US),
+    case run_sql_query(["select * from archive_global_prefs "
+                        "where us = ", SUS]) of
+        {selected, _, [C | _]} -> get_global_prefs_from_query_result(C);
+        _ -> #archive_global_prefs{}
+    end.
+
+get_global_prefs_from_query_result({US, Save, Expire, OTR,
+                                    MAuto, MLocal, MManual, AutoSave}) ->
+    {RSave, RExpire, ROTR} = get_common_prefs_from_query_result(Save, Expire, OTR),
+    #archive_global_prefs{us = get_us_separated(US),
+                          save = RSave,
+                          expire = RExpire,
+                          otr = ROTR,
+                          method_auto = get_method_from_query_result(MAuto),
+                          method_local = get_method_from_query_result(MLocal),
+                          method_manual = get_method_from_query_result(MManual),
+                          auto_save = case decode_integer(AutoSave) of
+                                          1 -> true;
+                                          0 -> false;
+                                          null -> undefined
+                                      end}.
+
+get_method_from_query_result(Method) ->
+    case decode_integer(Method) of
+        0 -> prefer;
+        1 -> concede;
+        2 -> forbid;
+        _ -> undefined
+    end.
+
+get_jid_prefs(US, JID) ->
+    SUS = get_us_escaped(US),
+    {SUser, SServer, SRes} = get_jid_escaped(JID),
+    case run_sql_query(["select * from archive_jid_prefs "
+                        "where us = ", SUS, " "
+                        "and with_user = ", SUser, " "
+                        "and with_server = ", SServer, " "
+                        "and with_resource = ", SRes]) of
+        {selected, _, [C | _]} -> get_jid_prefs_from_query_result(C);
+        _ -> #archive_jid_prefs{}
+    end.
+
+get_all_jids_prefs(US) ->
+    SUS = get_us_escaped(US),
+    case run_sql_query(["select * from archive_jid_prefs "
+                        "where us = ", SUS]) of
+        {selected, _, Rs} -> lists:map(fun(P) -> get_jid_prefs_from_query_result(P) end, Rs);
+        _ -> throw({error, ?ERR_INTERNAL_SERVER_ERROR})
+    end.
+
+get_jid_prefs_from_query_result({US, User, Server, Resource, Save, Expire, OTR}) ->
+    {RSave, RExpire, ROTR} =
+        get_common_prefs_from_query_result(Save, Expire, OTR),
+    #archive_jid_prefs{us = get_us_separated(US),
+                       jid = jlib:jid_tolower(jlib:make_jid(User, Server, Resource)),
+                       save = RSave,
+                       expire = RExpire,
+                       otr = ROTR}.
+
+get_common_prefs_from_query_result(Save, Expire, OTR) ->
+    {case decode_integer(Save) of
+         1 -> body;
+         0 -> false;
+         _ -> undefined
+     end,
+     case decode_integer(Expire) of
+         null -> undefined;
+         N -> N
+     end,
+     case decode_integer(OTR) of
+         0 -> approve;
+         1 -> concede;
+         2 -> forbid;
+         3 -> oppose;
+         4 -> prefer;
+         5 -> require;
+         _ -> undefined
+     end}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Dealing with collections expiration
+%%
+%% TODO: looks scaring, but I do not see any other realistic way to do it
+%% without involving the caller ...
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+expire_collections(Host) ->
+    STS = encode_timestamp(get_timestamp()),
+
+    ExpiredByPrefJID = [get_expired_str(Host, "archive_jid_prefs.expire", "utc"), " < ", STS],
+    ExpiredByPrefGlobal = [get_expired_str(Host, "archive_global_prefs.expire", "utc"), " < ", STS],
+    ExpiredByDefault = case gen_mod:get_module_opt(Host, ?MODULE, default_expire, infinity) of
+                           infinity -> "";
+                           N -> [get_expired_str(Host, integer_to_list(N), "utc"), " < ", STS]
+                       end,
+
+    ExistsFullJID = ["exists (select * from archive_jid_prefs "
+                     "where archive_collections.us = archive_jid_prefs.us "
+                     "and archive_collections.with_server = archive_jid_prefs.with_server "
+                     "and archive_collections.with_user = archive_jid_prefs.with_user "
+                     "and archive_collections.with_resource = archive_jid_prefs.with_resource"],
+    ExistsBareJID = ["exists (select * from archive_jid_prefs "
+                     "where archive_collections.us = archive_jid_prefs.us "
+                     "and archive_collections.with_server = archive_jid_prefs.with_server "
+                     "and archive_collections.with_user = archive_jid_prefs.with_user "
+                     "and archive_jid_prefs.with_resource = ''"],
+    ExistsDomainJID = ["exists (select * from archive_jid_prefs "
+                       "where archive_collections.us = archive_jid_prefs.us "
+                       "and archive_collections.with_server = archive_jid_prefs.with_server "
+                       "and archive_jid_prefs.with_user = '' "
+                       "and archive_jid_prefs.with_resource = ''"],
+    ExistsGlobal =  ["exists (select * from archive_global_prefs "
+                     "where archive_collections.us = archive_global_prefs.us"],
+
+    F = fun() ->
+		run_sql_query([
+			       "update archive_collections "
+			       "set deleted = 1, "
+			       "change_by = ", escape(Host), ", "
+			       "change_utc = ", STS, " "
+			       "where deleted = 0 and (",
+
+			       ExistsFullJID, " and ", ExpiredByPrefJID, ") "
+
+			       "or not ", ExistsFullJID, ") and ", ExistsBareJID, " and ", ExpiredByPrefJID, ") "
+
+			       "or not ", ExistsFullJID, ") and not ", ExistsBareJID, ") and ", ExistsDomainJID,
+			       " and ", ExpiredByPrefJID, ") "
+
+			       "or not ", ExistsFullJID, ") and not ", ExistsBareJID, ") and not ", ExistsDomainJID, ") "
+			       "and ", ExistsGlobal, " and ", ExpiredByPrefGlobal, ") ",
+
+			       if ExpiredByDefault /= "" ->
+				       ["or not ", ExistsFullJID, ") and not ", ExistsBareJID, ") and not ", ExistsDomainJID, ") "
+					"and not ", ExistsGlobal, ") and ", ExpiredByDefault];
+				  true -> ""
+			       end,
+			       ")"]),
+		case gen_mod:get_module_opt(Host, ?MODULE, replication_expire, 31536000) of
+		    infinity -> [];
+		    N1 ->
+			run_sql_query(["delete from archive_collections "
+				       "where deleted = 1 "
+				       "and ", get_expired_str(Host, integer_to_list(N1), "change_utc"), " < ", STS])
+		end
+        end,
+    run_sql_transaction(Host, F).
+
+get_expired_str(Host, ExpExpr, UTCField) ->
+    case jlib:tolower(gen_mod:get_module_opt(Host, ?MODULE, database_type, "")) of
+        "mysql" -> ["timestampadd(second, ", ExpExpr, ", archive_collections.", UTCField, ")"];
+        "sqlite" -> ["datetime(archive_collections.", UTCField, ", '+' || ", ExpExpr, " || ' seconds')"];
+        "pgsql" -> ["timestamp archive_collections.", UTCField, " + interval ", ExpExpr, " || ' seconds'"];
+        _ -> throw({error, ?ERR_INTERNAL_SERVER_ERROR})
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Utility functions to make database interaction easier.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Noone seems to follow standards these days :-(
+%% We have to perform DB-specific escaping,as f.e. SQLite does not understand
+%% '\' as escaping character (which is exactly in accordance with the standard,
+%% by the way), while most other DBs do.
+
+%% Generic, DB-independent escaping for integers and simple strings.
+escape(null) ->
+    "null";
+escape(undefined) ->
+    "null";
+escape(infinity) ->
+    integer_to_list(?INFINITY);
+escape(Num) when is_integer(Num) ->
+    integer_to_list(Num);
+escape(Str) ->
+    "'" ++ [escape_chars(C) || C <- Str] ++ "'".
+
+%% DB-specific strings escaping.
+escape_str(_, null) ->
+    "null";
+escape_str(_, undefined) ->
+    "null";
+escape_str(LServer, Str) ->
+    case jlib:tolower(gen_mod:get_module_opt(LServer, ?MODULE, database_type, "")) of
+        "sqlite" -> "'" ++ [escape_chars(C) || C <- Str] ++ "'";
+	_ -> "'" ++ ejabberd_odbc:escape(Str) ++ "'"
+    end.
+
+%% Characters to escape
+escape_chars($')  -> "''";
+escape_chars(C)  -> C.
+
+%% Assume that if there are no sub-elements for "x" tag - this is
+%% extra info removal request
+encode_extra({xmlelement, "x", _, []}) ->
+    "";
+%% We could try to use BLOBs here, but base64 in text columns should
+%% be more porable and should be enough - it's unlikely someone
+%% will store much info here anyway.
+encode_extra(Extra) ->
+    jlib:encode_base64(binary_to_list(term_to_binary(Extra))).
+
+decode_extra(Extra) ->
+    binary_to_term(list_to_binary(jlib:decode_base64(Extra))).
+
+encode_timestamp(infinity) ->
+    escape(get_sql_datetime_string_from_seconds(?INFINITY));
+
+encode_timestamp(TS) ->
+    escape(get_sql_datetime_string_from_seconds(TS)).
+
+decode_timestamp(Str) ->
+    get_seconds_from_sql_datetime_string(Str).
+
+timestamp_to_integer(infinity) ->
+    ?INFINITY;
+timestamp_to_integer(Num) ->
+    Num.
+
+get_us_escaped({LUser, LServer}) ->
+    escape(LUser ++ "@" ++ LServer).
+
+get_us_separated(US) ->
+    JID = jlib:string_to_jid(US),
+    #jid{luser = LUser, lserver = LServer} = JID,
+    {LUser, LServer}.
+
+get_jid_escaped({LUser, LServer, LResource}) ->
+    {escape(LUser), escape(LServer), escape(LResource)}.
+
+get_jid_full_escaped({LUser, LServer, undefined}) ->
+    escape(LUser ++ "@" ++ LServer);
+get_jid_full_escaped({LUser, LServer, ""}) ->
+    escape(LUser ++ "@" ++ LServer);
+get_jid_full_escaped({LUser, LServer, LResource}) ->
+    escape(LUser ++ "@" ++ LServer ++ "/" ++ LResource).
+
+decode_integer(Val) when is_integer(Val) ->
+    Val;
+decode_integer(null) ->
+    null;
+decode_integer(Val) ->
+    list_to_integer(Val).
+
+is_non_empty(null) -> false;
+is_non_empty(undefined) -> false;
+is_non_empty("") -> false;
+is_non_empty("''") -> false;
+is_non_empty(_) -> true.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Date-time handling.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_seconds_from_datetime_string(Str) ->
+    case jlib:datetime_string_to_timestamp(Str) of
+        undefined -> throw({error, ?ERR_BAD_REQUEST});
+        No ->
+	    calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(No))
+    end.
+
+get_datetime_string_from_seconds(Secs) ->
+    Zero = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+    Secs2 = Secs - Zero,
+    jlib:now_to_utc_string({Secs2 div 1000000, Secs2 rem 1000000, 0}).
+
+get_seconds_from_sql_datetime_string(Str) ->
+    case sql_datetime_string_to_timestamp(Str) of
+        undefined -> throw({error, ?ERR_BAD_REQUEST});
+        No ->
+	    calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(No))
+    end.
+
+get_sql_datetime_string_from_seconds(Secs) ->
+    Zero = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+    Secs2 = Secs - Zero,
+    now_to_utc_sql_datetime({Secs2 div 1000000, Secs2 rem 1000000, 0}).
+
+%% We do not output MicroSecs as our timestamps are seconds-based anyway, also
+%% it may help to be more portable between SQL servers.
+now_to_utc_sql_datetime({MegaSecs, Secs, MicroSecs}) ->
+    {{Year, Month, Day}, {Hour, Minute, Second}} =
+	calendar:now_to_universal_time({MegaSecs, Secs, MicroSecs}),
+    lists:flatten(
+      io_lib:format("~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",
+		    [Year, Month, Day, Hour, Minute, Second])).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Copy-paste-modified from jlib.erl, as jlib:datetime_string_to_timestamp does not tolerate SQL syntax.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% 'yyyy-mm-dd hh:mm:ss[.sss]' -> {MegaSecs, Secs, MicroSecs}
+sql_datetime_string_to_timestamp(TimeStr) ->
+    case catch parse_sql_datetime(TimeStr) of
+	{'EXIT', _Err} ->
+	    undefined;
+	TimeStamp ->
+	    TimeStamp
+    end.
+
+parse_sql_datetime(TimeStr) ->
+    [Date, Time] = string:tokens(TimeStr, " "),
+    D = parse_date(Date),
+    {T, MS, TZH, TZM} = parse_time(Time),
+    S = calendar:datetime_to_gregorian_seconds({D, T}),
+    S1 = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+    Seconds = (S - S1) - TZH * 60 * 60 - TZM * 60,
+    {Seconds div 1000000, Seconds rem 1000000, MS}.
+
+%% yyyy-mm-dd
+parse_date(Date) ->
+    [Y, M, D] = string:tokens(Date, "-"),
+    Date1 = {list_to_integer(Y), list_to_integer(M), list_to_integer(D)},
+    case calendar:valid_date(Date1) of
+	true ->
+	    Date1;
+	_ ->
+	    false
+    end.
+
+%% hh:mm:ss[.sss]
+parse_time(Time) ->
+    [HMS | T] =  string:tokens(Time, "."),
+    MS = case T of
+	     [] ->
+		 0;
+	     [Val] ->
+		 list_to_integer(string:left(Val, 6, $0))
+	 end,
+    [H, M, S] = string:tokens(HMS, ":"),
+    {[H1, M1, S1], true} = check_list([{H, 24}, {M, 60}, {S, 60}]),
+    {{H1, M1, S1}, MS, 0, 0}.
+
+check_list(List) ->
+    lists:mapfoldl(
+      fun({L, N}, B)->
+	      V = list_to_integer(L),
+	      if
+		  (V >= 0) and (V =< N) ->
+		      {V, B};
+		  true ->
+		      {false, false}
+	      end
+      end, true, List).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% End of copy-paste-modified
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_timestamp() ->
+    calendar:datetime_to_gregorian_seconds(calendar:universal_time()).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Wrapper functions to perform queries and transactions.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+run_sql_query(Query) ->
+    %%?MYDEBUG("running query: ~p", [lists:flatten(Query)]),
+    case catch ejabberd_odbc:sql_query_t(Query) of
+        {'EXIT', Err} ->
+            ?ERROR_MSG("unhandled exception during query: ~p", [Err]),
+            exit(Err);
+        {error, Err} ->
+            ?ERROR_MSG("error during query: ~p", [Err]),
+            throw({error, Err});
+        aborted ->
+            ?ERROR_MSG("query aborted", []),
+            throw(aborted);
+        R -> %?MYDEBUG("query result: ~p", [R]),
+	    R
+    end.
+
+run_sql_transaction(LServer, F) ->
+    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+    case ejabberd_odbc:sql_transaction(DBHost, F) of
+        {atomic, R} ->
+	    %%?MYDEBUG("succeeded transaction: ~p", [R]),
+	    R;
+        {error, Err} -> {error, Err};
+        E ->
+            ?ERROR_MSG("failed transaction: ~p, stack: ~p", [E, process_info(self(),backtrace)]),
+            {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+%% return either  {error, Err}  or {LUser, LServer, Jid, Start}
+link_from_argument(LUser, LServer,  Elem) ->
+    case parse_root_argument(Elem) of
+        {error, E} ->  {error, E};
+        {interval, Start, _, JID} when Start /= 0,
+                                       JID /= undefined ->
+            {LUser, LServer, JID, Start};
+        _ -> throw({error, ?ERR_BAD_REQUEST})
+    end.
+
+%%parse commons arguments of root elements
+
+parse_root_argument({xmlelement, _, Attrs, _}) ->
+    With = xml:get_attr_s("with", Attrs),
+    Start = xml:get_attr_s("start", Attrs),
+    End = xml:get_attr_s("end", Attrs),
+    {interval,
+     if Start /= "" -> get_seconds_from_datetime_string(Start); true -> 0 end,
+     if End /= "" -> get_seconds_from_datetime_string(End); true -> infinity end,
+     if With /= "" -> jlib:jid_tolower(jlib:string_to_jid(With)); true -> undefined end}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  Result Set Management (JEP-0059)
+%%
+%%
+-define(MY_NS_RSM, "http://jabber.org/protocol/rsm").
+
+
+%% If "index" is specified, returns {{index, Index}, Max},
+%% otherwise returns {{range, {StartTime, StartID}, {EndTime, EndID}, Order}, Max}
+%% where Order == 'normal' means that up to Max elements should be output
+%% from Start, and 'reversed' - from End respectively.
+
+%% !!! TODO: rewrite in "parse_root_argument" style, without recursion.
+
+parse_rsm([A | Tail]) ->
+    case A of
+        {xmlelement, _,  Attrs1, _} ->
+            case xml:get_attr_s("xmlns", Attrs1) of
+                ?MY_NS_RSM ->
+                    parse_rsm(A);
+                _ ->
+                    parse_rsm(Tail)
+            end;
+        _ ->
+            parse_rsm(Tail)
+    end;
+parse_rsm([]) ->
+    {{range, {0, undefined}, {infinity, undefined}, normal}, undefined};
+
+parse_rsm({xmlelement, "set", _, SubEls}) ->
+    parse_rsm_aux(SubEls, {{range, {0, undefined}, {infinity, undefined}, normal}, undefined});
+
+parse_rsm(_) ->
+    throw({error, ?ERR_BAD_REQUEST}).
+
+parse_rsm_aux([{xmlelement, "max", _Attrs, Contents} | Tail], Acc) ->
+    case catch list_to_integer(xml:get_cdata(Contents)) of
+        P when is_integer(P) ->
+            case Acc of
+                {Req, undefined} ->
+                    parse_rsm_aux(Tail, {Req, P});
+                _ ->
+                    throw({error, ?ERR_BAD_REQUEST})
+            end;
+        _ ->
+            throw({error, ?ERR_BAD_REQUEST})
+    end;
+
+parse_rsm_aux([{xmlelement, "index", _Attrs, Contents} | Tail], Acc) ->
+    case catch list_to_integer(xml:get_cdata(Contents)) of
+        P when is_integer(P) ->
+            case Acc of
+                {{range, {0, undefined}, {infinity, undefined}, normal}, Max} ->
+                    parse_rsm_aux(Tail, {{index, P}, Max});
+                _ ->
+                    throw({error, ?ERR_BAD_REQUEST})
+            end;
+        _ ->
+            throw({error, ?ERR_BAD_REQUEST})
+    end;
+
+parse_rsm_aux([{xmlelement, "after", _Attrs, Contents} | Tail], Acc) ->
+    case Acc of
+        {{range, {0, undefined}, {infinity, undefined}, normal}, Max} ->
+            parse_rsm_aux(Tail, {{range, parse_rsm_range_item(xml:get_cdata(Contents)), {infinity, undefined}, normal}, Max});
+        _ ->
+            throw({error, ?ERR_BAD_REQUEST})
+    end;
+
+parse_rsm_aux([{xmlelement, "before", _Attrs, Contents} | Tail], Acc) ->
+    case Acc of
+        {{range, {0, undefined}, {infinity, undefined}, normal}, Max} ->
+            BT = case xml:get_cdata(Contents) of
+                     [] -> {infinity, undefined};
+                     CD -> parse_rsm_range_item(CD)
+		 end,
+            parse_rsm_aux(Tail, {{range, {0, undefined}, BT, reversed}, Max});
+        _ ->
+            throw({error, ?ERR_BAD_REQUEST})
+    end;
+
+parse_rsm_aux([_ | Tail], Acc) ->
+    parse_rsm_aux(Tail, Acc);
+parse_rsm_aux([], Acc) ->
+    Acc.
+
+make_rsm(undefined, undefined, undefined, Changed, Count) ->
+    [{xmlelement, "set", [{"xmlns", ?MY_NS_RSM}], [
+					       {xmlelement, "changed", [], [{xmlcdata,  Changed}]},
+					       {xmlelement, "count", [], [{xmlcdata, integer_to_list(Count)}]}]}];
+
+make_rsm(FirstIndex, FirstId, LastId, Changed, Count) ->
+    [{xmlelement, "set", [{"xmlns", ?MY_NS_RSM}], [
+						{xmlelement, "first", [{"index", integer_to_list(FirstIndex)}], [{xmlcdata,  FirstId}]},
+						{xmlelement, "last", [], [{xmlcdata,  LastId}]},
+						{xmlelement, "changed", [], [{xmlcdata,  Changed}]},
+						{xmlelement, "count", [], [{xmlcdata,  integer_to_list(Count)}]}]}].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Utility functions for RSM
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+parse_rsm_range_item(Item) ->
+    Len = string:len(Item),
+    Pos = string:chr(Item, $@),
+    if Pos == 0 ->
+	    %% It must be either bad request or stupid RSM-136 special case for replication :-(
+	    %% It's not easy to distinguish between them here, so we just return at least smth,
+	    %% so that it can be dealt later with.
+	    {Item, undefined};
+       true ->
+	    %% we do not care about exact length in second "sublist", it should only be bigger than string length.
+	    {list_to_integer(lists:sublist(Item, Pos - 1)), list_to_integer(lists:sublist(Item, Pos + 1, Len))}
+    end.
+
+make_rsm_range_item(UTC, ID) ->
+    integer_to_list(UTC) ++ "@" ++ integer_to_list(ID).
diff --git a/mod_archive/src/mod_archive_odbc_mysql.sql b/mod_archive/src/mod_archive_odbc_mysql.sql
new file mode 100644
index 0000000..2b3ee7b
--- /dev/null
+++ b/mod_archive/src/mod_archive_odbc_mysql.sql
@@ -0,0 +1,83 @@
+CREATE DATABASE IF NOT EXISTS ejabberd CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+USE ejabberd;
+
+SET table_type=InnoDB;
+
+CREATE TABLE archive_collections(id INTEGER NOT NULL AUTO_INCREMENT,
+                                 prev_id INTEGER,
+                                 next_id INTEGER,
+                                 us VARCHAR(2047) NOT NULL,
+                                 with_user VARCHAR(1023) NOT NULL,
+                                 with_server VARCHAR(1023) NOT NULL,
+                                 with_resource VARCHAR(1023) NOT NULL,
+                                 utc DATETIME NOT NULL,
+                                 change_by VARCHAR(3071),
+                                 change_utc DATETIME,
+                                 deleted TINYINT,
+                                 subject VARCHAR(1023),
+                                 thread VARCHAR(1023),
+                                 crypt TINYINT,
+                                 extra VARCHAR(32767),
+                                 PRIMARY KEY(id))
+                                 CHARACTER SET utf8
+                                 COLLATE utf8_general_ci;
+CREATE INDEX IDX_archive_colls_with ON archive_collections(us(16),with_user(8),with_server(8),utc);
+CREATE INDEX IDX_archive_colls_prev_id ON archive_collections(prev_id);
+CREATE INDEX IDX_archive_colls_next_id ON archive_collections(next_id);
+CREATE INDEX IDX_archive_colls_utc ON archive_collections(us(16),utc);
+CREATE INDEX IDX_archive_colls_change ON archive_collections(deleted,change_utc);
+
+CREATE TABLE archive_messages(id INTEGER NOT NULL AUTO_INCREMENT,
+                              coll_id INTEGER NOT NULL,
+                              utc DATETIME NOT NULL,
+                              dir TINYINT,
+                              body VARCHAR(63488),
+                              name VARCHAR(1023),
+                              PRIMARY KEY(id))
+                              CHARACTER SET utf8
+                              COLLATE utf8_general_ci;
+CREATE INDEX IDX_archive_msgs_coll_id ON archive_messages(coll_id,utc);
+
+CREATE TABLE archive_jid_prefs(us VARCHAR(2047) NOT NULL,
+                               with_user VARCHAR(1023) NOT NULL,
+                               with_server VARCHAR(1023) NOT NULL,
+                               with_resource VARCHAR(1023) NOT NULL,
+                               save TINYINT,
+                               expire INTEGER,
+                               otr TINYINT,
+                               PRIMARY KEY  (us(16),with_user(8),with_server(8),with_resource(8)))
+                               CHARACTER SET utf8
+                               COLLATE utf8_general_ci;
+
+CREATE TABLE archive_global_prefs(us VARCHAR(2047) NOT NULL,
+                                  save TINYINT,
+                                  expire INTEGER,
+                                  otr TINYINT,
+                                  method_auto TINYINT,
+                                  method_local TINYINT,
+                                  method_manual TINYINT,
+                                  auto_save TINYINT,
+                                  PRIMARY KEY  (us(16)))
+                                  CHARACTER SET utf8
+                                  COLLATE utf8_general_ci;
+
+DELIMITER |
+
+CREATE TRIGGER archive_collections_delete BEFORE DELETE ON archive_collections
+FOR EACH ROW
+BEGIN
+  DELETE FROM archive_messages WHERE coll_id = OLD.id;
+END;
+|
+
+CREATE TRIGGER archive_collections_update BEFORE UPDATE ON archive_collections
+FOR EACH ROW
+BEGIN
+  IF NEW.deleted = 1 THEN
+    DELETE FROM archive_messages WHERE coll_id = NEW.id;
+  END IF;
+END;
+|
+
+DELIMITER ;
diff --git a/mod_archive/src/mod_archive_odbc_pgsql.sql b/mod_archive/src/mod_archive_odbc_pgsql.sql
new file mode 100644
index 0000000..32b12b6
--- /dev/null
+++ b/mod_archive/src/mod_archive_odbc_pgsql.sql
@@ -0,0 +1,72 @@
+DROP TABLE archive_collections;
+
+CREATE TABLE archive_collections(id SERIAL not null,
+                                 prev_id INTEGER,
+                                 next_id INTEGER,
+                                 us VARCHAR(2047) NOT NULL,
+                                 with_user VARCHAR(1023) NOT NULL,
+                                 with_server VARCHAR(1023) NOT NULL,
+                                 with_resource VARCHAR(1023) NOT NULL,
+                                 utc timestamp NOT NULL,
+                                 change_by VARCHAR(3071),
+                                 change_utc timestamp,
+                                 deleted INTEGER,
+                                 subject VARCHAR(1023),
+                                 thread VARCHAR(1023),
+                                 crypt INTEGER,
+                                 extra VARCHAR(32767),
+                                 PRIMARY KEY(id));
+CREATE INDEX IDX_archive_colls_prev_id ON archive_collections(prev_id);
+CREATE INDEX IDX_archive_colls_next_id ON archive_collections(next_id);
+CREATE INDEX IDX_archive_colls_us ON archive_collections(us);
+CREATE INDEX IDX_archive_colls_with_server ON archive_collections(with_server);
+CREATE INDEX IDX_archive_colls_with_user ON archive_collections(with_user);
+CREATE INDEX IDX_archive_colls_with_resource ON archive_collections(with_resource);
+CREATE INDEX IDX_archive_colls_utc ON archive_collections(utc);
+CREATE INDEX IDX_archive_colls_change_utc ON archive_collections(change_utc);
+
+DROP TABLE archive_messages;
+CREATE TABLE archive_messages(id SERIAL NOT NULL,
+                              coll_id INTEGER NOT NULL,
+                              utc timestamp NOT NULL,
+                              dir INTEGER,
+                              body VARCHAR(65535),
+                              name VARCHAR(1023),
+                              PRIMARY KEY(id));
+CREATE INDEX IDX_archive_msgs_coll_id ON archive_messages(coll_id);
+CREATE INDEX IDX_archive_msgs_utc ON archive_messages(utc);
+
+DROP TABLE archive_jid_prefs;
+CREATE TABLE archive_jid_prefs(us VARCHAR(2047) NOT NULL,
+                               with_user VARCHAR(1023) NOT NULL,
+                               with_server VARCHAR(1023) NOT NULL,
+                               with_resource VARCHAR(1023) NOT NULL,
+                               save integer,
+                               expire INTEGER,
+                               otr integer);
+CREATE INDEX IDX_archive_jid_prefs_us ON archive_jid_prefs(us);
+CREATE INDEX IDX_archive_jid_prefs_with_user ON archive_jid_prefs(with_user);
+CREATE INDEX IDX_archive_jid_prefs_with_server ON archive_jid_prefs(with_server);
+CREATE INDEX IDX_archive_jid_prefs_with_resource ON archive_jid_prefs(with_resource);
+
+DROP TABLE archive_global_prefs;
+CREATE TABLE archive_global_prefs(us VARCHAR(2047) NOT NULL,
+                                  save integer,
+                                  expire INTEGER,
+                                  otr integer,
+                                  method_auto integer,
+                                  method_local integer,
+                                  method_manual integer,
+                                  auto_save integer);
+CREATE INDEX IDX_archive_global_prefs_us ON archive_global_prefs(us);
+
+
+CREATE RULE archive_collections_delete AS ON DELETE 
+    TO archive_collections
+    DO DELETE FROM archive_messages WHERE coll_id = OLD.id;
+    
+CREATE RULE archive_collections_update AS ON UPDATE
+    TO archive_collections
+    DO DELETE FROM archive_messages WHERE coll_id = NEW.id and NEW.deleted=1;
+    
+
diff --git a/mod_archive/src/mod_archive_odbc_sqlite3.sql b/mod_archive/src/mod_archive_odbc_sqlite3.sql
new file mode 100644
index 0000000..6363217
--- /dev/null
+++ b/mod_archive/src/mod_archive_odbc_sqlite3.sql
@@ -0,0 +1,70 @@
+CREATE TABLE archive_collections(id INTEGER NOT NULL,
+                                 prev_id INTEGER,
+                                 next_id INTEGER,
+                                 us VARCHAR(2047) NOT NULL,
+                                 with_user VARCHAR(1023) NOT NULL,
+                                 with_server VARCHAR(1023) NOT NULL,
+                                 with_resource VARCHAR(1023) NOT NULL,
+                                 utc DATETIME NOT NULL,
+                                 change_by VARCHAR(3071),
+                                 change_utc DATETIME,
+                                 deleted INTEGER,
+                                 subject VARCHAR(1023),
+                                 thread VARCHAR(1023),
+                                 crypt INTEGER,
+                                 extra VARCHAR(32767),
+                                 PRIMARY KEY(id));
+CREATE INDEX IDX_archive_colls_prev_id ON archive_collections(prev_id);
+CREATE INDEX IDX_archive_colls_next_id ON archive_collections(next_id);
+CREATE INDEX IDX_archive_colls_us ON archive_collections(us);
+CREATE INDEX IDX_archive_colls_with_server ON archive_collections(with_server);
+CREATE INDEX IDX_archive_colls_with_user ON archive_collections(with_user);
+CREATE INDEX IDX_archive_colls_with_resource ON archive_collections(with_resource);
+CREATE INDEX IDX_archive_colls_utc ON archive_collections(utc);
+CREATE INDEX IDX_archive_colls_change_utc ON archive_collections(change_utc);
+
+CREATE TABLE archive_messages(id INTEGER NOT NULL,
+                              coll_id INTEGER NOT NULL,
+                              utc DATETIME NOT NULL,
+                              dir INTEGER,
+                              body VARCHAR(65535),
+                              name VARCHAR(1023),
+                              PRIMARY KEY(id));
+CREATE INDEX IDX_archive_msgs_coll_id ON archive_messages(coll_id);
+CREATE INDEX IDX_archive_msgs_utc ON archive_messages(utc);
+
+CREATE TABLE archive_jid_prefs(us VARCHAR(2047) NOT NULL,
+                               with_user VARCHAR(1023) NOT NULL,
+                               with_server VARCHAR(1023) NOT NULL,
+                               with_resource VARCHAR(1023) NOT NULL,
+                               save INTEGER,
+                               expire INTEGER,
+                               otr INTEGER,
+                               PRIMARY KEY(us, with_user, with_server, with_resource));
+CREATE INDEX IDX_archive_jid_prefs_us ON archive_jid_prefs(us);
+
+CREATE TABLE archive_global_prefs(us VARCHAR(2047) NOT NULL,
+                                  save INTEGER,
+                                  expire INTEGER,
+                                  otr INTEGER,
+                                  method_auto INTEGER,
+                                  method_local INTEGER,
+                                  method_manual INTEGER,
+                                  auto_save INTEGER,
+                                  PRIMARY KEY(us));
+
+CREATE TRIGGER archive_collections_delete BEFORE DELETE ON archive_collections
+FOR EACH ROW
+BEGIN
+  DELETE FROM archive_messages WHERE coll_id = OLD.id;
+  UPDATE archive_collections SET prev_id = null WHERE prev_id = OLD.id;
+  UPDATE archive_collections SET next_id = null WHERE next_id = OLD.id;
+END;
+
+CREATE TRIGGER archive_collections_update BEFORE UPDATE ON archive_collections
+FOR EACH ROW WHEN NEW.deleted = 1
+BEGIN
+  DELETE FROM archive_messages WHERE coll_id = NEW.id;
+  UPDATE archive_collections SET prev_id = null WHERE prev_id = NEW.id;
+  UPDATE archive_collections SET next_id = null WHERE next_id = NEW.id;
+END;
\ No newline at end of file
diff --git a/mod_archive/src/mod_archive_sql.erl b/mod_archive/src/mod_archive_sql.erl
new file mode 100644
index 0000000..27f8150
--- /dev/null
+++ b/mod_archive/src/mod_archive_sql.erl
@@ -0,0 +1,1379 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_archive_sql.erl
+%%% Author  : Olivier Goffart <ogoffar@kde.org>
+%%% Purpose : Message Archiving using SQL DB (JEP-0136)
+%%% Created : 19 Aug 2006 by Olivier Goffart <ogoffar@kde.org>
+%%%----------------------------------------------------------------------
+
+%% Options:
+%%  save_default -> true | false      if messages are stored by default or not
+%%  session_duration ->  time in secondes before the timeout of a session
+
+
+-module(mod_archive_sql).
+-author('ogoffart@kde.org').
+-author('alexey@process-one.net').
+
+-behaviour(gen_server).
+-behaviour(gen_mod).
+
+-export([start_link/2, start/2, stop/1,
+	 remove_user/2,
+	 send_packet/3,
+	 receive_packet/3,
+	 receive_packet/4,
+         process_iq/3, process_local_iq/3,
+	 get_disco_features/5]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+	 terminate/2, code_change/3]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+
+-record(state, {host,
+		sessions,
+		save_default,
+		session_duration}).
+
+-define(PROCNAME, ejabberd_mod_archive_sql).
+-define(NS_ARCHIVE,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns").
+-define(NS_ARCHIVE_MANAGE,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns-manage").
+-define(NS_ARCHIVE_PREF,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns-pref").
+-define(NS_ARCHIVE_MANUAL,
+	"http://www.xmpp.org/extensions/xep-0136.html#ns-manual").
+-define(INFINITY, calendar:datetime_to_gregorian_seconds({{2038,1,19},{0,0,0}})).
+-define(DICT, dict).
+
+-define(MYDEBUG(Format, Args),
+	io:format("D(~p:~p:~p) : " ++ Format ++ "~n",
+		  [calendar:local_time(), ?MODULE, ?LINE] ++ Args)).
+
+
+%NOTE  i was not sure what format to adopt for archive_option.    otr_list  is unused
+-record(archive_options,
+	{us,
+	 default = unset,
+	 save_list = [],
+	 nosave_list = [],
+	 otr_list = []}).
+
+%-record(archive_options, {usj, us, jid, type, value}).
+
+-record(archive_message,
+	{usjs,
+	 us,
+	 jid,
+	 start,
+	 message_list = [],
+	 subject = ""}).
+
+-record(msg, {direction, secs, body}).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link(Host, Opts) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
+
+start(Host, Opts) ->
+    DBHost = gen_mod:get_opt(db_host, Opts, Host),
+    if
+	DBHost /= Host ->
+	    PGChildSpec =
+		{gen_mod:get_module_proc(DBHost, ejabberd_odbc_sup),
+		 {ejabberd_odbc_sup, start_link, [DBHost]},
+		 temporary,
+		 infinity,
+		 supervisor,
+		 [ejabberd_odbc_sup]},
+	    supervisor:start_child(ejabberd_sup, PGChildSpec);
+	true ->
+	    ok
+    end,
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    ChildSpec =
+	{Proc,
+	 {?MODULE, start_link, [Host, Opts]},
+	 temporary,
+	 1000,
+	 worker,
+	 [?MODULE]},
+    supervisor:start_child(ejabberd_sup, ChildSpec).
+
+stop(Host) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    gen_server:call(Proc, stop),
+    supervisor:delete_child(ejabberd_sup, Proc).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%%                         {ok, State, Timeout} |
+%%                         ignore               |
+%%                         {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Host, Opts]) ->
+    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+    SaveDefault = gen_mod:get_opt(save_default, Opts, false),
+    SessionDuration = gen_mod:get_opt(session_duration, Opts, 900),
+    mnesia:create_table(archive_options,
+			[{disc_copies, [node()]},
+			 {attributes, record_info(fields, archive_options)}]),
+    mnesia:create_table(archive_message,
+			[{disc_copies, [node()]},
+			 {attributes, record_info(fields, archive_message)}]),
+%    mnesia:add_table_index(archive_options, us),
+    mnesia:add_table_index(archive_message, us),
+    ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
+    gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ARCHIVE, ?MODULE, process_iq, IQDisc),
+    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_ARCHIVE, ?MODULE, process_local_iq, IQDisc),
+    ejabberd_hooks:add(user_send_packet, Host, ?MODULE, send_packet, 90),
+    ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, receive_packet, 90),
+    ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, receive_packet, 35),
+    ejabberd_hooks:add(disco_local_features, Host, ?MODULE, get_disco_features, 99),
+    timer:send_interval(1000 * SessionDuration div 2, clean_sessions),
+    {ok, #state{host = Host,
+		sessions = ?DICT:new(),
+		save_default = SaveDefault,
+		session_duration = SessionDuration}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%%                                      {reply, Reply, State, Timeout} |
+%%                                      {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, Reply, State} |
+%%                                      {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call(get_save_default, _From, State) ->
+    {reply, State#state.save_default, State};
+handle_call(stop, _From, State) ->
+    {stop, normal, ok, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%%                                      {noreply, State, Timeout} |
+%%                                      {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({addlog, Direction, LUser, LServer, JID, Body}, State) ->
+    Sessions = State#state.sessions,
+    NewSessions =
+	case should_store_jid(LUser, LServer, JID,
+			      State#state.save_default) of
+	    false ->
+		Sessions;
+	    true ->
+		do_log(Sessions, LUser, LServer, JID,
+		       Direction, Body,
+		       State#state.session_duration)
+	end,
+    {noreply, State#state{sessions = NewSessions}};
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%%                                       {noreply, State, Timeout} |
+%%                                       {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(clean_sessions, State) ->
+    Sessions = State#state.sessions,
+    Timeout = State#state.session_duration,
+    TS = get_timestamp(),
+    NewSessions = ?DICT:filter(fun(_Key, {_Start, Last}) ->
+				       TS - Last =< Timeout
+			       end, Sessions),
+    {noreply, State#state{sessions = NewSessions}};
+
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, State) ->
+    Host = State#state.host,
+    ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
+    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_ARCHIVE),
+    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_ARCHIVE),
+    ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, send_packet, 90),
+    ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, receive_packet, 90),
+    ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, receive_packet, 35),
+    ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, get_disco_features, 99),
+    ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+%% Workaround the fact that if the client send <iq type='get'>
+%% it end up like <iq type='get' from='u@h' to = 'u@h'>
+process_iq(From, To, IQ) ->
+    #iq{sub_el = SubEl} = IQ,
+    #jid{lserver = LServer, luser = LUser} = To,
+    #jid{luser = FromUser} = From,
+    case {LUser, LServer, lists:member(LServer, ?MYHOSTS)} of
+	{FromUser, _, true} ->
+	    process_local_iq(From, To, IQ);
+	{"", _, true} ->
+	    process_local_iq(From, To, IQ);
+	{"", "", _} ->
+	    process_local_iq(From, To, IQ);
+	_ ->
+	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+    end.
+
+process_local_iq(From, To, #iq{sub_el = SubEl} = IQ) ->
+    case lists:member(From#jid.lserver, ?MYHOSTS) of
+	false ->
+	    IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+	true ->
+	    {xmlelement, Name, _Attrs, _Els} = SubEl,
+	    case Name of
+		"pref" -> process_local_iq_pref(From, To, IQ);
+		"auto" -> process_local_iq_auto(From, To, IQ);
+		%%"otr" -> process_local_iq_otr(From, To, IQ);
+		"list" -> process_local_iq_list(From, To, IQ);
+		"retrieve" -> process_local_iq_retrieve(From, To, IQ);
+		"save" -> process_local_iq_save(From, To, IQ);
+		"remove" -> process_local_iq_remove(From, To, IQ);
+		_ -> IQ#iq{type = error,
+			   sub_el = [SubEl, ?ERR_FEATURE_NOT_IMPLEMENTED]}
+            end
+    end.
+
+
+remove_user(User, Server) ->
+    LUser = jlib:nodeprep(User),
+    LServer = jlib:nameprep(Server),
+    US = {LUser, LServer},
+    Username = ejabberd_odbc:escape(LUser),
+    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+    F = fun() ->
+		ejabberd_odbc:sql_query_t(
+		  ["delete from archive_collection "
+		   "where username = '", Username, "'"])
+	end,
+    ejabberd_odbc:sql_transaction(DBHost, F),
+    F = fun() ->
+		mnesia:delete({archive_options, US})
+        end,
+    mnesia:transaction(F).
+
+get_disco_features(Acc, _From, _To, "", _Lang) ->
+    Features =
+	case Acc of
+	    {result, I} -> I;
+	    _ -> []
+	end,
+    {result, Features ++ [?NS_ARCHIVE_MANAGE,
+			  ?NS_ARCHIVE_PREF,
+			  ?NS_ARCHIVE_MANUAL]};
+
+get_disco_features(Acc, _From, _To, _Node, _Lang) ->
+    Acc.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3 Automated archiving
+%%
+
+send_packet(From, To, Packet) ->
+    add_log(to, From#jid.luser, From#jid.lserver, To, Packet).
+
+receive_packet(From, To, Packet) ->
+    add_log(from, To#jid.luser, To#jid.lserver, From, Packet).
+
+receive_packet(_JID, From, To, Packet) ->
+    receive_packet(From, To, Packet).
+
+add_log(Direction, LUser, LServer, JID, Packet) ->
+    case parse_message(Packet) of
+	"" ->
+	    ok;
+	Body ->
+	    Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
+	    gen_server:cast(
+	      Proc, {addlog, Direction, LUser, LServer, JID, Body})
+    end.
+
+% Parse the message and return the body string if successful
+parse_message({xmlelement, "message", _, _} = Packet) ->
+    case xml:get_tag_attr_s("type", Packet) of
+	Type when Type == "";
+		  Type == "normal";
+		  Type == "chat" ->
+	    xml:get_path_s(Packet, [{elem, "body"}, cdata]);
+	_ ->
+	    ""
+    end;
+parse_message(_) ->
+    "".
+
+% archive the message Body    return new Sessions
+%  Sessions:  a dict of open sessions
+%  LUser, LServer :  the local user's information
+%  Jid : the contact's jid
+%  Body : the message body
+do_log(Sessions, LUser, LServer, JID, Direction, Body, SessionDuration) ->
+    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+    {NewSessions, Start, Secs} =
+	find_storage(LUser, LServer, JID, Sessions,
+		     SessionDuration),
+    Username = ejabberd_odbc:escape(LUser),
+    LJID = jlib:jid_tolower(JID),
+    SJIDU = ejabberd_odbc:escape(element(1, LJID)),
+    SJIDS = ejabberd_odbc:escape(element(2, LJID)),
+    SJIDR = ejabberd_odbc:escape(element(3, LJID)),
+    SStart = ejabberd_odbc:escape(integer_to_list(Start)),
+    SDirection = case Direction of
+		     to ->
+			 "true";
+		     from ->
+			 "false"
+		 end,
+    SSecs = ejabberd_odbc:escape(integer_to_list(Secs)),
+    SBody = ejabberd_odbc:escape(Body),
+    SUTC = "0",
+    SName = "",
+    SJID1 = "",
+    F = fun() ->
+		SSID =
+		    case ejabberd_odbc:sql_query_t(
+			   ["select sid from archive_collection "
+			    "where username = '", Username, "' ",
+			    "and jid_u = '", SJIDU, "' ",
+			    "and jid_s = '", SJIDS, "' ",
+			    "and jid_r = '", SJIDR, "' ",
+			    "and start = '", SStart, "'"]) of
+			{selected, ["sid"], [{SID}]} ->
+			    ["'", ejabberd_odbc:escape(SID), "'"];
+			{selected, ["sid"], []} ->
+			    SSubject = "",
+			    CollVals =
+				["currval('archive_collection_sid_seq'),",
+				 "'", Username, "',"
+				 "'", SJIDU, "',"
+				 "'", SJIDS, "',"
+				 "'", SJIDR, "',"
+				 "'", SStart, "',"
+				 "'", SSubject, "'"],
+			    ejabberd_odbc:sql_query_t(
+			      "select nextval('archive_collection_sid_seq')"),
+			    ejabberd_odbc:sql_query_t(
+			      ["insert into archive_collection("
+			       "              sid, username,"
+			       "              jid_u, jid_s, jid_r,"
+			       "              start, subject) "
+			       " values (", CollVals, ");"]),
+			    "currval('archive_collection_sid_seq')"
+		    end,
+		MsgVals =
+		    [SSID, ","
+		     "'", SDirection, "',"
+		     "'", SSecs, "',"
+		     "'", SUTC, "',"
+		     "'", SName, "',"
+		     "'", SJID1, "',"
+		     "'", SBody, "'"],
+		ejabberd_odbc:sql_query_t(
+		  ["insert into archive_message("
+		   "              sid, direction_to, secs, utc,"
+		   "              name, jid, body) "
+		   " values (", MsgVals, ");"])
+	end,
+    ejabberd_odbc:sql_transaction(DBHost, F),
+    NewSessions.
+
+find_storage(LUser, LServer, JID, Sessions, Timeout) ->
+    LJID = jlib:jid_tolower(JID),
+    Key = {LUser, LServer, LJID},
+    case ?DICT:find(Key, Sessions) of
+	error ->
+	    TS = get_timestamp(),
+	    {?DICT:store(Key, {TS, TS}, Sessions), TS, 0};
+	{ok, {Start, Last}} ->
+	    TS = get_timestamp(),
+	    if
+		TS - Last > Timeout ->
+		    {?DICT:store(Key, {TS, TS}, Sessions), TS, 0};
+		true ->
+		    {?DICT:store(Key, {Start, TS}, Sessions),
+		     Start, TS - Last}
+	    end
+    end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3.1 Preferences
+%%
+
+process_local_iq_pref(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    Result = case Type of
+        set ->
+            {xmlelement, _Name, _Attrs, Els} = SubEl,
+            process_save_set(From#jid.luser, From#jid.lserver, Els);
+        get ->
+            process_save_get(From#jid.luser, From#jid.lserver)
+        end,
+    case Result of
+        {result, R} ->
+            IQ#iq{type = result, sub_el = [R]};
+        ok ->
+            broadcast_iq(From, IQ#iq{type = set, sub_el=[SubEl]}),
+            IQ#iq{type = result, sub_el = []};
+        {error, E} ->
+            IQ#iq{type = error, sub_el = [SubEl, E]};
+        _ ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+    end.
+
+
+
+
+% return {error, xmlelement} or {result, xmlelement}
+process_save_get(LUser, LServer) ->
+    case catch mnesia:dirty_read(archive_options, {LUser, LServer}) of
+        {'EXIT', _Reason} ->
+            {error, ?ERR_INTERNAL_SERVER_ERROR};
+        [] ->
+            {result,
+	     {xmlelement, "pref", [{"xmlns", ?NS_ARCHIVE}],
+	      default_element(LServer)}};
+        [#archive_options{default = Default,
+			  save_list = SaveList,
+			  nosave_list = NoSaveList}] ->
+            LItems = lists:append(
+		       lists:map(fun(J) ->
+					 {xmlelement, "item",
+					  [{"jid", jlib:jid_to_string(J)},
+					   {"save","body"}],
+					  []}
+				 end, SaveList),
+		       lists:map(fun(J) ->
+					 {xmlelement, "item",
+					  [{"jid", jlib:jid_to_string(J)},
+					   {"save","false"}],
+					  []}
+				 end, NoSaveList)),
+            DItem = case Default of
+                        true ->			% TODO: <auto/>
+                            [{xmlelement, "default", [{"save", "body"}], []}];
+                        false ->
+                            [{xmlelement, "default", [{"save", "false"}], []}];
+                        _ ->
+                            default_element(LServer)
+                    end,
+            {result, {xmlelement, "save", [{"xmlns", ?NS_ARCHIVE}], DItem ++ LItems}}
+    end.
+
+%return the <default .../> element
+default_element(Host) ->
+    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
+    AutoSave = gen_server:call(Proc, get_save_default),
+    SaveAttr = if
+		   AutoSave -> "true";
+		   true -> "false"
+	       end,
+    [{xmlelement, "default", [{"save", "false"}, {"otr", "forbid"}], []},
+     {xmlelement, "auto", [{"save", SaveAttr}], []}].
+
+
+% return {error, xmlelement} or {result, xmlelement} or  ok
+process_save_set(LUser, LServer, Elms) ->
+    F = fun() ->
+		NE = case mnesia:read({archive_options, {LUser, LServer}}) of
+			 [] ->
+			     #archive_options{us = {LUser, LServer}};
+			 [E] ->
+			     E
+		     end,
+		SNE = transaction_parse_save_elem(NE, Elms),
+		case SNE of
+		    {error, _} -> SNE;
+		    _ -> mnesia:write(SNE)
+		end
+	end,
+    case mnesia:transaction(F) of
+        {atomic, {error, _} = Error} ->
+            Error;
+        {atomic, _} ->
+            ok;
+        _ ->
+	    {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+
+% transaction_parse_save_elem(archive_options, ListOfXmlElement) -> #archive_options
+%  parse the list of xml element, and modify the given archive_option
+transaction_parse_save_elem(Options, [{xmlelement, "default", Attrs, _} | Tail]) ->
+    V = case xml:get_attr_s("save", Attrs) of
+            "true" -> true;
+            "false" -> false;
+            _ -> unset
+        end,
+    transaction_parse_save_elem(Options#archive_options{default = V}, Tail);
+
+transaction_parse_save_elem(Options, [{xmlelement, "item", Attrs, _}  | Tail]) ->
+    case jlib:string_to_jid(xml:get_attr_s("jid", Attrs)) of
+        error -> {error, ?ERR_JID_MALFORMED};
+        #jid{luser = LUser, lserver = LServer, lresource = LResource} ->
+            JID = {LUser, LServer, LResource},
+            case xml:get_attr_s("save", Attrs) of
+                "body" ->
+                    transaction_parse_save_elem(
+		      Options#archive_options{
+			save_list = [JID | lists:delete(JID, Options#archive_options.save_list)],
+			nosave_list = lists:delete(JID, Options#archive_options.nosave_list)
+		       }, Tail);
+                "false" ->
+                    transaction_parse_save_elem(
+		      Options#archive_options{
+			save_list = lists:delete(JID, Options#archive_options.save_list),
+			nosave_list = [JID | lists:delete(JID, Options#archive_options.nosave_list)]
+		       }, Tail);
+                _ ->
+                    transaction_parse_save_elem(
+		      Options#archive_options{
+			save_list = lists:delete(JID, Options#archive_options.save_list),
+			nosave_list = lists:delete(JID, Options#archive_options.nosave_list)
+		       }, Tail)
+	    end
+    end;
+
+transaction_parse_save_elem(Options, []) ->  Options;
+transaction_parse_save_elem(Options, [_ | Tail]) ->
+    transaction_parse_save_elem(Options,  Tail).
+
+
+broadcast_iq(#jid{luser = User, lserver = Server}, IQ) ->
+    Fun = fun(Resource) ->
+        ejabberd_router:route(
+                    jlib:make_jid("", Server, ""),
+                    jlib:make_jid(User, Server, Resource),
+                    jlib:iq_to_xml(IQ#iq{id="push"}))
+        end,
+    lists:foreach(Fun, ejabberd_sm:get_user_resources(User,Server)).
+
+
+
+process_local_iq_auto(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    Result =
+	case Type of
+	    set ->
+		{xmlelement, _Name, Attrs, _Els} = SubEl,
+		Auto = case xml:get_attr_s("save", Attrs) of
+			   "true" -> true;
+			   "false" -> false;
+			   _ -> unset
+		       end,
+		case Auto of
+		    unset ->
+			{error, ?ERR_BAD_REQUEST};
+		    _ ->
+			LUser = From#jid.luser,
+			LServer = From#jid.lserver,
+			F = fun() ->
+				    Opts =
+					case mnesia:read({archive_options,
+							   {LUser, LServer}}) of
+					     [] ->
+						 #archive_options{us = {LUser, LServer}};
+					     [E] ->
+						 E
+					 end,
+				    mnesia:write(Opts#archive_options{
+						   default = Auto})
+			    end,
+			mnesia:transaction(F),
+			{result, []}
+		end;
+	    get ->
+		{error, ?ERR_BAD_REQUEST}
+        end,
+    case Result of
+        {result, R} ->
+            IQ#iq{type = result, sub_el = R};
+        {error, E} ->
+            IQ#iq{type = error, sub_el = [SubEl, E]};
+        _ ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+    end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%  3.1 Off-the-Record Mode
+%%
+
+%TODO
+
+
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Utility function
+
+% Return true if LUser@LServer should log message for the contact JID
+should_store_jid(LUser, LServer, Jid, Service_Default) ->
+    case catch mnesia:dirty_read(archive_options, {LUser, LServer}) of
+        [#archive_options{default = Default,
+			  save_list = SaveList,
+			  nosave_list = NoSaveList}] ->
+            Jid_t = jlib:jid_tolower(Jid),
+            Jid_b = jlib:jid_remove_resource(Jid_t),
+            A = lists:member(Jid_t, SaveList),
+            B = lists:member(Jid_t, NoSaveList),
+            C = lists:member(Jid_b, SaveList),
+            D = lists:member(Jid_b, NoSaveList),
+            if  A -> true;
+                B -> false;
+                C -> true;
+                D -> false;
+                Default == true -> true;
+                Default == false -> false;
+                true -> Service_Default
+            end;
+        _ ->
+	    Service_Default
+    end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   4. Manual Archiving
+%%
+
+
+process_local_iq_save(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        get ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        set ->
+            case parse_store_element (LUser, LServer, SubEl) of
+                {error, E} ->  IQ#iq{type = error, sub_el = [SubEl, E]};
+                Collection ->
+		    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+		    F = fun() ->
+				store_collection(Collection)
+			end,
+		    case ejabberd_odbc:sql_transaction(DBHost, F) of
+                        {atomic, _} ->
+                            IQ#iq{type = result, sub_el = []};
+                        _ ->
+                            IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+		    end
+            end
+    end.
+
+% return a #archive_message   StoreElem is an xmlelement, or return  {error, E}
+parse_store_element(LUser, LServer,
+		    {xmlelement, "save", _ChatAttrs, ChatSubEls}) ->
+    case xml:remove_cdata(ChatSubEls) of
+	[{xmlelement, "chat", Attrs, SubEls}] ->
+	    case index_from_argument(LUser, LServer, Attrs)  of
+		{error, E} -> {error, E};
+		{LUser, LServer, Jid, Start} = Index ->
+		    Messages = parse_store_element_sub(SubEls),
+		    #archive_message{usjs = Index,
+				     us = {LUser, LServer},
+				     jid = Jid,
+				     start = Start,
+				     subject = xml:get_attr_s("subject", Attrs),
+				     message_list = Messages}
+	    end;
+	_ ->
+	    {error, ?ERR_BAD_REQUEST}
+    end.
+
+% TODO: utc attribute, catch list_to_integer errors
+
+parse_store_element_sub([{xmlelement, Dir, _, _}  = E | Tail])
+  when Dir == "from";
+       Dir == "to" ->
+    [#msg{direction = list_to_atom(Dir),
+	  secs = list_to_integer(xml:get_tag_attr_s("secs", E)),
+	  body = xml:get_tag_cdata(xml:get_subtag(E,"body"))} |
+     parse_store_element_sub(Tail)];
+
+parse_store_element_sub([]) -> [];
+parse_store_element_sub([_ | Tail]) -> parse_store_element_sub(Tail).
+
+
+store_collection(Collection) ->
+    {LUser, _LServer, JID, Start} = Collection#archive_message.usjs,
+    LJID = jlib:jid_tolower(JID),
+    Username = ejabberd_odbc:escape(LUser),
+    SJIDU = ejabberd_odbc:escape(element(1, LJID)),
+    SJIDS = ejabberd_odbc:escape(element(2, LJID)),
+    SJIDR = ejabberd_odbc:escape(element(3, LJID)),
+    SStart = ejabberd_odbc:escape(integer_to_list(Start)),
+    SSubject = ejabberd_odbc:escape(Collection#archive_message.subject),
+    CollVals =
+	["currval('archive_collection_sid_seq'),",
+	 "'", Username, "',"
+	 "'", SJIDU, "',"
+	 "'", SJIDS, "',"
+	 "'", SJIDR, "',"
+	 "'", SStart, "',"
+	 "'", SSubject, "'"],
+    ejabberd_odbc:sql_query_t(
+      ["delete from archive_collection "
+       "      where username = '", Username, "' "
+       "        and jid_u = '", SJIDU, "' ",
+       "        and jid_s = '", SJIDS, "' ",
+       "        and jid_r = '", SJIDR, "';"]),
+    ejabberd_odbc:sql_query_t(
+      "select nextval('archive_collection_sid_seq')"),
+    ejabberd_odbc:sql_query_t(
+      ["insert into archive_collection("
+       "              sid, username, jid_u, jid_s, jid_r, start, subject) "
+       " values (", CollVals, ");"]),
+    lists:map(
+      fun(#msg{direction = Direction, secs = Secs, body = Body}) ->
+	      SDirection = case Direction of
+			       to ->
+				   "true";
+			       from ->
+				   "false"
+			   end,
+	      SSecs = ejabberd_odbc:escape(integer_to_list(Secs)),
+	      SBody = ejabberd_odbc:escape(Body),
+	      SUTC = "0",
+	      SName = "",
+	      SJID1 = "",
+	      MsgVals =
+		  ["'", SDirection, "',"
+		   "'", SSecs, "',"
+		   "'", SUTC, "',"
+		   "'", SName, "',"
+		   "'", SJID1, "',"
+		   "'", SBody, "'"],
+	      ejabberd_odbc:sql_query_t(
+		["insert into archive_message("
+		 "              sid, direction_to, secs, utc,"
+		 "              name, jid, body) "
+		 " values (currval('archive_collection_sid_seq'),"
+		 "      ", MsgVals, ");"])
+      end, Collection#archive_message.message_list).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   5. Archive Management
+%%
+
+
+process_local_iq_list(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        set ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        get ->
+            {xmlelement, _, Attrs, SubEls} = SubEl,
+            RSM = parse_rsm(SubEls),
+            %?MYDEBUG("RSM Results: ~p ~n", [RSM]),
+            Result = case parse_root_argument(Attrs) of
+			 {error, E} -> {error, E};
+			 {interval, Start, Stop, JID} ->
+			     get_list(LUser, LServer, Start, Stop, JID);
+			 _ ->
+			     {error, ?ERR_BAD_REQUEST}
+		     end,
+            case Result of
+                {ok, Items} ->
+                    FunId = fun(El) ->
+				    %?MYDEBUG("FunId  ~p  ~n", [El]),
+				    integer_to_list(element(5, El))
+			    end,
+                    FunCompare = fun(Id, El) ->
+					 Id2 = list_to_integer(FunId(El)),
+					 Id1 = list_to_integer(Id),
+					 if Id1 == Id2 -> equal;
+					    Id1 > Id2 -> greater;
+					    Id1 < Id2 -> smaller
+					 end
+				 end,
+                    case catch execute_rsm(RSM, lists:keysort(5, Items), FunId,FunCompare)  of
+                        {error, R} ->
+                            IQ#iq{type = error, sub_el = [SubEl, R]};
+                        {'EXIT', Errr} ->
+                            %?MYDEBUG("INTERNAL ERROR  ~p  ~n", [Errr]),
+                            IQ#iq{type = error,
+				  sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
+                        {RSM_Elem, Items2} ->
+                            Zero = calendar:datetime_to_gregorian_seconds(
+				     {{1970, 1, 1}, {0, 0, 0}}),
+                            Fun = fun(A) ->
+					  Seconds= A#archive_message.start - Zero,
+					  Start2 =  jlib:now_to_utc_string(
+						      {Seconds div 1000000,
+						       Seconds rem 1000000,
+						       0}),
+					  Args0 = [{"with", jlib:jid_to_string(A#archive_message.jid)},
+						   {"start", Start2}],
+					  Args = case A#archive_message.subject of
+						     "" ->
+							 Args0;
+						     Subject ->
+							 [{"subject",Subject} | Args0]
+						 end,
+					  {xmlelement, "chat", Args, []}
+				  end,
+                            IQ#iq{type = result,
+				  sub_el = [{xmlelement, "list",
+					     [{"xmlns", ?NS_ARCHIVE}],
+					     lists:append(
+					       lists:map(Fun, Items2),
+					       [RSM_Elem])}]}
+                    end;
+                {error, R} ->
+                    IQ#iq{type = error, sub_el = [SubEl, R]}
+            end
+    end.
+
+
+
+
+process_local_iq_retrieve(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        set ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        get ->
+            {xmlelement, _, Attrs, SubEls} = SubEl,
+            RSM = parse_rsm(SubEls),
+            case index_from_argument(LUser, LServer, Attrs)  of
+                {error, E} ->
+		    IQ#iq{type = error, sub_el = [SubEl, E]};
+                Index ->
+                    case retrieve_collection(Index, RSM) of
+			{error, Err} ->
+			    IQ#iq{type = error, sub_el = [SubEl, Err]};
+			Store ->
+			    IQ#iq{type = result, sub_el = [Store]}
+                    end
+                end
+        end.
+
+
+retrieve_collection(Index, RSM) ->
+    case get_collection(Index) of
+        {error, E} ->
+            {error, E};
+        A ->
+            Zero = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}),
+            Seconds = A#archive_message.start-Zero,
+            Start2 =  jlib:now_to_utc_string({Seconds div 1000000, Seconds rem 1000000, 0}),
+            Args0 = [{"xmlns", ?NS_ARCHIVE}, {"with", jlib:jid_to_string(A#archive_message.jid)}, {"start", Start2}],
+            Args = case A#archive_message.subject of
+		       "" -> Args0;
+		       Subject -> [{"subject", Subject} | Args0]
+		   end,
+            case catch execute_rsm(RSM, A#archive_message.message_list, index, index)  of
+                {error, R} ->
+                    {error, R};
+                {'EXIT', _} ->
+                    {error, ?ERR_INTERNAL_SERVER_ERROR};
+                {RSM_Elem, Items} ->
+                    Format_Fun =
+			fun(Elem) ->
+				{xmlelement,  atom_to_list(Elem#msg.direction),
+				 [{"secs", integer_to_list(Elem#msg.secs)}],
+				 [{xmlelement, "body", [], [{xmlcdata,  Elem#msg.body}]}]}
+			end,
+                    {xmlelement, "chat", Args, lists:append(lists:map(Format_Fun, Items), [RSM_Elem])}
+            end
+    end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%   5.3 Removing a Collection
+%%
+
+
+process_local_iq_remove(From, _To, #iq{type = Type, sub_el = SubEl} = IQ) ->
+    #jid{luser = LUser, lserver = LServer} = From,
+    case Type of
+        get ->
+            IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+        set ->
+            {xmlelement, _,  Attrs, _} = SubEl,
+            Result =
+		case parse_root_argument(Attrs) of
+		    {error, E} ->
+			IQ#iq{type = error, sub_el = [SubEl, E]};
+		    {interval, Start, Stop, Jid} ->
+			process_remove_interval(
+			  LUser, LServer, Start, Stop, Jid)
+		end,
+            case Result of
+                {error, Ee} ->
+		    IQ#iq{type = error, sub_el = [SubEl, Ee]};
+                ok ->
+		    IQ#iq{type = result, sub_el=[]}
+            end
+    end.
+
+process_remove_interval(LUser, LServer, Start, End, With) ->
+    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+    Username = ejabberd_odbc:escape(LUser),
+    WithCond = case With of
+		   undefined ->
+		       "";
+		   JID ->
+		       LJID = jlib:jid_tolower(JID),
+		       case LJID of
+			   {"", LJIDS, ""} ->
+			       SJIDS = ejabberd_odbc:escape(LJIDS),
+			       [" and jid_s = '", SJIDS, "'"];
+			   {LJIDU, LJIDS, ""} ->
+			       SJIDU = ejabberd_odbc:escape(LJIDU),
+			       SJIDS = ejabberd_odbc:escape(LJIDS),
+			       [" and jid_u = '", SJIDU, "'",
+				" and jid_s = '", SJIDS, "'"];
+			   {LJIDU, LJIDS, LJIDR} ->
+			       SJIDU = ejabberd_odbc:escape(LJIDU),
+			       SJIDS = ejabberd_odbc:escape(LJIDS),
+			       SJIDR = ejabberd_odbc:escape(LJIDR),
+			       [" and jid_u = '", SJIDU, "'",
+				" and jid_s = '", SJIDS, "'",
+				" and jid_r = '", SJIDR, "'"]
+		       end
+	       end,
+    TimeCond =
+	case {Start, End} of
+	    {undefined, undefined} ->
+		"";
+	    {undefined, _} ->
+		SEnd = ejabberd_odbc:escape(integer_to_list(End)),
+		[" and start <= '", SEnd, "'"];
+	    {_, undefined} ->
+		SStart = ejabberd_odbc:escape(integer_to_list(Start)),
+		[" and start = '", SStart, "'"];
+	    _ ->
+		SStart = ejabberd_odbc:escape(integer_to_list(Start)),
+		SEnd = ejabberd_odbc:escape(integer_to_list(End)),
+		[" and start >= '", SStart, "'"
+		 " and start <= '", SEnd, "'"]
+	    end,
+    F = fun() ->
+		ejabberd_odbc:sql_query_t(
+		  ["delete from archive_collection "
+		   "where username = '", Username, "' ",
+		   TimeCond,
+		   WithCond])
+	end,
+    case ejabberd_odbc:sql_transaction(DBHost, F) of
+        {atomic, _} ->
+            ok;
+        {aborted, _} ->
+            {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+
+
+% return {ok, [{#archive_message}]} or {error, xmlelement}
+% With is a tuple Jid,  or '_'
+get_list(LUser, LServer, Start, End, With) ->
+    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+    Username = ejabberd_odbc:escape(LUser),
+    WithCond = case With of
+		   undefined ->
+		       "";
+		   JID ->
+		       LJID = jlib:jid_tolower(JID),
+		       case LJID of
+			   {"", LJIDS, ""} ->
+			       SJIDS = ejabberd_odbc:escape(LJIDS),
+			       [" and jid_s = '", SJIDS, "'"];
+			   {LJIDU, LJIDS, ""} ->
+			       SJIDU = ejabberd_odbc:escape(LJIDU),
+			       SJIDS = ejabberd_odbc:escape(LJIDS),
+			       [" and jid_u = '", SJIDU, "'",
+				" and jid_s = '", SJIDS, "'"];
+			   {LJIDU, LJIDS, LJIDR} ->
+			       SJIDU = ejabberd_odbc:escape(LJIDU),
+			       SJIDS = ejabberd_odbc:escape(LJIDS),
+			       SJIDR = ejabberd_odbc:escape(LJIDR),
+			       [" and jid_u = '", SJIDU, "'",
+				" and jid_s = '", SJIDS, "'",
+				" and jid_r = '", SJIDR, "'"]
+		       end
+	       end,
+    StartCond =
+	case Start of
+	    undefined ->
+		"";
+	    _ ->
+		SStart = ejabberd_odbc:escape(integer_to_list(Start)),
+		[" and start >= '", SStart, "'"]
+	end,
+    EndCond =
+	case End of
+	    undefined ->
+		"";
+	    _ ->
+		SEnd = ejabberd_odbc:escape(integer_to_list(End)),
+		[" and start <= '", SEnd, "'"]
+	end,
+    case catch ejabberd_odbc:sql_query(
+		 DBHost,
+		 ["select jid_u, jid_s, jid_r, start, subject "
+		  "from archive_collection "
+		  "where username = '", Username, "' ",
+		  StartCond,
+		  EndCond,
+		  WithCond]) of
+	{selected, ["jid_u", "jid_s", "jid_r", "start", "subject"], Rs} ->
+	    Result =
+		lists:map(
+		  fun({SJIDU, SJIDS, SJIDR, SStart1, Subject}) ->
+			  JID1 = jlib:make_jid(SJIDU, SJIDS, SJIDR),
+			  Start1 = list_to_integer(SStart1),
+			  Index = {LUser, LServer, JID1, Start1},
+			  #archive_message{
+				    usjs = Index,
+				    us = {LUser, LServer},
+				    jid = JID1,
+				    start = Start1,
+				    subject = Subject,
+				    message_list = []}
+		  end, Rs),
+	    {ok, Result};
+	_ ->
+	    {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+
+% Index is  {LUser, LServer, With, Start}
+get_collection(Index) ->
+    {LUser, LServer, JID, Start} = Index,
+    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+    LJID = jlib:jid_tolower(JID),
+    Username = ejabberd_odbc:escape(LUser),
+    SJIDU = ejabberd_odbc:escape(element(1, LJID)),
+    SJIDS = ejabberd_odbc:escape(element(2, LJID)),
+    SJIDR = ejabberd_odbc:escape(element(3, LJID)),
+    SStart = ejabberd_odbc:escape(integer_to_list(Start)),
+    case catch ejabberd_odbc:sql_query(
+		 DBHost,
+		 ["select sid, subject from archive_collection "
+		  "where username = '", Username, "' ",
+		  "and jid_u = '", SJIDU, "' ",
+		  "and jid_s = '", SJIDS, "' ",
+		  "and jid_r = '", SJIDR, "' ",
+		  "and start = '", SStart, "'"]) of
+	{selected, ["sid", "subject"], [{SID, Subject}]} ->
+	    SSID = ejabberd_odbc:escape(SID),
+	    case catch ejabberd_odbc:sql_query(
+			 DBHost,
+			 ["select direction_to, secs, utc, name, jid, body "
+			  "from archive_message "
+			  "where sid = '", SSID, "' ",
+			  "order by ser"]) of
+		{selected, ["direction_to", "secs", "utc",
+			    "name", "jid", "body"], Rs} ->
+		    Msgs = lists:map(
+			     fun({DirectionTo, SSecs, SUTC,
+				  SName, SJID, Body}) ->
+				     Direction =
+					 if
+					     DirectionTo == "t" ->
+						 to;
+					     DirectionTo == "f" ->
+						 from
+					 end,
+				     Secs = list_to_integer(SSecs),
+				     #msg{direction = Direction,
+					  secs = Secs,
+					  body = Body}
+			     end, Rs),
+		    #archive_message{usjs = Index,
+				     us = {LUser, LServer},
+				     jid = JID,
+				     start = Start,
+				     message_list = Msgs,
+				     subject = Subject};
+		_ ->
+		    {error, ?ERR_INTERNAL_SERVER_ERROR}
+		end;
+	{selected, ["sid", "subject"], []} ->
+            {error, ?ERR_ITEM_NOT_FOUND};
+	_ ->
+            {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Utility
+
+
+% return either  {error, Err}  or {LUser, LServer, Jid, Start}
+index_from_argument(LUser, LServer,  Attrs) ->
+    case parse_root_argument(Attrs) of
+        {error, E} ->  {error, E};
+        {interval, Start, _, JID} when Start /= undefined,
+				       JID /= undefined ->
+	    {LUser, LServer, JID, Start};
+        _ -> {error, ?ERR_BAD_REQUEST}
+    end.
+
+%parse commons arguments of root elements
+parse_root_argument(Attrs) ->
+    case parse_root_argument_aux(Attrs, {undefined, undefined, undefined}) of
+        {error, E} -> {error, E};
+        {JID, Start, Stop} -> {interval, Start, Stop, JID};
+        _ -> {error,  ?ERR_BAD_REQUEST}
+    end.
+
+parse_root_argument_aux([{"with", JidStr} | Tail], {_, AS, AE}) ->
+    case jlib:string_to_jid(JidStr) of
+        error -> {error, ?ERR_JID_MALFORMED};
+        JID ->
+            LJID = jlib:jid_tolower(JID),
+            parse_root_argument_aux(Tail, {LJID, AS, AE})
+    end;
+parse_root_argument_aux([{"start", Str} | Tail], {AW, _, AE}) ->
+    case jlib:datetime_string_to_timestamp(Str) of
+        undefined -> {error, ?ERR_BAD_REQUEST};
+        No ->
+            Val = calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(No)),
+            parse_root_argument_aux(Tail, {AW, Val, AE})
+    end;
+parse_root_argument_aux([{"end", Str} | Tail], {AW, AS, _}) ->
+    case jlib:datetime_string_to_timestamp(Str) of
+        undefined -> {error, ?ERR_BAD_REQUEST};
+        No ->
+            Val = calendar:datetime_to_gregorian_seconds(calendar:now_to_datetime(No)),
+            parse_root_argument_aux(Tail, {AW, AS, Val})
+    end;
+parse_root_argument_aux([_ | Tail], A) ->
+    parse_root_argument_aux(Tail, A);
+parse_root_argument_aux([], A) ->  A.
+
+
+
+get_timestamp() ->
+    calendar:datetime_to_gregorian_seconds(calendar:universal_time()).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%  Result Set Management (JEP-0059)
+%
+% USAGE:
+%   RSM = parce_rsm(Xmlelement)
+%   case execute_rsm(RSM, List,  GetIdFun, IdCompareFun) of
+%      {error, E} -> ....;
+%      {RSMElement, Items} ->
+%           SubElements = lists:append(lists:map(Format_Fun, Items), [RSMElement]),
+%           ...;
+%   end
+
+-define(MY_NS_RSM, "http://jabber.org/protocol/rsm").
+
+
+
+
+%  {Start, Max, Order} | error |  none  % | count
+
+parse_rsm([A | Tail]) ->
+    %?MYDEBUG("parse RSM elem ~p ", [A]),
+    case A of
+        {xmlelement, _,  Attrs1, _} ->
+            case xml:get_attr_s("xmlns", Attrs1) of
+                ?MY_NS_RSM ->
+                    parse_rsm(A);
+                HEPO ->
+                    %?MYDEBUG("HEPO ~p ", [HEPO]),
+                    parse_rsm(Tail)
+            end;
+        _ ->
+            parse_rsm(Tail)
+    end;
+parse_rsm([]) ->
+    none;
+
+% parse_rsm({xmlelement, "count", _, _}) ->
+%     count;
+
+parse_rsm({xmlelement, "set", _, SubEls}) ->
+    parse_rsm_aux(SubEls, {0, infinity, normal});
+
+parse_rsm(_) ->
+    error.
+
+parse_rsm_aux([{xmlelement, "max", _Attrs, Contents} | Tail], Acc) ->
+    case catch list_to_integer(xml:get_cdata(Contents)) of
+        P when is_integer(P) ->
+            case Acc of
+                {Start, infinity, Order} ->
+                    parse_rsm_aux(Tail, {Start, P, Order});
+                _ ->
+                    error
+            end;
+        HEPO ->
+            %?MYDEBUG("<max> Not an INTEGER ~p ", [HEPO]),
+            error
+    end;
+
+parse_rsm_aux([{xmlelement, "index", _Attrs, Contents} | Tail], Acc) ->
+    case catch list_to_integer(xml:get_cdata(Contents)) of
+        P when is_integer(P) ->
+            case Acc of
+                {0, Max, normal} ->
+                    parse_rsm_aux(Tail, {P, Max, normal});
+                _ ->
+                    error
+            end;
+        _ ->
+            error
+    end;
+
+parse_rsm_aux([{xmlelement, "after", _Attrs, Contents} | Tail], Acc) ->
+    case Acc of
+        {0, Max, normal} ->
+            parse_rsm_aux(Tail, {{id, xml:get_cdata(Contents)}, Max, normal});
+        _ ->
+            error
+    end;
+
+
+parse_rsm_aux([{xmlelement, "before", _Attrs, Contents} | Tail], Acc) ->
+    case Acc of
+        {0, Max, normal} ->
+            case xml:get_cdata(Contents) of
+                [] ->
+                    parse_rsm_aux(Tail, {0, Max, reversed});
+                CD ->
+                    parse_rsm_aux(Tail, {{id, CD}, Max, reversed})
+            end;
+        _ ->
+            error
+    end;
+
+parse_rsm_aux([_ | Tail], Acc) ->
+    parse_rsm_aux(Tail, Acc);
+parse_rsm_aux([], Acc) ->
+    Acc.
+
+%  RSM = {Start, Max, Order}
+%  GetId = fun(Elem) -> Id
+%  IdCompare = fun(Id, Elem) -> equal | greater | smaller
+%
+%  ->  {RSMElement, List} | {error, ErrElement}
+
+execute_rsm(RSM, List, GetId, IdCompare) ->
+    %?MYDEBUG("execute_rsm RSM ~p  ~n", [RSM]),
+    case execute_rsm_aux(RSM, List, IdCompare, 0) of
+        none ->
+            {{xmlcdata, ""}, List};
+%         count ->
+%             {{xmlelement, "count", [{"xmlns", ?MY_NS_RSM}], [{xmlcdata,  integer_to_list(length(List))}]}, []};
+        {error, E} ->
+            {error, E};
+        {_, []} ->
+              {{xmlelement, "set", [{"xmlns", ?MY_NS_RSM}], [{xmlelement, "count", [], [{xmlcdata,  integer_to_list(length(List))}]}]}, []};
+        {Index, L} ->
+            case GetId of
+                index ->
+                    {make_rsm(Index, integer_to_list(Index), integer_to_list(Index+length(L)),length(List)), L};
+                _ ->
+                    {make_rsm(Index, GetId(hd(L)), GetId(lists:last(L)),length(List)), L}
+            end
+    end.
+
+
+% execute_rsm_aux(count, _List, _, _) ->
+%      count;
+
+execute_rsm_aux(none, _List, _, _) ->
+    none;
+
+execute_rsm_aux(error, _List, _, _) ->
+    {error, ?ERR_BAD_REQUEST};
+
+execute_rsm_aux({S, M, reversed}, List, IdFun, Acc) ->
+    {NewFun,NewS} = case IdFun of
+        index ->
+            {index,
+                case S of
+                    {id, IdentIndex} ->
+                        integer_to_list(length(List) - list_to_integer(IdentIndex));
+                    _ -> S
+                end};
+        _ ->
+            {fun(Index, Elem) ->
+                    case IdFun(Index, Elem) of
+                        equal -> equal;
+                        greater -> smaller;
+                        smaller -> greater;
+                        O -> O
+                    end
+                end,
+               S}
+        end,
+    {Index, L2} =  execute_rsm_aux({NewS,M,normal}, lists:reverse(List), NewFun, 0),
+    {Acc + length(List) - Index - length(L2), lists:reverse(L2)};
+
+execute_rsm_aux({{id,I}, M, normal}, List,  index,  Acc) ->
+    execute_rsm_aux({list_to_integer(I), M, normal}, List,  index,  Acc);
+
+execute_rsm_aux({{id,I}, M, normal} = RSM, [E | Tail],  IdFun,  Acc) ->
+    case IdFun(I, E) of
+        smaller ->
+            execute_rsm_aux(RSM, Tail,  IdFun,  Acc + 1);
+         _ ->
+            execute_rsm_aux({0, M, normal}, [E | Tail],  IdFun, Acc)
+    end;
+
+execute_rsm_aux({{id,_}, _, normal}, [], _, Acc) ->
+    {Acc, []};
+
+execute_rsm_aux({0, infinity, normal}, List, _, Acc) ->
+    {Acc, List};
+
+execute_rsm_aux({_, 0, _}, _, _, Acc) ->
+    {Acc, []};
+
+execute_rsm_aux({S, M, _}, List, _, Acc)  when  is_integer(S) and is_integer(M) ->
+    %?MYDEBUG("execute_rsm_aux  sublist  ~p  ~n", [{S,M,List,Acc}]),
+    {Acc + S, lists:sublist(List, S+1,M)}.
+
+make_rsm(FirstIndex, FirstId, LastId, Count) ->
+    {xmlelement, "set", [{"xmlns", ?MY_NS_RSM}], [
+        {xmlelement, "first", [{"index", integer_to_list(FirstIndex)}], [{xmlcdata,  FirstId}]},
+        {xmlelement, "last", [], [{xmlcdata,  LastId}]},
+        {xmlelement, "count", [], [{xmlcdata,  integer_to_list(Count)}]}]}.
diff --git a/mod_archive/src/mod_archive_webview.erl b/mod_archive/src/mod_archive_webview.erl
new file mode 100644
index 0000000..3aa18c8
--- /dev/null
+++ b/mod_archive/src/mod_archive_webview.erl
@@ -0,0 +1,554 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_archive_webview.erl
+%%% Author  : Olivier Goffart <ogoffart at kde dot org>
+%%% Purpose : Online viewer of message archive.  (to be used with mod_archive_odbc)
+%%% Created :
+%%% Id      :
+%%%----------------------------------------------------------------------
+
+-module(mod_archive_webview).
+-author('ogoffart@kde.org').
+
+-export([
+	 process/2
+	]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+-include("ejabberd_http.hrl").
+-include("ejabberd_web_admin.hrl"). %for all the defines
+
+-define(LINK(L) , "/archive/" ++ L).
+%-define(P(Els), ?XE("p", Els)).
+-define(PC(Text), ?XE("p", [?C(Text)])).
+-define(PCT(Text), ?PC(?T(Text))).
+
+
+-define(MYDEBUG(Format, Args),
+        io:format("D(~p:~p:~p) : " ++ Format ++ "~n",
+                  [calendar:local_time(), ?MODULE, ?LINE] ++ Args)).
+
+
+%%%----------------------------------------------------------------------
+%%% REQUEST HANDLERS
+%%%----------------------------------------------------------------------
+
+%process([], Request) -> process2([], Request, {}).
+
+process(["style.css"], _) ->
+    {200,[{"Content-Type", "text/css"}], "
+#navigation li { list-style:none; display:inline;  }
+.message_from, .message_to { margin:0; padding:0; }
+.message_from .time { color:#BD2134; font-weight:bold; }
+.message_from .jid  { color:#BD2134; font-weight:bold; }
+.message_to   .time { color:#1E6CC6; font-weight:bold; }
+.message_to   .jid  { color:#1E6CC6; font-weight:bold; }
+.search_result a { display:block; }
+.search_result em { display:block; color:green; }
+/*a.link_prev { float:left } */
+a.link_next { float:right }
+.message_body { white-space: pre-wrap; }
+"};
+
+process(Path, #request{auth = Auth} = Request) ->
+    ?MYDEBUG("Requested ~p ~p", [Path, Request]),
+    
+    case get_auth(Auth) of
+        {User, Server} ->
+            process2(Path, Request, {User, Server});
+        unauthorized ->
+            {401, [{"WWW-Authenticate", "basic realm=\"ejabberd-archives\""}],
+                ejabberd_web:make_xhtml([{xmlelement, "h1", [],
+                                               [{xmlcdata, "401 Unauthorized"}]}])}
+    end.
+   
+
+%process2(["config" | tail], #request{lang = Lang } = Request , {User, Server}) ->
+
+process2(["config" ], #request{lang = Lang } = _Request , {User, Server}) ->
+    make_xhtml(?T("Config"),
+        [?XE("h3", [?CT("Global Settings")]) ] ++ global_config_form({User, Server}, Lang) ++
+        [?XE("h3", [?CT("Specific Contact Settings")]) ] ++ contact_config_form({User, Server}, Lang) ++
+        [?X("hr"), ?ACT(?LINK("config/complex"),"Advanced settings")] , Lang);
+
+process2(["config" , "submit", "global"], #request{q = Query } = Request , US) ->
+    submit_config_global( Query  , US),
+    process2(["config"], Request, US);
+
+% process2(["config" , "submit", "contact"], #request{q = Query } = Request , US) ->
+%     submit_config_contact( Query  , US),
+%     process2(["config"], Request, US);
+
+process2(["config" , "complex" ], #request{lang = Lang } = _Request , {User, Server}) ->
+    make_xhtml(?T("Config"),
+        [?XE("h3", [?CT("Global Settings")]) ] ++ global_config_form_complex({User, Server}, Lang) ++
+        [?XE("h3", [?CT("Specific Contact Settings")]) ] ++ contact_config_form({User, Server}, Lang) ++
+        [?X("hr"), ?ACT(?LINK("config"),"Simple settings")] , Lang);
+
+process2(["config" , "submit", "complex_global"], #request{q = Query } = Request , US) ->
+    submit_config_global_complex( Query  , US),
+    process2(["config/complex"], Request, US);
+
+process2(["contact"], #request{lang = Lang } = _Request , US) ->
+    make_xhtml(?T("Contact List"), [
+                           ?XE("ul", lists:map( fun({Node,Server,Count}) -> 
+                                                    With = jlib:jid_to_string({Node,Server,""}),
+                                                    ?LI([?AC(?LINK("contact/" ++ ejabberd_http:url_encode(With)), With ) ,
+                                                         ?C(" (" ++ Count  ++")")] ) end,
+                                                get_contacts(US)))
+               ], Lang);
+
+process2(["contact" , Jid], #request{lang = Lang } = _Request , US) ->
+    make_xhtml(?T("Chat with ") ++ Jid, contact_config(Jid,US,Lang) ++
+                           [?XE("ul", lists:map( fun({Id, Node, Server, Resource, Utc, Subject }) -> 
+                                                    With = jlib:jid_to_string({Node,Server,Resource}),
+                                                    ?LI([?AC(?LINK("show/" ++ integer_to_list(Id)), "On " ++ Utc ++ " with " ++ With ++ " -> " ++ escape_str(Subject)  )] ) end,
+                                                get_collection_list(jlib:string_to_jid(Jid), US)))
+               ], Lang);
+
+process2(["show" , Id], #request{lang = Lang } = _Request , US) ->
+    { With, Utc, Subject,  List, NPId } = get_collection(Id, US),
+    [Date, _Time] = string:tokens(Utc, " "),
+    make_xhtml(?T("Chat with ") ++ jlib:jid_to_string(With) ++ ?T(" on ") ++ Date ++ ?T(" : ") ++ escape_str(Subject),
+               lists:map(fun(Msg) -> format_message(Msg,With, US) end, List)
+               ++ links_previous_next(NPId, Lang)
+               ++ [?X("hr"), ?XAE("form",[{"action",?LINK("edit/" ++ Id)},{"metohd","post"}],
+                                  [?XE("label",[?CT("Edit subject: "),
+                                                ?INPUT("text","subject",escape_str(Subject))]),
+                                   ?INPUT("submit","submit",?T("Ok"))]),
+                  ?XAE("form",[{"action",?LINK("delete/" ++ Id)},{"metohd","post"},
+                               {"onsubmit","return confirm('"++ ?T("Do you realy want to delete this chat") ++"')"}],
+                       [?INPUT("hidden","id",Id),?INPUT("submit","delete",?T("Delete"))])]
+               , Lang);
+
+process2(["edit" , Id], #request{ q = Query} = Request , US) ->
+    case lists:keysearch("subject", 1, Query) of
+        {value, {_, Subject}} -> change_subject(Id,Subject,US);
+        _ -> ok
+    end,
+    process2(["show", Id] , Request, US);
+
+process2(["delete" , Id], #request{q = Query, lang=Lang} = _Request , US) ->
+    case lists:keysearch("id", 1, Query) of
+        {value, {_, Id2}} when Id==Id2 -> 
+            delete_collection(Id,US), make_xhtml("Chat deleted",[],Lang);
+        _ -> ?ERR_INTERNAL_SERVER_ERROR
+    end;
+
+process2(["search"], #request{lang = Lang } = Request , US) ->
+    make_xhtml(?T("Search"), [
+                search_form(Request, US) ], Lang);
+
+process2(["search", "results"], #request{lang = Lang } = Request , US) ->
+    make_xhtml(?T("Search"), [ search_form(Request, US) | search_results(Request, US)], Lang);
+
+process2([], #request{lang = Lang } = _Request , {LUser,LServer}) ->
+    make_xhtml(?T("Archives viewer"),[?PCT("Welcome " ++ LUser ++ "@" ++ LServer)], Lang);
+
+process2(_, #request{lang = Lang } = _Request , _US) ->
+    make_xhtml(?T("404 File not found"),[], Lang).
+
+%------------------------------
+
+make_xhtml(Title, Els, Lang) ->
+    {200, [html],
+        {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
+                {"xml:lang", Lang},
+                {"lang", Lang}],
+        [{xmlelement, "head", [],
+        [?XE("title",  [?C(Title) , ?CT(" - ejabberd Web Archive Viewer")]),
+        {xmlelement, "meta", [{"http-equiv", "Content-Type"},
+                    {"content", "text/html; charset=utf-8"}], []},
+        {xmlelement, "link", [{"href", ?LINK("style.css")},
+                    {"type", "text/css"},
+                    {"rel", "stylesheet"}], []}]},
+        ?XE("body",
+        [?XAE("div", [{"id", "container"}],
+              [?XAE("div", [{"id", "header"}],
+                    [?XE("h1", [?CT("Archives Viewer")])]),
+               ?XAE("div", [{"id", "navigation"}],
+                    [?XE("ul",
+                     [?LI([?ACT(?LINK("config"), "Config")]), ?C(" "),
+                      ?LI([?ACT(?LINK("contact"), "Browse")]), ?C(" "),
+                      ?LI([?ACT(?LINK("search"), "Search")])])]), ?C(" "),
+               ?XAE("div", [{"id", "content"}], [ ?XE("h2", [?C(Title)]) | Els])])])]}}.
+
+
+get_auth(Auth) ->
+    case Auth of
+        {SJID, P} ->
+            case jlib:string_to_jid(SJID) of
+                error ->
+                    unauthorized;
+                #jid{user = U, server = S} ->
+                    case ejabberd_auth:check_password(U, S, P) of
+                        true ->
+                            {U, S};
+                        false ->
+                            unauthorized
+                    end
+            end;
+         _ ->
+            unauthorized
+    end.
+
+select_element(Name, List, Value1) ->
+    Value = if is_integer(Value1) -> integer_to_list(Value1); true -> Value1 end,
+    ?XAE("select",[{"name",Name}],lists:map( 
+        fun({Key,Text}) -> ?XAE("option", 
+                            case Key of
+                                Value -> [{"value",Value},{"selected","selected"}];
+                                _ -> [{"value",Key}]
+                            end, [?C(Text)]) end, List)).
+
+table_element(Rows) ->
+    ?XE("table",lists:map(fun(Cols)-> ?XE("tr", lists:map(fun(Ct)-> ?XE("td",Ct) end, Cols)) end, Rows)).
+
+%------------------------
+
+format_message({ Utc, Dir, Body } ,{WithU,WithS,WithR}, {LUser,LServer} ) ->
+    {From, Class} = case Dir of 
+        0 -> { jlib:jid_to_string({WithU,WithS,WithR}) , "message_from" } ;
+        1 -> { jlib:jid_to_string({LUser,LServer,""}) , "message_to" } 
+    end,
+    [_Date, Time] = string:tokens(Utc, " "),
+    ?XAE("p", [{"class", Class}] , [ ?XAE("span", [{"class","time"}], [?C("["++Time++"]")]), ?C(" "),
+                                   ?XAE("span", [{"class","jid"}], [?C(From)]), ?C(": "),
+                                   ?XAE("span", [{"class","message_body"}], [?C(Body)])]).
+
+contact_config(Jid,{LUser,LServer},Lang) ->
+    %run_sql_transaction(LServer, fun() -> run_sql_query("") end)
+    %[?XE("p",[?CT("Automatic archive with this contact is " + Au   ), ].
+    [].
+
+
+global_config_form({LUser,LServer},Lang) ->
+    {Save,Expire,Auto_save} =
+        case run_sql_transaction(LServer, fun() -> run_sql_query(
+                "SELECT save,expire,auto_save"
+                " FROM archive_global_prefs"
+                " WHERE us = " ++ get_us_escaped({LUser,LServer}) ) end) of
+            {selected, _ , [ Ok ]} -> Ok;
+            {selected, _ , [ ]} -> { -1, -1, -1 }
+        end,
+   [?XAE("form",[{"action",?LINK("config/submit/global")}],
+         [?XE("label",[?CT("Disable or enable automatic archiving globaly: "), select_element("global_auto_save",
+                        [{"-1",?T("--Server Default--")},{"1",?T("Enabled")},{"0",?T("Disabled")}],decode_integer(Auto_save))]),
+          ?BR,
+          ?XE("label",[?CT("Default for contact not specified bellow : "), select_element("global_save",
+                             [{"-1",?T("--Server Default--")},{"1",?T("Enabled")},{"0",?T("Disabled")}],decode_integer(Save))]),
+          ?BR, ?XE("label",[?CT("Default expiration time: "), ?INPUT("text","global_expire",integer_to_list(decode_integer(Expire)))]),
+               ?CT("(number of seconds before deleting message, '-1' = server default)"),
+          ?BR, ?INPUTT("submit","global_submit","Submit")]
+         )].
+
+global_config_form_complex({LUser,LServer},Lang) ->
+    {Save,Expire,Otr,Method_auto,Method_local,Method_manual,Auto_save} =
+        case run_sql_transaction(LServer, fun() -> run_sql_query(
+                "SELECT save,expire,otr,method_auto,method_local,method_manual,auto_save"
+                " FROM archive_global_prefs"
+                " WHERE us = " ++ get_us_escaped({LUser,LServer}) ) end) of
+            {selected, _ , [ Ok ]} -> Ok;
+            {selected, _ , [ ]} -> { -1, -1, -1, -1, -1, -1, -1 }
+        end,
+    MethodList = [ {"-1",?T("--Undefined--")}, {"0",?T("Prefer")}, {"1",?T("Concede")}, {"2",?T("Forbid")} ],
+    [?XAE("form",[{"action",?LINK("config/submit/complex_global")}],[table_element([[
+            [?XE("label",[?CT("Save: "), select_element("global_save",[{"-1",?T("--Default--")},{"1",?T("Enabled")},{"0",?T("Disabled")}],decode_integer(Save))])],
+            [?XE("label",[?CT("Expire: "), ?INPUT("text","global_expire",integer_to_list(decode_integer(Expire)))])],
+            [?XE("label",[?CT("Otr: "), select_element("global_otr",[{"-1",?T("--Undefined--")},
+                                                                      {"0",?T("Approve")},
+                                                                      {"1",?T("Concede")},
+                                                                      {"2",?T("Forbid")},
+                                                                      {"3",?T("Oppose")},
+                                                                      {"4",?T("Prefer")},
+                                                                      {"5",?T("Require")} ],decode_integer(Otr))])],
+            [?XE("label",[?CT("Auto Method: "), select_element("global_method_auto", MethodList,decode_integer(Method_auto))])],
+            [?XE("label",[?CT("Local Method: "), select_element("global_method_local", MethodList,decode_integer(Method_local))])],
+            [?XE("label",[?CT("Manual Method: "), select_element("global_method_manual", MethodList,decode_integer(Method_manual))])],
+            [?XE("label",[?CT("Auto Save "), select_element("global_auto_save",
+                                [{"-1",?T("--Default--")},{"1",?T("Enabled")},{"0",?T("Disabled")}],decode_integer(Auto_save))])],
+            [?INPUT("submit","global_modify",?T("Modify"))]
+       ]])])].
+
+    
+contact_config_form({LUser,LServer},Lang) ->
+     {selected, _, List} =
+          run_sql_transaction(LServer, fun() -> run_sql_query(
+             "SELECT with_user,with_server,with_resource,save,expire"
+             " FROM archive_jid_prefs"
+             " WHERE us = " ++ get_us_escaped({LUser,LServer}) ) end),
+    [ table_element([[[?CT("JID")],[?CT("Auto archive")],[?CT("Expire")]] |
+        lists:map(fun({WithU,WithS,WithR,Save,Expire}) -> 
+            [ [?C(jlib:jid_to_string({WithU,WithS,WithR}))],
+              [case decode_integer(Save) of 1 -> ?CT("Enabled"); 0 -> ?CT("Disabled"); _ -> ?CT("Default") end],
+              [?C(integer_to_list(decode_integer(Expire)))]  ] end , List ) ]) %,
+%       ?XAE("form",[{"action",?LINK("config/submit/contact")}],
+%          [?XE("label",[?CT("Add/Modify settings for Jid: "), ?INPUT("text","jid","")]),
+%           ?BR,
+%           ?XE("label",[?CT("Archiving : "), select_element("save",
+%                              [{"-1",?T("--Default--")},{"1",?T("Enabled")},{"0",?T("Disabled")}],"-1")]),
+%           ?BR, ?XE("label",[?CT("Expiration time: "), ?INPUT("text","expire","-1")]),
+%           ?BR, ?INPUTT("submit","submit","Submit")]
+%          )
+    ].
+
+
+
+get_from_query_escaped(Key,Query) ->
+    {value, {_, Value}} = lists:keysearch(Key, 1, Query),
+    case Value of
+        -1 -> "NULL";
+        Integer when is_integer(Integer) -> Integer;
+        "-1" -> "NULL";
+        Value -> "'" ++ ejabberd_odbc:escape(Value) ++ "'"
+    end.
+
+submit_config_global(Query , {LUser,LServer}) ->
+    SUS = get_us_escaped({LUser,LServer}),
+    SQLQuery = 
+        "UPDATE archive_global_prefs"
+        " SET save = " ++ get_from_query_escaped("global_save",Query) ++ ","
+        "     expire = " ++ get_from_query_escaped("global_expire",Query) ++ ","
+        "     auto_save = " ++ get_from_query_escaped("global_auto_save",Query) ++ 
+        " WHERE us = " ++ SUS,
+    F = fun() ->
+        %TODO: use REPLACE
+        case run_sql_query("SELECT us FROM archive_global_prefs WHERE us = " ++ SUS) of
+            {selected, _, Rs} when Rs /= [] -> ok;
+            _ -> run_sql_query("INSERT INTO archive_global_prefs (us) VALUES (" ++ SUS ++ ")")
+        end,
+        run_sql_query(SQLQuery) end,
+    run_sql_transaction(LServer, F).
+
+submit_config_global_complex(Query , {LUser,LServer}) ->
+    SUS = get_us_escaped({LUser,LServer}),
+    SQLQuery = 
+        "UPDATE archive_global_prefs"
+        " SET save = " ++ get_from_query_escaped("global_save",Query) ++ ","
+        "     expire = " ++ get_from_query_escaped("global_expire",Query) ++ ","
+        "     otr = " ++ get_from_query_escaped("global_otr",Query) ++ ","
+        "     method_auto = " ++ get_from_query_escaped("global_method_auto",Query) ++ ","
+        "     method_local = " ++ get_from_query_escaped("global_method_local",Query) ++ ","
+        "     method_manual = " ++ get_from_query_escaped("global_method_manual",Query) ++ ","
+        "     auto_save = " ++ get_from_query_escaped("global_auto_save",Query) ++ 
+        " WHERE us = " ++ SUS,
+    F = fun() ->
+        %TODO: use REPLACE
+        case run_sql_query("SELECT us FROM archive_global_prefs WHERE us = " ++ SUS) of
+            {selected, _, Rs} when Rs /= [] -> ok;
+            _ -> run_sql_query("INSERT INTO archive_global_prefs (us) VALUES (" ++ SUS ++ ")")
+        end,
+        run_sql_query(SQLQuery) end,
+    run_sql_transaction(LServer, F).
+    
+get_from_query_with_default(Key,Query,Default) ->
+    case  lists:keysearch(Key, 1, Query) of
+        {value, {_, Value}} -> Value;
+        _ -> Default
+    end.
+
+search_form( #request{lang = Lang, q = Query } = _Request, US) ->
+    ?XAE("form",[{"method","post"},{"action", ?LINK("search/results")}],
+          [ ?XE("label", [?CT("With: ") ,
+                select_element("with" , [ { "", "--All--" } |
+                                            lists:map( fun({Node,Server,_Count}) -> 
+                                                    With = jlib:jid_to_string({Node,Server,""}),
+                                                    {With, With}  end,
+                                                    get_contacts(US)) ],
+                                get_from_query_with_default("with",Query,""))]),
+            ?BR,
+            ?XE("label", [?CT("From: ") , ?INPUT("text","from", get_from_query_with_default("from",Query,"")),
+                          ?CT(" (date in SQL format,  may be empty)")]),
+            ?BR,
+            ?XE("label", [?CT("To: ") , ?INPUT("text","to", get_from_query_with_default("to",Query,"")),
+                          ?CT(" (date in SQL format,  may be empty)")]),
+            ?BR,
+            ?XE("label", [?CT("Search keyword: ") , ?INPUT("text","keywords",
+                                                        get_from_query_with_default("keywords",Query,""))]),
+            ?BR,
+            ?INPUT("submit","search",?T("Search"))  ]).
+
+
+
+search_results( #request{lang = Lang, q = Query } = _Request, {_, LServer} = US) ->
+    With = case  lists:keysearch("with", 1, Query) of
+        {value, {_, Value}} -> case jlib:string_to_jid(Value) of
+            #jid{ luser = Node , lserver = Server , lresource = "" } -> 
+                " AND with_user ='" ++ ejabberd_odbc:escape(Node) ++ "'" ++
+                " AND with_server ='" ++ ejabberd_odbc:escape(Server) ++ "'";
+            #jid{ luser = Node , lserver = Server , lresource = Resource } -> 
+                " AND with_user ='" ++ ejabberd_odbc:escape(Node) ++ "'" ++
+                " AND with_server ='" ++ ejabberd_odbc:escape(Server) ++ "'" ++
+                " AND with_resource ='" ++ ejabberd_odbc:escape(Resource) ++ "'";
+            _ -> ""
+            end;
+        _ -> ""
+    end,
+    From = case lists:keysearch("from", 1, Query) of
+        {value, {_, V1}} when V1 /= "" -> " AND M.utc >= '" ++ ejabberd_odbc:escape(V1) ++ "'";
+        _ -> ""
+    end,
+    To = case lists:keysearch("to", 1, Query) of
+        {value, {_, V2}} when V2 /= "" -> " AND M.utc <= '" ++ ejabberd_odbc:escape(V2) ++ "'";
+        _ -> ""
+    end,
+    Kw = case lists:keysearch("keywords", 1, Query) of
+        {value, {_, V3}} when V3 /= "" -> " AND body LIKE '%" ++ ejabberd_odbc:escape(V3) ++ "%'";
+        _ -> ""
+    end,
+    
+    F = fun() -> run_sql_query(
+            "SELECT coll_id,subject,with_user,with_server,with_resource,C.utc,body"
+            " FROM archive_collections as C, archive_messages as M"
+            " WHERE C.id = M.coll_id AND C.us = " ++  get_us_escaped(US) ++ 
+            "   AND C.deleted='0'" ++ With ++ From ++ To ++ Kw ++
+            " GROUP BY coll_id") end,
+    case run_sql_transaction(LServer,F) of
+        {selected, _ , []} -> [?PCT("No matches")];
+        {selected, _ , Results} ->
+           lists:map(fun(R) -> format_search_result(R,Lang) end, Results)
+    end.
+
+format_search_result( {Id,Subject,User,Server,Resource,Utc,Body} ,_Lang) ->
+    ?XAE("p",[{"class","search_result"}],
+         [?AC(?LINK("show/" ++ integer_to_list(Id)), jlib:jid_to_string({User,Server,Resource}) ++ " : " ++ escape_str(Subject)),
+          ?C(Body), ?XE("em",[?C(Utc)]) ] ).
+          
+links_previous_next({PrevId,NextId},Lang) ->
+    [?XAE("p",[{"class","links_previous_next"}],
+        links_previous_next_aux("link_prev", ?T("Previous"), PrevId) ++ [?C(" ")] ++
+        links_previous_next_aux("link_next", ?T("Next"), NextId))].
+
+links_previous_next_aux(Class, Text, Id) ->
+    case Id of
+        -1 -> [];
+        _ -> [?XAE("a",[{"href",?LINK("show/" ++ integer_to_list(Id))},{"class",Class}], [?C(Text)])]
+    end.
+
+%------------------------
+
+get_contacts({LUser, LServer}) ->
+    Fun = fun() ->
+        {selected, _ , Contacts} = run_sql_query("SELECT with_user,with_server,COUNT(*)"
+                                                 " FROM archive_collections"
+                                                 " WHERE us = " ++ get_us_escaped({LUser,LServer}) ++ " AND deleted=0"
+                                                 " GROUP BY with_user,with_server"),
+        Contacts end,
+    run_sql_transaction(LServer, Fun).
+
+get_collection_list(Jid, {LUser, LServer}) ->
+    {WithU, WithS, _} = get_jid_escaped(Jid),
+    Fun = fun() ->
+        {selected, _ , List} = run_sql_query("SELECT id,with_user,with_server,with_resource,utc,subject"
+                                                 " FROM archive_collections"
+                                                 " WHERE us = " ++ get_us_escaped({LUser,LServer}) ++ 
+                                                 "  AND deleted=0 "
+                                                 "  AND with_user = " ++ WithU ++
+                                                 "  AND with_server = " ++ WithS),
+        List end,
+    run_sql_transaction(LServer, Fun).
+    
+get_collection(Id,{LUser,LServer}) ->
+    Fun = fun() ->
+        SUS = get_us_escaped({LUser,LServer}),
+        {selected, _ , [{WithU, WithS, WithR, Utc, Subject}] } = run_sql_query(
+                            "SELECT with_user,with_server,with_resource,utc,subject"
+                            " FROM archive_collections"
+                            " WHERE id = '" ++ ejabberd_odbc:escape(Id) ++ "'" 
+                            "  AND us = " ++ SUS),
+        %If the previous query fail, that mean the collection doesn't exist or is not 
+        % one of the users connection.
+
+        {selected, _ , List} = run_sql_query("SELECT utc,dir,body"
+                                                 " FROM archive_messages"
+                                                 " WHERE coll_id = '" ++ ejabberd_odbc:escape(Id) ++ "'"),
+        NextId = case run_sql_query("SELECT id"
+                                    " FROM archive_collections"
+                                    " WHERE us = " ++  SUS ++  " AND deleted=0 "
+                                    "  AND with_user ='" ++ ejabberd_odbc:escape(WithU) ++ "'" ++
+                                    "  AND with_server ='" ++ ejabberd_odbc:escape(WithS) ++ "'" ++
+                                    "  AND utc > '" ++ Utc ++ "'" ++
+                                    " ORDER BY utc LIMIT 1") of
+            {selected, _ , [{V1}]} -> V1;
+            _ -> -1
+        end,
+        PrevId = case run_sql_query("SELECT id"
+                                    " FROM archive_collections"
+                                    " WHERE us = " ++  SUS ++  " AND deleted=0 "
+                                    "  AND with_user ='" ++ ejabberd_odbc:escape(WithU) ++ "'" ++
+                                    "  AND with_server ='" ++ ejabberd_odbc:escape(WithS) ++ "'" ++
+                                    "  AND utc < '" ++ Utc ++ "'" ++
+                                    " ORDER BY utc DESC LIMIT 1") of
+            {selected, _ , [{V2}]} -> V2;
+            _ -> -1
+        end,
+        { {WithU,WithS,WithR} , Utc,  Subject , List, {PrevId,NextId}} end,
+    run_sql_transaction(LServer, Fun).
+
+change_subject(Id,Subject,{LUser,LServer}) ->
+    run_sql_transaction(LServer, fun() -> run_sql_query(
+            "UPDATE archive_collections"
+            " SET subject='"++ ejabberd_odbc:escape(Subject)++"',"
+            "     change_utc=NOW(), change_by='webview'"
+            " WHERE id = '" ++ ejabberd_odbc:escape(Id) ++ "'" 
+            "  AND us = " ++ get_us_escaped({LUser,LServer})) end).
+
+delete_collection(Id,{LUser,LServer}) ->
+    run_sql_transaction(LServer, fun() -> run_sql_query(
+            "UPDATE archive_collections"
+            " SET deleted=1, subject='', thread = '', extra = '', prev_id = NULL, next_id = NULL,"
+            "     change_utc=NOW(), change_by='webview'"
+            " WHERE id = '" ++ ejabberd_odbc:escape(Id) ++ "'" 
+            "  AND us = " ++ get_us_escaped({LUser,LServer})) end).
+
+%------------------------
+% from mod_archive_odbc
+run_sql_query(Query) ->
+    ?MYDEBUG("running query: ~p", [lists:flatten(Query)]),
+    case catch ejabberd_odbc:sql_query_t(Query) of
+        {'EXIT', Err} ->
+            ?ERROR_MSG("unhandled exception during query: ~p", [Err]),
+            exit(Err);
+        {error, Err} ->
+            ?ERROR_MSG("error during query: ~p", [Err]),
+            throw({error, Err});
+        aborted ->
+            ?ERROR_MSG("query aborted ~p", [Query]),
+            throw(aborted);
+        R -> ?MYDEBUG("query result: ~p", [R]),
+            R
+    end.
+
+run_sql_transaction(LServer, F) ->
+    DBHost = gen_mod:get_module_opt(LServer, ?MODULE, db_host, LServer),
+    case ejabberd_odbc:sql_transaction(DBHost, F) of
+        {atomic, R} ->
+            ?MYDEBUG("succeeded transaction: ~p", [R]),
+            R;
+        {error, Err} -> {error, Err};
+        E ->
+            ?ERROR_MSG("failed transaction: ~p, stack: ~p", [E, process_info(self(),backtrace)]),
+            {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+    
+get_us_escaped({LUser, LServer}) ->
+    "'" ++ ejabberd_odbc:escape(LUser ++ "@" ++ LServer) ++ "'".
+
+get_jid_escaped({LUser, LServer, LResource}) ->
+    {"'" ++ ejabberd_odbc:escape(LUser), "'" ++ ejabberd_odbc:escape(LServer),
+     "'" ++ ejabberd_odbc:escape(LResource)};
+
+get_jid_escaped(#jid{luser = LUser, lserver = LServer, lresource=LResource}) ->
+    {"'" ++ ejabberd_odbc:escape(LUser) ++ "'", "'" ++ ejabberd_odbc:escape(LServer) ++ "'",
+     "'" ++ ejabberd_odbc:escape(LResource) ++ "'"}.
+
+decode_integer(Val) when is_integer(Val) ->
+    Val;
+decode_integer(null) ->
+    -1;
+decode_integer(Val) ->
+    list_to_integer(Val).
+
+escape_str(null) -> "";
+escape_str(Str) -> Str.
diff --git a/mod_archive/src/pg_mod_archive.sql b/mod_archive/src/pg_mod_archive.sql
new file mode 100644
index 0000000..9638aad
--- /dev/null
+++ b/mod_archive/src/pg_mod_archive.sql
@@ -0,0 +1,60 @@
+CREATE SEQUENCE archive_collection_sid_seq;
+
+CREATE TABLE archive_collection (
+    sid bigint NOT NULL DEFAULT nextval('archive_collection_sid_seq') PRIMARY KEY,
+    username text NOT NULL,
+    jid_u text NOT NULL,
+    jid_s text NOT NULL,
+    jid_r text NOT NULL,
+    start int8 NOT NULL,
+    subject text NOT NULL
+);
+
+CREATE INDEX i_archive_collection_username ON archive_collection USING btree (username);
+CREATE INDEX i_archive_collection_user_jid ON archive_collection USING btree (username, jid_u, jid_s);
+CREATE UNIQUE INDEX i_archive_collection_user_jid_start ON archive_collection USING btree (username, jid_u, jid_s, start);
+
+CREATE SEQUENCE archive_message_ser_seq;
+
+CREATE TABLE archive_message (
+    sid bigint REFERENCES archive_collection ON DELETE CASCADE,
+    ser bigint NOT NULL DEFAULT nextval('archive_message_ser_seq'),
+    direction_to boolean NOT NULL,
+    secs int NOT NULL,
+    utc int8 NOT NULL,
+    name text NOT NULL,
+    jid text NOT NULL,
+    body text NOT NULL
+);
+
+CREATE INDEX i_archive_message_sid ON archive_message USING btree (sid);
+
+-- FTS support
+-- Requires tsearch2 and btree_gist
+
+ALTER TABLE archive_collection ADD COLUMN fts tsvector NOT NULL DEFAULT to_tsvector('');
+
+CREATE INDEX i_archive_collection_fts on archive_collection USING gist(username, fts);
+
+CREATE RULE r_archive_fts_update AS ON INSERT TO archive_message DO ALSO
+    UPDATE archive_collection
+        SET fts = fts || to_tsvector(translate(NEW.body, '<>&', '   '))
+        WHERE sid = NEW.sid;
+
+--CREATE TABLE archive_fts (
+--    sid bigint PRIMARY KEY REFERENCES archive_collection ON DELETE CASCADE,
+--    username text NOT NULL,
+--    fts tsvector
+--);
+--
+--CREATE INDEX i_archive_fts on archive_fts USING gist(username, fts);
+--
+--CREATE RULE r_archive_fts_empty AS ON INSERT TO archive_collection DO ALSO
+--    INSERT INTO archive_fts(sid, username, fts)
+--        VALUES (NEW.sid, NEW.username, to_tsvector(''));
+--
+--CREATE RULE r_archive_fts_update AS ON INSERT TO archive_message DO ALSO
+--    UPDATE archive_fts
+--        SET fts = fts || to_tsvector(translate(NEW.body, '<>&', '   '))
+--        WHERE sid = NEW.sid;
+
diff --git a/mod_openid/README.txt b/mod_openid/README.txt
new file mode 100644
index 0000000..edf4708
--- /dev/null
+++ b/mod_openid/README.txt
@@ -0,0 +1,46 @@
+
+
+		***************
+		  PLEASE NOTE
+		***************
+
+	This module does NOT work
+	with ejabberd 13 or newer.
+
+		***************
+
+
+mod_openid 
+Transform the Jabber Server in an openid provider.
+(http://openid.net/)
+
+Author: Olivier Goffart <ogoffart@kde.org>
+
+Motivation:
+There are already severals existing openid provider that uses the JabberId as id. 
+( http://openid.xmpp.za.net/ http://xmppid.net/ )
+But none of them are open source.
+The idea is that having the openid server in the same place as the jabber server reduce 
+the size of the security chain we have to trust.   
+Instead of trusting both the jabber server and the openid provider, we can trust only 
+the Jabber server.
+
+
+Status:
+Currently, the implementation just ask for the jabber password. 
+Some security function are also lacking.
+The plan was to use something similair to XEP-0070
+
+How it works:
+Add in your ejabberd.cfg
+{listen, [  ...
+          {5280, ejabberd_http,    [http_poll, web_admin, {request_handlers , [{["openid"],mod_openid }]}]} ,
+
+Then your open id is    http://server.org:5280/openid/user@server.org
+Hopelifully it should be possible to have more nice-looking urls.
+
+
+Future:
+I have no plan to continue working on it.  Feel free to take over.
+I'd be happy to reply to questions.
+
diff --git a/mod_openid/mod_openid.spec.broken b/mod_openid/mod_openid.spec.broken
new file mode 100644
index 0000000..1ed92d8
--- /dev/null
+++ b/mod_openid/mod_openid.spec.broken
@@ -0,0 +1,5 @@
+author: "Olivier Goffart <ogoffart at kde.org>"
+category: "service"
+summary: "Transform the Jabber Server in an openid provider"
+home: "https://github.com/processone/ejabberd-contrib/tree/master/"
+url: "git@github.com:processone/ejabberd-contrib.git"
diff --git a/mod_openid/src/mod_openid.erl b/mod_openid/src/mod_openid.erl
new file mode 100644
index 0000000..3e2c5be
--- /dev/null
+++ b/mod_openid/src/mod_openid.erl
@@ -0,0 +1,251 @@
+
+%%%----------------------------------------------------------------------
+%%% File    : mod_openid.erl
+%%% Author  : Olivier Goffart <ogoffart@kde.org>
+%%% Purpose : Open id provider using XEP-0070
+%%% Created : 24 Dec 2007 Olivier Goffart
+%%%----------------------------------------------------------------------
+%%% Copyright (c) 2007-2008 Olivier Goffart
+%%%----------------------------------------------------------------------
+
+
+%% WARNING: secret/1 and new_assoc/2 MUST be implemented correctly for security issue
+
+-module(mod_openid).
+-author('ogoffart@kde.org').
+
+%% External exports
+-export([process/2]).
+
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("web/ejabberd_http.hrl").
+-include("web/ejabberd_web_admin.hrl").
+
+-record(profile, {identity, server, lang, jid}).
+
+-define(MYDEBUG(Format,Args),io:format("D(~p:~p:~p) : "++Format++"~n",
+				       [calendar:local_time(),?MODULE,?LINE]++Args)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% PART 1 : OpenID
+
+process([Jid],  #request{ q = Query, lang = Lang} = Request) ->
+    %%?MYDEBUG("Auth Failed ~p ~n", [C]),
+    %%[{server, Server}] = ets:lookup(mod_openid, server)
+    JJid = jlib:string_to_jid(Jid),
+    Server = "http://" ++ JJid#jid.server ++ ":5280/openid",
+    Profile = #profile{identity = Server  ++"/"++ Jid,
+		       server = Server ++ "/" ++ Jid,
+		       lang = Lang, jid= JJid},
+    case lists:keysearch("openid.mode", 1, Query) of
+	{value, {_, "associate"}}  -> associate(Query,Profile);
+	{value, {_, "checkid_immediate"}}  -> checkid_immediate(Query,Profile);
+	{value, {_, "checkid_setup"}}  -> checkid_setup(Request,Profile);
+	{value, {_, "check_authentication"}}  -> check_authentication(Query,Profile);
+	_ -> default_page(Profile)
+    end;
+
+process(_,  _) ->error_400("Invalid identity").
+
+default_page(Profile) ->
+    make_xhtml([{xmlelement,"h1",[],[{xmlcdata, "400 Bad Request"}]}],Profile).
+
+associate(_,Profile) ->
+    not_implemented(Profile).
+
+checkid_immediate(_,Profile) ->
+    not_implemented(Profile).
+
+checkid_setup(#request{ q = Query} = Request, Profile) ->
+    case lists:keysearch("openid.return_to", 1, Query) of
+	{value, {_, ReturnTo}} ->
+	    case catch verify_id(Request,Profile) of
+		verified -> checkid2(Request,Profile,ReturnTo);
+		{return, Value} -> Value;
+		_ -> redirect_reply([{"openid_mode","error"},
+				     {"openid_error","InternalError"}], ReturnTo)
+	    end;
+	_ -> error_400("Missing 'openid.return_to'")
+    end.
+
+%% Helper for checkid_setup
+checkid2(#request{ q = Query} = _Request, Profile,ReturnTo) ->
+    case lists:keysearch("openid.identity", 1, Query) of
+	{value, {_, Ident}} when Ident ==  Profile#profile.identity ->
+	    {AssocHandle,Secret} =
+		case lists:keysearch("openid.assoc_handle", 1, Query) of
+		    {value, {_, V}} -> case secret(V) of
+					   {ok, S} -> {V,S};
+					   false -> new_assoc()
+				       end;
+		    false -> new_assoc()
+		end,
+	    _TrustRoot = case lists:keysearch("openid.trust_root", 1, Query) of
+			     {value, {_, Vs}} -> Vs;
+			     false -> ReturnTo
+			 end,
+	    Params = [{"identity",Ident} ,
+		      {"return_to",ReturnTo}, {"assoc_handle", AssocHandle} ],
+	    {Signed,Sig} = make_signature(Params,Secret),
+	    redirect_reply(
+	      lists:map(fun({K,V}) -> {"openid_" ++ K, V} end, Params)
+	      ++ [{"openid_mode","id_res"},
+		  {"openid_signed", Signed},
+		  {"openid_sig", Sig}],
+	      ReturnTo);
+	_ -> redirect_reply([{"openid_mode","error"},
+			     {"openid_error","WrongIdentity"}],
+			    ReturnTo)
+    end.
+
+check_authentication(Query, _Profile) ->
+    case lists:keysearch("openid.assoc_handle", 1, Query) of
+	{value, {_, AssocHandle} } ->
+	    case lists:keysearch("openid.sig", 1, Query) of
+		{value, {_, Sig} } ->
+		    case lists:keysearch("openid.signed", 1, Query) of
+			{value, {_, Signed} } ->
+			    direct_reply([{"openid.mode","id_res"},
+					  {"is_valid",
+					   check_authentication2(AssocHandle, Sig,
+								 Signed, Query)}]);
+			false -> error_reply("missing sig")
+		    end;
+		false -> error_reply("missing sig")
+	    end;
+	false -> error_reply("missing handle")
+    end.
+
+%% Helper for check_authentication
+%% return "true" if the authentication is valid, "false" otherwhise
+check_authentication2(AssocHandle, Sig, Signed, Query) ->
+    case secret(AssocHandle) of
+	{ok, Secret} ->
+	    case catch make_signature(retrieve_params(Signed,Query),Secret) of
+		{Signed,Sig} -> "true";
+		_ -> "false"
+	    end;
+	_ -> "false"
+    end.
+
+%% Fields is a list of fields which should be in the query as openid.Field
+%% return the list of argument [{Key,Value}] as they appears in the query
+retrieve_params(Fields,Query) ->
+    FList = re:split(Fields, ",", [{return, list}]),
+    retrieve_params_recurse(FList,Query).
+retrieve_params_recurse([],_) -> [];
+retrieve_params_recurse([Key | Tail ], Query) ->
+    {value, {_, Value} } = lists:keysearch("openid." ++ Key, 1, Query),
+    [ {Key, Value} | retrieve_params_recurse(Tail,Query) ].
+
+not_implemented(Profile) ->
+    make_xhtml([{xmlelement,"h1",[],[{xmlcdata, "NOT IMPLEMENTED"}]}],Profile).
+
+make_xhtml(Els,  #profile{lang=Lang} = Profile) ->
+    {200, [html],
+     {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
+			   {"xml:lang", Profile#profile.lang},
+			   {"lang", Profile#profile.lang}],
+      [{xmlelement, "head", [],
+	[?XCT("title", "ejabberd OpenId Provider"),
+	 {xmlelement, "meta", [{"http-equiv", "Content-Type"},
+			       {"content", "text/html; charset=utf-8"}], []},
+	 {xmlelement, "link", [{"rel", "openid.server"},
+			       {"href", Profile#profile.server}], []},
+	 {xmlelement, "link", [{"rel", "openid.delegate"},
+			       {"href", Profile#profile.identity}], []}]},
+       ?XE("body", Els) ]}}.
+
+error_400(Message) ->
+    {400, [], ejabberd_web:make_xhtml([{xmlelement,"h1",[],
+					[{xmlcdata, "400 Bad Request"}]},
+				       {xmlelement,"p",[],[{xmlcdata, Message}]}])}.
+
+%% Ask the user agent to go to the ReturnTo URL with the specified
+%% Params in the query
+redirect_reply(Params, ReturnTo) ->
+    Delim = case lists:member($?, ReturnTo) of
+		true -> $&;
+		false -> $?
+	    end,
+    {303, [{"Location", ReturnTo ++ build_query(Params, Delim)}], []}.
+
+%% Given a list of {Key,Value}, construct the query string, starting
+%% with Delim (either '?' or '&')
+build_query([ {Key,Value} | Tail ], Delim) ->
+    [Delim] ++ Key ++ "=" ++ Value ++ build_query(Tail, $&);
+build_query([], _) ->
+    [].
+
+%% return the parameters directly in the HTTP reply
+direct_reply(Params) ->
+    {200, [], build_reply(Params)}.
+
+%% Given a list of {Key,Value}, return the string which should
+%% appears in the dirrect reply.
+build_reply([ {Key,Value} | Tail ]) ->
+    Key ++ ":" ++ Value ++ "\n" ++ build_reply(Tail);
+build_reply([]) -> [].
+
+%% Direct reply of an error
+error_reply(Message) ->
+    {400, [], "error:" ++ Message ++ "\n"}.
+
+%% Given a list of parameters, sign them.
+make_signature(Param, Secret) ->
+    {field_list(Param),
+     jlib:encode_base64(binary_to_list(crypto:sha_mac(Secret, build_reply(Param))))}.
+
+%% Given a list of parameters, return a string containing the list of
+%% key separated by comas.
+field_list([]) -> [];
+field_list([ {Key,_Value} ]) -> Key;
+field_list([ {Key,_Value} | Tail ]) ->
+    Key ++ "," ++ field_list(Tail).
+
+%% TODO: thoses function need to be implemented for security
+%% given an association key, return {ok, Secret} or false
+secret(_) -> {ok, "Secret"}.
+%% create a new association and return {Key, Secret}
+new_assoc() -> {"assoc", "Secret"}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% PART 2 : Glue
+
+%% return "verified" if the user agent is authorized,  otherwhise, return an HTTP reply.
+verify_id(#request{auth = Auth} = _Request, #profile{ jid = Jid} = _Profile) ->
+    %% TODO verify that the Jid in the identity is the same as the Jid in the profile
+    Exp = {Jid#jid.luser, Jid#jid.lserver},
+    case get_auth(Auth) of
+	Exp ->
+	    verified;
+	C ->
+	    ?MYDEBUG("Auth Failed ~p ~n", [C]),
+	    {return, {401,
+		      [{"WWW-Authenticate", "basic realm=\"ejabberd-mod_openid\""}],
+		      ejabberd_web:make_xhtml([{xmlelement, "h1", [],
+						[{xmlcdata, "401 Unauthorized"}]}])}}
+    end.
+
+%% return the {User,Server} jid if the authentification is correct, or unauthorized
+get_auth(Auth) ->
+    case Auth of
+	{SJID, P} ->
+	    case jlib:string_to_jid(SJID) of
+		error ->
+		    unauthorized;
+		#jid{user = U, server = S} ->
+		    case ejabberd_auth:check_password(U, S, P) of
+			true ->
+			    {U, S};
+			false ->
+			    unauthorized
+		    end
+	    end;
+	_ ->
+	    unauthorized
+    end.
+
diff --git a/mod_profile/README.txt b/mod_profile/README.txt
new file mode 100644
index 0000000..627b106
--- /dev/null
+++ b/mod_profile/README.txt
@@ -0,0 +1,37 @@
+
+
+		***************
+		  PLEASE NOTE
+		***************
+
+	This module does NOT work
+	with ejabberd 13 or newer.
+
+		***************
+
+
+	mod_profile - User Profile (XEP-0154) in Mnesia table 
+
+	http://www.ejabberd.im/mod_profile
+	Author: Magnus Henoch
+	    mailto:henoch@dtek.chalmers.se
+	    xmpp:legoscia@jabber.cd.chalmers.se
+
+
+This module supports storing and retrieving a profile according to
+XEP-0154.  It does no validation of the data, but simply stores
+whatever XML the user sends in a Mnesia table.  The PEP parts of
+XEP-0154 are out of scope for this module.
+
+In the beginning of the erl file it says what parts of the XEP
+are implemented.
+
+
+	BASIC CONFIGURATION
+	===================
+
+To install this module, follow the general build instructions, and add the
+following to your configuration, among the other modules:
+
+{mod_profile, []}
+
diff --git a/mod_profile/conf/mod_profile.yml b/mod_profile/conf/mod_profile.yml
new file mode 100644
index 0000000..7d138b1
--- /dev/null
+++ b/mod_profile/conf/mod_profile.yml
@@ -0,0 +1,2 @@
+modules:
+  mod_profile: {}
diff --git a/mod_profile/mod_profile.spec.broken b/mod_profile/mod_profile.spec.broken
new file mode 100644
index 0000000..6c6e4fc
--- /dev/null
+++ b/mod_profile/mod_profile.spec.broken
@@ -0,0 +1,5 @@
+author: "Magnus Henoch <henoch at dtek.chalmers.se>"
+category: "data"
+summary: "User Profile (XEP-0154) in Mnesia table"
+home: "https://github.com/processone/ejabberd-contrib/tree/master/"
+url: "git@github.com:processone/ejabberd-contrib.git"
diff --git a/mod_profile/src/mod_profile.erl b/mod_profile/src/mod_profile.erl
new file mode 100644
index 0000000..70fdb95
--- /dev/null
+++ b/mod_profile/src/mod_profile.erl
@@ -0,0 +1,308 @@
+%%%----------------------------------------------------------------------
+%%% File    : mod_profile.erl
+%%% Author  : Magnus Henoch <henoch@dtek.chalmers.se>
+%%% Purpose : Store profile stanzas in Mnesia table (XEP-0154 0.5 IQ semantics)
+%%% Created : 22 Oct 2006 by Magnus Henoch <henoch@dtek.chalmers.se>
+%%% Id      : $Id: mod_profile.erl 1039 2009-11-24 22:47:40Z badlop $
+%%%----------------------------------------------------------------------
+
+%%%% Documentation
+
+%%% Protocol implementation (as of XEP-0154 0.6)
+%%%
+%%% 4.1. Publishing a Full Profile
+%%% + Stanza is processed, and a response is returned
+%%% - But the stanza is stored in a Mnesia table, not in PubSub
+%%%
+%%% 4.2. Updating One or More Profile Fields
+%%% - Not possible because PubSub is not used for storage
+%%%
+%%% 5.1. Discovering Support
+%%% + Fully implemented
+%%%
+%%% 5.2. Requesting Full Profile
+%%% + Stanza is processed, and a response is returned
+%%% - The information is read from Mnesia table, not from PubSub
+%%%
+%%% 5.3. Receiving Profile Updates
+%%% - Does not work because storage is done on Mnesia, not PubSub
+%%%
+%%% 7. Security Considerations
+%%% - Implement Access option to restrict who is allowed to read/write profiles
+%%% - any pubsub node it may create for profile data has an access model of "presence" or "roster"
+%%%
+%%%
+%%% The server should include code to forward information of IQ queries into PubSub,
+%%% so all the information provided with any semantic mentioned in XEP-0154
+%%% must be stored in PubSub.
+%%% stpeter: I think the idea was (B), but it's not well-defined
+%%%
+%%%
+%%% Custom IQ semantics for XEP-0154:
+%%%
+%%% 5.4. Requesting One or More Profile Fields
+%%%
+%%% A user can query some specific fields from his profile:
+%%%
+%%% <iq type='get' to='user@localhost'>
+%%%   <profile xmlns='urn:xmpp:tmp:profile'>
+%%%     <x xmlns='jabber:x:data' type='submit'>
+%%%       <field var='FORM_TYPE' type='hidden'><value>urn:xmpp:tmp:profile</value></field>
+%%%       <field var='nickname'/>
+%%%       <field var='country'/>
+%%%       <field var='postal_code'/>
+%%%     </x>
+%%%   </profile>
+%%% </iq>
+%%%
+%%% The server will return only the fields that were requested and the user had defined previously:
+%%%
+%%% <iq from='user@localhost' type='result' to='user@localhost/Home'>
+%%%   <profile xmlns='urn:xmpp:tmp:profile'>
+%%%     <x xmlns='jabber:x:data' type='result'>
+%%%       <field var='FORM_TYPE' type='hidden'><value>urn:xmpp:tmp:profile</value></field>
+%%%       <field var='nickname'><value>Drummy</value></field>
+%%%       <field var='country'><value>DK</value></field>
+%%%     </x>
+%%%   </profile>
+%%% </iq>
+%%%
+%%% 4.3. Updating One or More Profile Fields using IQ Semantics
+%%%
+%%% - if a field is set empty value, delete field in the stored profile
+%%%
+
+%%%=======================
+%%%% Headers
+
+-module(mod_profile).
+
+-author('henoch@dtek.chalmers.se').
+
+-behaviour(gen_mod).
+
+-export([start/2, stop/1, process_sm_iq/3,
+	 get_sm_features/5, remove_user/2]).
+
+-include("ejabberd.hrl").
+
+-include("jlib.hrl").
+
+-include("logger.hrl").
+
+-record(profile, {us, fields}).
+
+-define(NS_PROFILE, <<"urn:xmpp:tmp:profile">>).
+
+%%%=======================
+%%%% gen_mod
+
+start(Host, Opts) ->
+    mnesia:create_table(profile,
+			[{disc_only_copies, [node()]},
+			 {attributes, record_info(fields, profile)}]),
+    ejabberd_hooks:add(remove_user, Host, ?MODULE,
+		       remove_user, 50),
+    ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
+		       get_sm_features, 50),
+    IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue),
+    gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
+				  ?NS_PROFILE, ?MODULE, process_sm_iq, IQDisc).
+
+stop(Host) ->
+    ejabberd_hooks:delete(remove_user, Host, ?MODULE,
+			  remove_user, 50),
+    ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
+			  get_sm_features, 50),
+    gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
+				     ?NS_PROFILE).
+
+%%%=======================
+%%%% Hooks
+
+get_sm_features({error, _} = Acc, _From, _To, _Node,
+		_Lang) ->
+    Acc;
+get_sm_features(Acc, _From, _To, Node, _Lang) ->
+    case Node of
+      [] ->
+	  case Acc of
+	    {result, Features} ->
+		{result, [?NS_PROFILE | Features]};
+	    empty -> {result, [?NS_PROFILE]}
+	  end;
+      _ -> Acc
+    end.
+
+remove_user(User, Server) ->
+    LUser = jlib:nodeprep(User),
+    LServer = jlib:nameprep(Server),
+    US = {LUser, LServer},
+    F = fun () -> mnesia:delete({profile, US}) end,
+    mnesia:transaction(F).
+
+%%%=======================
+%%%% IQ handler
+
+process_sm_iq(From, To,
+	      #iq{type = Type, sub_el = SubEl} = IQ) ->
+    case Type of
+      set ->
+	  #jid{luser = LUser, lserver = LServer} = From,
+	  process_sm_iq_set(LUser, LServer, SubEl, IQ);
+      get ->
+	  #jid{luser = LUser, lserver = LServer} = To,
+	  process_sm_iq_get(LUser, LServer, SubEl, IQ)
+    end.
+
+process_sm_iq_set(LUser, LServer, SubEl, IQ) ->
+    case lists:member(LServer, ?MYHOSTS) of
+      true ->
+	  #xmlel{children = SubSubEls} = SubEl,
+	  ElsList = [El
+		     || #xmlel{name = Name} = El
+			    <- fxml:remove_cdata(SubSubEls),
+			Name == <<"x">>],
+	  case ElsList of
+	    [XData] ->
+		case set_profile(LUser, LServer, XData) of
+		  ok -> IQ#iq{type = result, sub_el = []};
+		  {error, Error} ->
+		      IQ#iq{type = error, sub_el = [SubEl, Error]}
+		end;
+	    _ ->
+		IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
+	  end;
+      false ->
+	  IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
+    end.
+
+process_sm_iq_get(LUser, LServer, SubEl, IQ) ->
+    ReqFields = get_requested_fields(SubEl),
+    case get_profile(LUser, LServer, ReqFields) of
+      {ok, Fields} ->
+	  XEl = #xmlel{name = <<"x">>,
+		       attrs =
+			   [{<<"xmlns">>, <<"jabber:x:data">>},
+			    {<<"type">>, <<"result">>}],
+		       children = Fields},
+	  ProfileEl = #xmlel{name = <<"profile">>,
+			     attrs = [{<<"xmlns">>, ?NS_PROFILE}],
+			     children = [XEl]},
+	  IQ#iq{type = result, sub_el = [ProfileEl]};
+      {error, user_not_found} ->
+	  IQ#iq{type = error,
+		sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
+      {error, OtherError} ->
+	  ?ERROR_MSG("Problem found when getting profile of "
+		     "~p@~p:~n~p",
+		     [LUser, LServer, OtherError]),
+	  IQ#iq{type = error,
+		sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
+    end.
+
+%%%=======================
+%%%% Set profile
+
+set_profile(LUser, LServer,
+	    #xmlel{name = <<"x">>, children = Els}) ->
+    US = {LUser, LServer},
+    F = fun () ->
+		mnesia:write(#profile{us = US, fields = Els})
+	end,
+    case mnesia:transaction(F) of
+      {atomic, _} -> ok;
+      _ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
+    end.
+
+%%%=======================
+%%%% Get profile
+
+get_profile(LUser, LServer, []) ->
+    US = {LUser, LServer},
+    F = fun () -> mnesia:read({profile, US}) end,
+    case mnesia:transaction(F) of
+      {atomic, [#profile{fields = Fields}]} -> {ok, Fields};
+      {atomic, []} -> {error, user_not_found};
+      OtherResult -> {error, OtherResult}
+    end;
+get_profile(LUser, LServer, ReqFields) ->
+    case get_profile(LUser, LServer, []) of
+      {ok, Fields} ->
+	  filter_profile_fields(Fields, ReqFields);
+      Other -> Other
+    end.
+
+filter_profile_fields(Fields, ReqFields) ->
+    filter_profile_fields(Fields, ReqFields, []).
+
+filter_profile_fields(StoredFields,
+		      [#xmlel{name = <<"field">>, attrs = Attrs,
+			      children = []}
+		       | ReqFields],
+		      ResFields) ->
+    case lists:keysearch(Attrs, 3, StoredFields) of
+      {value, StoredField} ->
+	  filter_profile_fields(StoredFields, ReqFields,
+				[StoredField | ResFields]);
+      false ->
+	  filter_profile_fields(StoredFields, ReqFields,
+				ResFields)
+    end;
+filter_profile_fields(StoredFields,
+		      [_FieldForm | ReqFields], ResFields) ->
+    filter_profile_fields(StoredFields, ReqFields,
+			  ResFields);
+filter_profile_fields(_StoredFields, [], ResFields) ->
+    FieldFormType = #xmlel{name = <<"field">>,
+			   attrs =
+			       [{<<"var">>, <<"FORM_TYPE">>},
+				{<<"type">>, <<"hidden">>}],
+			   children =
+			       [#xmlel{name = <<"value">>, attrs = [],
+				       children =
+					   [{xmlcdata,
+					     <<"urn:xmpp:tmp:profile">>}]}]},
+    {ok, [FieldFormType | lists:reverse(ResFields)]}.
+
+%% TODO: la respuesta a una iq query de fields especificos ha de incluir
+
+%%%=======================
+%%%% Mnesia storage
+
+%%%=======================
+%%%% PubSub storage
+
+%%%=======================
+%%%% XML processing
+
+%% Copied from exmpp_xml.erl, then customized
+
+get_requested_fields(SubEl) ->
+    case fxml:get_subtag(SubEl, <<"x">>) of
+      false -> [];
+      XEl -> get_elements(XEl, <<"field">>)
+    end.
+
+get_elements(#xmlel{name = <<"x">>,
+		    children = Children},
+	     Name) ->
+    get_elements2(Children, Name);
+get_elements(_, _Name) -> [].
+
+get_elements2([], _Name) -> [];
+get_elements2(Children, Name) ->
+    lists:filter(filter_by_name(Name), Children).
+
+filter_by_name(Searched_Name) ->
+    fun (XML_Element) ->
+	    element_matches(XML_Element, Searched_Name)
+    end.
+
+element_matches(#xmlel{name = Name}, Name) -> true;
+element_matches(_XML_Element, _Name) -> false.
+
+%%%================
+
+%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
+

More details

Full run details

Historical runs