changeset 1668:41c26b7d2890

Install mcabber headers * Change mcabber headers naming scheme * Move 'src/' -> 'mcabber/' * Add missing include <mcabber/config.h>'s * Create and install clean config.h version in 'include/' * Move "dirty" config.h version to 'mcabber/' * Add $(top_srcdir) to compiler include path * Update modules HOWTO
author Myhailo Danylenko <isbear@ukrpost.net>
date Mon, 18 Jan 2010 15:36:19 +0200
parents 8af0e0ad20ad
children 004739237999
files mcabber/INSTALL mcabber/Makefile.am mcabber/configure.ac mcabber/hgcset.sh mcabber/include/config.h.in mcabber/mcabber/COPYING mcabber/mcabber/Makefile.am mcabber/mcabber/caps.c mcabber/mcabber/caps.h mcabber/mcabber/commands.c mcabber/mcabber/commands.h mcabber/mcabber/compl.c mcabber/mcabber/compl.h mcabber/mcabber/events.c mcabber/mcabber/events.h mcabber/mcabber/fifo.c mcabber/mcabber/fifo.h mcabber/mcabber/hbuf.c mcabber/mcabber/hbuf.h mcabber/mcabber/help.c mcabber/mcabber/help.h mcabber/mcabber/histolog.c mcabber/mcabber/histolog.h mcabber/mcabber/hooks.c mcabber/mcabber/hooks.h mcabber/mcabber/logprint.h mcabber/mcabber/main.c mcabber/mcabber/main.h mcabber/mcabber/nohtml.c mcabber/mcabber/nohtml.h mcabber/mcabber/otr.c mcabber/mcabber/otr.h mcabber/mcabber/pgp.c mcabber/mcabber/pgp.h mcabber/mcabber/roster.c mcabber/mcabber/roster.h mcabber/mcabber/screen.c mcabber/mcabber/screen.h mcabber/mcabber/settings.c mcabber/mcabber/settings.h mcabber/mcabber/utf8.c mcabber/mcabber/utf8.h mcabber/mcabber/utils.c mcabber/mcabber/utils.h mcabber/mcabber/xmpp.c mcabber/mcabber/xmpp.h mcabber/mcabber/xmpp_defines.h mcabber/mcabber/xmpp_helper.c mcabber/mcabber/xmpp_helper.h mcabber/mcabber/xmpp_iq.c mcabber/mcabber/xmpp_iq.h mcabber/mcabber/xmpp_iqrequest.c mcabber/mcabber/xmpp_iqrequest.h mcabber/mcabber/xmpp_muc.c mcabber/mcabber/xmpp_muc.h mcabber/mcabber/xmpp_s10n.c mcabber/mcabber/xmpp_s10n.h mcabber/src/COPYING mcabber/src/Makefile.am mcabber/src/caps.c mcabber/src/caps.h mcabber/src/commands.c mcabber/src/commands.h mcabber/src/compl.c mcabber/src/compl.h mcabber/src/events.c mcabber/src/events.h mcabber/src/fifo.c mcabber/src/fifo.h mcabber/src/hbuf.c mcabber/src/hbuf.h mcabber/src/help.c mcabber/src/help.h mcabber/src/histolog.c mcabber/src/histolog.h mcabber/src/hooks.c mcabber/src/hooks.h mcabber/src/logprint.h mcabber/src/main.c mcabber/src/main.h mcabber/src/nohtml.c mcabber/src/nohtml.h mcabber/src/otr.c mcabber/src/otr.h mcabber/src/pgp.c mcabber/src/pgp.h mcabber/src/roster.c mcabber/src/roster.h mcabber/src/screen.c mcabber/src/screen.h mcabber/src/settings.c mcabber/src/settings.h mcabber/src/utf8.c mcabber/src/utf8.h mcabber/src/utils.c mcabber/src/utils.h mcabber/src/xmpp.c mcabber/src/xmpp.h mcabber/src/xmpp_defines.h mcabber/src/xmpp_helper.c mcabber/src/xmpp_helper.h mcabber/src/xmpp_iq.c mcabber/src/xmpp_iq.h mcabber/src/xmpp_iqrequest.c mcabber/src/xmpp_iqrequest.h mcabber/src/xmpp_muc.c mcabber/src/xmpp_muc.h mcabber/src/xmpp_s10n.c mcabber/src/xmpp_s10n.h
diffstat 109 files changed, 22299 insertions(+), 22213 deletions(-) [+]
line wrap: on
line diff
--- a/mcabber/INSTALL	Tue Feb 02 21:27:26 2010 +0100
+++ b/mcabber/INSTALL	Mon Jan 18 15:36:19 2010 +0200
@@ -4,7 +4,7 @@
 ... and if you want to install the software:
 $ make install
 (If you don't want to install it, the "mcabber" binary lies in
-the src/ directory after the build procedure)
+the mcabber/ directory after the build procedure)
 
 You will need the Loudmouth library, version >= 1.4.3 is recommended.
 
--- a/mcabber/Makefile.am	Tue Feb 02 21:27:26 2010 +0100
+++ b/mcabber/Makefile.am	Mon Jan 18 15:36:19 2010 +0200
@@ -1,2 +1,2 @@
-SUBDIRS = src doc
+SUBDIRS = mcabber doc
 ACLOCAL_AMFLAGS = -I macros
--- a/mcabber/configure.ac	Tue Feb 02 21:27:26 2010 +0100
+++ b/mcabber/configure.ac	Mon Jan 18 15:36:19 2010 +0200
@@ -4,8 +4,9 @@
 AC_PREREQ(2.59)
 AC_INIT([mcabber],[0.10.0-dev],[mcabber@lilotux.net])
 AM_INIT_AUTOMAKE
-AC_CONFIG_SRCDIR([src])
-AM_CONFIG_HEADER(config.h)
+AC_CONFIG_SRCDIR([mcabber])
+AM_CONFIG_HEADER(mcabber/config.h)
+#AC_CONFIG_HEADER(include/config.h)
 
 #AC_PROG_LIBTOOL
 AC_PROG_RANLIB
@@ -253,11 +254,12 @@
 fi
 
 AM_CONDITIONAL([OTR], [test x$libotr_found = xyes])
+AM_CONDITIONAL([INSTALL_HEADERS], [test x$enable_modules = xyes])
 
 # We need _GNU_SOURCE for strptime() and strcasestr()
 CFLAGS="$CFLAGS -D_GNU_SOURCE"
 
-AC_CONFIG_FILES([src/Makefile
+AC_CONFIG_FILES([mcabber/Makefile
                  doc/Makefile
                  doc/guide/Makefile
                  doc/help/Makefile
--- a/mcabber/hgcset.sh	Tue Feb 02 21:27:26 2010 +0100
+++ b/mcabber/hgcset.sh	Mon Jan 18 15:36:19 2010 +0200
@@ -1,7 +1,7 @@
 #! /bin/sh
 
 if [ ! -f logprint.h ]; then
-  echo "You are not in the src directory" >&2
+  echo "You are not in the mcabber directory" >&2
   exit 1
 fi
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/include/config.h.in	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,55 @@
+#ifndef __MCABBER_CONFIG_H__
+#define __MCABBER_CONFIG_H__ 1
+
+/* ... */
+#undef MODULES_ENABLE
+
+/* ... */
+#undef HAVE_LIBOTR
+
+/* ... */
+#undef HAVE_GPGME
+
+/* ... */
+#undef HAVE_NCURSESW_NCURSES_H
+
+/* ... */
+#undef HAVE_NCURSES_NCURSES_H
+
+/* ... */
+#undef WITH_ENCHANT
+
+/* ... */
+#undef WITH_ASPELL
+
+/* ... */
+#undef JEP0022
+
+/* ... */
+#undef JEP0085
+
+/* ... */
+#undef HAVE_UNICODE
+
+/* ... */
+#undef HAVE_WCHAR_H
+
+/* ... */
+#undef HAVE_WCTYPE_H
+
+/* ... */
+#undef HAVE_WCHAR_H
+
+/* ... */
+#undef HAVE_ISWBLANK
+
+/* ... */
+#undef HAVE_STRCASESTR
+
+/* ... */
+#undef DATA_DIR
+
+/* ... */
+#undef PKGLIB_DIR
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/COPYING	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,344 @@
+Specific permission is granted for the GPLed code in this distribution
+to be linked to OpenSSL without invoking GPL clause 2(b).
+----------------------------------------------------------------------
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  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 Library 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 Library General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/Makefile.am	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,56 @@
+bin_PROGRAMS = mcabber
+mcabber_SOURCES = main.c main.h roster.c roster.h events.c events.h \
+		  commands.c commands.h compl.c compl.h \
+		  hbuf.c hbuf.h screen.c screen.h logprint.h \
+		  settings.c settings.h hooks.c hooks.h utf8.c utf8.h \
+		  histolog.c histolog.h utils.c utils.h pgp.c pgp.h \
+		  xmpp.c xmpp.h xmpp_helper.c xmpp_helper.h xmpp_defines.h \
+		  xmpp_iq.c xmpp_iq.h xmpp_iqrequest.c xmpp_iqrequest.h \
+		  xmpp_muc.c xmpp_muc.h xmpp_s10n.c xmpp_s10n.h \
+		  caps.c caps.h fifo.c fifo.h help.c help.h
+
+if OTR
+mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h
+endif
+
+LDADD = $(GLIB_LIBS) $(LOUDMOUTH_LIBS) $(GPGME_LIBS) $(LIBOTR_LIBS) \
+				$(ENCHANT_LIBS)
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(LOUDMOUTH_CFLAGS) \
+				$(GPGME_CFLAGS) $(LIBOTR_CFLAGS) \
+				$(ENCHANT_CFLAGS)
+
+CLEANFILES = hgcset.h
+
+if HGCSET
+BUILT_SOURCES = hgcset.h
+
+hgcset.h:
+	../hgcset.sh
+
+.PHONY: hgcset.h
+endif
+
+if INSTALL_HEADERS
+mcabberinclude_HEADERS = main.h roster.h events.h \
+			 commands.h compl.h \
+			 hbuf.h screen.h logprint.h \
+			 settings.h hooks.h utf8.c utf8.h \
+			 histolog.h utils.h pgp.h \
+			 xmpp.h xmpp_helper.h xmpp_defines.h \
+			 xmpp_iq.h xmpp_iqrequest.h \
+			 xmpp_muc.h xmpp_s10n.h \
+			 caps.h fifo.h help.h $(top_srcdir)/include/config.h
+
+if OTR
+mcabberinclude_HEADERS += otr.h nohtml.h
+endif
+
+if HGCSET
+mcabberinclude_HEADERS += hgcset.h
+endif
+
+mcabberincludedir = $(includedir)/mcabber
+endif
+
+#SUBDIRS =
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/caps.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,178 @@
+/*
+ * caps.c       -- Entity Capabilities Cache for mcabber
+ *
+ * Copyright (C) 2008 Frank Zschockelt <mcabber@freakysoft.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+
+typedef struct {
+  char *category;
+  char *name;
+  char *type;
+  GHashTable *features;
+} caps;
+
+static GHashTable *caps_cache = NULL;
+
+void caps_destroy(gpointer data)
+{
+  caps *c = data;
+  g_free(c->category);
+  g_free(c->name);
+  g_free(c->type);
+  g_hash_table_destroy(c->features);
+  g_free(c);
+}
+
+void caps_init(void)
+{
+  if (!caps_cache)
+    caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                       g_free, caps_destroy);
+}
+
+void caps_free(void)
+{
+  if (caps_cache) {
+    g_hash_table_destroy(caps_cache);
+    caps_cache = NULL;
+  }
+}
+
+void caps_add(char *hash)
+{
+  if (!hash)
+    return;
+  caps *c = g_new0(caps, 1);
+  c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+  g_hash_table_insert(caps_cache, g_strdup(hash), c);
+}
+
+int caps_has_hash(const char *hash)
+{
+  return (hash != NULL && (g_hash_table_lookup(caps_cache, hash) != NULL));
+}
+
+void caps_set_identity(char *hash,
+                       const char *category,
+                       const char *name,
+                       const char *type)
+{
+  caps *c;
+  if (!hash)
+    return;
+
+  c = g_hash_table_lookup(caps_cache, hash);
+  if (c) {
+    c->category = g_strdup(category);
+    c->name = g_strdup(name);
+    c->type = g_strdup(type);
+  }
+}
+
+void caps_add_feature(char *hash, const char *feature)
+{
+  caps *c;
+  if (!hash)
+    return;
+  c = g_hash_table_lookup(caps_cache, hash);
+  if (c) {
+    char *f = g_strdup(feature);
+    g_hash_table_replace(c->features, f, f);
+  }
+}
+
+int caps_has_feature(char *hash, char *feature)
+{
+  caps *c;
+  if (!hash)
+    return 0;
+  c = g_hash_table_lookup(caps_cache, hash);
+  if (c)
+    return (g_hash_table_lookup(c->features, feature) != NULL);
+  return 0;
+}
+
+static GFunc _foreach_function;
+
+void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data)
+{
+  // GFunc func = (GFunc)user_data;
+  _foreach_function(value, user_data);
+}
+
+void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data)
+{
+  caps *c;
+  if (!hash)
+    return;
+  c = g_hash_table_lookup(caps_cache, hash);
+  if (!c)
+    return;
+  _foreach_function = func;
+  g_hash_table_foreach(c->features, _caps_foreach_helper, user_data);
+}
+
+gint _strcmp_sort(gconstpointer a, gconstpointer b)
+{
+  return g_strcmp0(a, b);
+}
+
+//generates the sha1 hash for the special capability "" and returns it
+const char *caps_generate(void)
+{
+  char *identity;
+  GList *features;
+  GChecksum *sha1;
+  guint8 digest[20];
+  gsize digest_size = 20;
+  gchar *hash, *old_hash = NULL;
+  caps *old_caps;
+  caps *c = g_hash_table_lookup(caps_cache, "");
+
+  g_hash_table_steal(caps_cache, "");
+  sha1 = g_checksum_new(G_CHECKSUM_SHA1);
+  identity = g_strdup_printf("%s/%s//%s<", c->category, c->type, c->name);
+  g_checksum_update(sha1, (guchar*)identity, -1);
+  g_free(identity);
+
+  features = g_hash_table_get_values(c->features);
+  features = g_list_sort(features, _strcmp_sort);
+  {
+    GList *feature;
+    for (feature=features; feature; feature=feature->next) {
+      g_checksum_update(sha1, feature->data, -1);
+      g_checksum_update(sha1, (guchar *)"<", -1);
+    }
+  }
+  g_list_free(features);
+
+  g_checksum_get_digest(sha1, digest, &digest_size);
+  hash = g_base64_encode(digest, digest_size);
+  g_checksum_free(sha1);
+  g_hash_table_lookup_extended(caps_cache, hash,
+                               (gpointer *)&old_hash, (gpointer *)&old_caps);
+  g_hash_table_insert(caps_cache, hash, c);
+  if (old_hash)
+    return old_hash;
+  else
+    return hash;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/caps.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,22 @@
+#ifndef __MCABBER_CAPS_H__
+#define __MCABBER_CAPS_H__ 1
+
+#include <glib.h>
+
+void  caps_init(void);
+void  caps_free(void);
+void  caps_add(char *hash);
+int   caps_has_hash(const char *hash);
+void  caps_set_identity(char *hash,
+                        const char *category,
+                        const char *name,
+                        const char *type);
+void  caps_add_feature(char *hash, const char *feature);
+int   caps_has_feature(char *hash, char *feature);
+void  caps_foreach_feature(const char *hash, GFunc func, gpointer user_data);
+
+char *caps_generate(void);
+
+#endif /* __MCABBER_CAPS_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/commands.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,3828 @@
+/*
+ * commands.c   -- user commands handling
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "commands.h"
+#include "help.h"
+#include "roster.h"
+#include "screen.h"
+#include "compl.h"
+#include "hooks.h"
+#include "hbuf.h"
+#include "utils.h"
+#include "settings.h"
+#include "events.h"
+#include "otr.h"
+#include "utf8.h"
+#include "xmpp.h"
+#include "main.h"
+
+#define IMSTATUS_AWAY           "away"
+#define IMSTATUS_ONLINE         "online"
+#define IMSTATUS_OFFLINE        "offline"
+#define IMSTATUS_FREE4CHAT      "free"
+#define IMSTATUS_INVISIBLE      "invisible"
+#define IMSTATUS_AVAILABLE      "avail"
+#define IMSTATUS_NOTAVAILABLE   "notavail"
+#define IMSTATUS_DONOTDISTURB   "dnd"
+
+// Return value container for the following functions
+static int retval_for_cmds;
+
+// Commands callbacks
+static void do_roster(char *arg);
+static void do_status(char *arg);
+static void do_status_to(char *arg);
+static void do_add(char *arg);
+static void do_del(char *arg);
+static void do_group(char *arg);
+static void do_say(char *arg);
+static void do_msay(char *arg);
+static void do_say_to(char *arg);
+static void do_buffer(char *arg);
+static void do_clear(char *arg);
+static void do_info(char *arg);
+static void do_rename(char *arg);
+static void do_move(char *arg);
+static void do_set(char *arg);
+static void do_alias(char *arg);
+static void do_bind(char *arg);
+static void do_connect(char *arg);
+static void do_disconnect(char *arg);
+static void do_rawxml(char *arg);
+static void do_room(char *arg);
+static void do_authorization(char *arg);
+static void do_version(char *arg);
+static void do_request(char *arg);
+static void do_event(char *arg);
+static void do_help(char *arg);
+static void do_pgp(char *arg);
+static void do_iline(char *arg);
+static void do_screen_refresh(char *arg);
+static void do_chat_disable(char *arg);
+static void do_source(char *arg);
+static void do_color(char *arg);
+static void do_otr(char *arg);
+static void do_otrpolicy(char *arg);
+static void do_echo(char *arg);
+
+static void do_say_internal(char *arg, int parse_flags);
+
+// Global variable for the commands list
+static GSList *Commands;
+
+#ifdef MODULES_ENABLE
+#include <gmodule.h>
+
+static void do_load(char *arg);
+static void do_unload(char *arg);
+
+typedef struct {
+  char *name;
+  GModule *module;
+} loaded_module_t;
+
+GSList *loaded_modules = NULL;
+
+gpointer cmd_del(const char *name)
+{
+  GSList *sl_cmd;
+  for (sl_cmd = Commands; sl_cmd; sl_cmd = sl_cmd->next) {
+    cmd *command = (cmd *) sl_cmd->data;
+    if (!strcmp (command->name, name)) {
+      gpointer userdata = command->userdata;
+      Commands = g_slist_delete_link(Commands, sl_cmd);
+      compl_del_category_word(COMPL_CMD, command->name);
+      g_free(command);
+      return userdata;
+    }
+  }
+  return NULL;
+}
+
+//  cmd_add()
+// Adds a command to the commands list and to the CMD completion list
+void cmd_add(const char *name, const char *help, guint flags_row1,
+             guint flags_row2, void (*f)(char*), gpointer userdata)
+#define cmd_add(A, B, C, D, E) cmd_add (A, B, C, D, E, NULL);
+#else
+static void cmd_add(const char *name, const char *help,
+        guint flags_row1, guint flags_row2, void (*f)(char*))
+#endif
+{
+  cmd *n_cmd = g_new0(cmd, 1);
+  strncpy(n_cmd->name, name, 32-1);
+  n_cmd->help = help;
+  n_cmd->completion_flags[0] = flags_row1;
+  n_cmd->completion_flags[1] = flags_row2;
+  n_cmd->func = f;
+#ifdef MODULES_ENABLE
+  n_cmd->userdata = userdata;
+#endif
+  Commands = g_slist_prepend(Commands, n_cmd);
+  // Add to completion CMD category
+  compl_add_category_word(COMPL_CMD, name);
+}
+
+//  cmd_init()
+// Commands table initialization
+// !!!
+// After changing commands names and it arguments names here, you must change
+// ones in init_bindings()!
+//
+void cmd_init(void)
+{
+  cmd_add("add", "Add a jabber user", COMPL_JID, 0, &do_add);
+  cmd_add("alias", "Add an alias", 0, 0, &do_alias);
+  cmd_add("authorization", "Manage subscription authorizations",
+          COMPL_AUTH, COMPL_JID, &do_authorization);
+  cmd_add("bind", "Add an key binding", 0, 0, &do_bind);
+  cmd_add("buffer", "Manipulate current buddy's buffer (chat window)",
+          COMPL_BUFFER, 0, &do_buffer);
+  cmd_add("chat_disable", "Disable chat mode", 0, 0, &do_chat_disable);
+  cmd_add("clear", "Clear the dialog window", 0, 0, &do_clear);
+  cmd_add("color", "Set coloring options", COMPL_COLOR, 0, &do_color);
+  cmd_add("connect", "Connect to the server", 0, 0, &do_connect);
+  cmd_add("del", "Delete the current buddy", 0, 0, &do_del);
+  cmd_add("disconnect", "Disconnect from server", 0, 0, &do_disconnect);
+  cmd_add("echo", "Display a string in the log window", 0, 0, &do_echo);
+  cmd_add("event", "Process an event", COMPL_EVENTSID, COMPL_EVENTS, &do_event);
+  cmd_add("group", "Change group display settings",
+          COMPL_GROUP, COMPL_GROUPNAME, &do_group);
+  cmd_add("help", "Display some help", COMPL_CMD, 0, &do_help);
+  cmd_add("iline", "Manipulate input buffer", 0, 0, &do_iline);
+  cmd_add("info", "Show basic info on current buddy", 0, 0, &do_info);
+  cmd_add("move", "Move the current buddy to another group", COMPL_GROUPNAME,
+          0, &do_move);
+  cmd_add("msay", "Send a multi-lines message to the selected buddy",
+          COMPL_MULTILINE, 0, &do_msay);
+  cmd_add("otr", "Manage OTR settings", COMPL_OTR, COMPL_JID, &do_otr);
+  cmd_add("otrpolicy", "Manage OTR policies", COMPL_JID, COMPL_OTRPOLICY,
+          &do_otrpolicy);
+  cmd_add("pgp", "Manage PGP settings", COMPL_PGP, COMPL_JID, &do_pgp);
+  cmd_add("quit", "Exit the software", 0, 0, NULL);
+  cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml);
+  cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename);
+  cmd_add("request", "Send a Jabber IQ request", COMPL_REQUEST, COMPL_JID,
+          &do_request);
+  cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room);
+  cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0,
+          &do_roster);
+  cmd_add("say", "Say something to the selected buddy", 0, 0, &do_say);
+  cmd_add("say_to", "Say something to a specific buddy", COMPL_JID, 0,
+          &do_say_to);
+  cmd_add("screen_refresh", "Redraw mcabber screen", 0, 0, &do_screen_refresh);
+  //cmd_add("search");
+  cmd_add("set", "Set/query an option value", 0, 0, &do_set);
+  cmd_add("source", "Read a configuration file", 0, 0, &do_source);
+  cmd_add("status", "Show or set your status", COMPL_STATUS, 0, &do_status);
+  cmd_add("status_to", "Show or set your status for one recipient",
+          COMPL_JID, COMPL_STATUS, &do_status_to);
+  cmd_add("version", "Show mcabber version", 0, 0, &do_version);
+#ifdef MODULES_ENABLE
+  cmd_add("load", "Load module", 0, 0, &do_load);
+  cmd_add("unload", "Unload module", 0, 0, &do_unload);
+#endif
+
+  // Status category
+  compl_add_category_word(COMPL_STATUS, "online");
+  compl_add_category_word(COMPL_STATUS, "avail");
+  compl_add_category_word(COMPL_STATUS, "invisible");
+  compl_add_category_word(COMPL_STATUS, "free");
+  compl_add_category_word(COMPL_STATUS, "dnd");
+  compl_add_category_word(COMPL_STATUS, "notavail");
+  compl_add_category_word(COMPL_STATUS, "away");
+  compl_add_category_word(COMPL_STATUS, "offline");
+  compl_add_category_word(COMPL_STATUS, "message");
+
+  // Roster category
+  compl_add_category_word(COMPL_ROSTER, "bottom");
+  compl_add_category_word(COMPL_ROSTER, "top");
+  compl_add_category_word(COMPL_ROSTER, "up");
+  compl_add_category_word(COMPL_ROSTER, "down");
+  compl_add_category_word(COMPL_ROSTER, "group_prev");
+  compl_add_category_word(COMPL_ROSTER, "group_next");
+  compl_add_category_word(COMPL_ROSTER, "hide");
+  compl_add_category_word(COMPL_ROSTER, "show");
+  compl_add_category_word(COMPL_ROSTER, "toggle");
+  compl_add_category_word(COMPL_ROSTER, "display");
+  compl_add_category_word(COMPL_ROSTER, "hide_offline");
+  compl_add_category_word(COMPL_ROSTER, "show_offline");
+  compl_add_category_word(COMPL_ROSTER, "toggle_offline");
+  compl_add_category_word(COMPL_ROSTER, "item_lock");
+  compl_add_category_word(COMPL_ROSTER, "item_unlock");
+  compl_add_category_word(COMPL_ROSTER, "item_toggle_lock");
+  compl_add_category_word(COMPL_ROSTER, "alternate");
+  compl_add_category_word(COMPL_ROSTER, "search");
+  compl_add_category_word(COMPL_ROSTER, "unread_first");
+  compl_add_category_word(COMPL_ROSTER, "unread_next");
+  compl_add_category_word(COMPL_ROSTER, "note");
+
+  // Buffer category
+  compl_add_category_word(COMPL_BUFFER, "clear");
+  compl_add_category_word(COMPL_BUFFER, "bottom");
+  compl_add_category_word(COMPL_BUFFER, "top");
+  compl_add_category_word(COMPL_BUFFER, "up");
+  compl_add_category_word(COMPL_BUFFER, "down");
+  compl_add_category_word(COMPL_BUFFER, "search_backward");
+  compl_add_category_word(COMPL_BUFFER, "search_forward");
+  compl_add_category_word(COMPL_BUFFER, "date");
+  compl_add_category_word(COMPL_BUFFER, "%");
+  compl_add_category_word(COMPL_BUFFER, "purge");
+  compl_add_category_word(COMPL_BUFFER, "close");
+  compl_add_category_word(COMPL_BUFFER, "close_all");
+  compl_add_category_word(COMPL_BUFFER, "scroll_lock");
+  compl_add_category_word(COMPL_BUFFER, "scroll_unlock");
+  compl_add_category_word(COMPL_BUFFER, "scroll_toggle");
+  compl_add_category_word(COMPL_BUFFER, "list");
+  compl_add_category_word(COMPL_BUFFER, "save");
+
+  // Group category
+  compl_add_category_word(COMPL_GROUP, "fold");
+  compl_add_category_word(COMPL_GROUP, "unfold");
+  compl_add_category_word(COMPL_GROUP, "toggle");
+
+  // Multi-line (msay) category
+  compl_add_category_word(COMPL_MULTILINE, "abort");
+  compl_add_category_word(COMPL_MULTILINE, "begin");
+  compl_add_category_word(COMPL_MULTILINE, "send");
+  compl_add_category_word(COMPL_MULTILINE, "send_to");
+  compl_add_category_word(COMPL_MULTILINE, "toggle");
+  compl_add_category_word(COMPL_MULTILINE, "toggle_verbatim");
+  compl_add_category_word(COMPL_MULTILINE, "verbatim");
+
+  // Room category
+  compl_add_category_word(COMPL_ROOM, "affil");
+  compl_add_category_word(COMPL_ROOM, "ban");
+  compl_add_category_word(COMPL_ROOM, "bookmark");
+  compl_add_category_word(COMPL_ROOM, "destroy");
+  compl_add_category_word(COMPL_ROOM, "invite");
+  compl_add_category_word(COMPL_ROOM, "join");
+  compl_add_category_word(COMPL_ROOM, "kick");
+  compl_add_category_word(COMPL_ROOM, "leave");
+  compl_add_category_word(COMPL_ROOM, "names");
+  compl_add_category_word(COMPL_ROOM, "nick");
+  compl_add_category_word(COMPL_ROOM, "privmsg");
+  compl_add_category_word(COMPL_ROOM, "remove");
+  compl_add_category_word(COMPL_ROOM, "role");
+  compl_add_category_word(COMPL_ROOM, "setopt");
+  compl_add_category_word(COMPL_ROOM, "topic");
+  compl_add_category_word(COMPL_ROOM, "unban");
+  compl_add_category_word(COMPL_ROOM, "unlock");
+  compl_add_category_word(COMPL_ROOM, "whois");
+
+  // Authorization category
+  compl_add_category_word(COMPL_AUTH, "allow");
+  compl_add_category_word(COMPL_AUTH, "cancel");
+  compl_add_category_word(COMPL_AUTH, "request");
+  compl_add_category_word(COMPL_AUTH, "request_unsubscribe");
+
+  // Request (query) category
+  compl_add_category_word(COMPL_REQUEST, "last");
+  compl_add_category_word(COMPL_REQUEST, "time");
+  compl_add_category_word(COMPL_REQUEST, "vcard");
+  compl_add_category_word(COMPL_REQUEST, "version");
+
+  // Events category
+  compl_add_category_word(COMPL_EVENTS, "accept");
+  compl_add_category_word(COMPL_EVENTS, "ignore");
+  compl_add_category_word(COMPL_EVENTS, "reject");
+
+  // PGP category
+  compl_add_category_word(COMPL_PGP, "disable");
+  compl_add_category_word(COMPL_PGP, "enable");
+  compl_add_category_word(COMPL_PGP, "force");
+  compl_add_category_word(COMPL_PGP, "info");
+  compl_add_category_word(COMPL_PGP, "setkey");
+
+  // OTR category
+  compl_add_category_word(COMPL_OTR, "start");
+  compl_add_category_word(COMPL_OTR, "stop");
+  compl_add_category_word(COMPL_OTR, "fingerprint");
+  compl_add_category_word(COMPL_OTR, "smpq");
+  compl_add_category_word(COMPL_OTR, "smpr");
+  compl_add_category_word(COMPL_OTR, "smpa");
+  compl_add_category_word(COMPL_OTR, "info");
+  compl_add_category_word(COMPL_OTR, "key");
+
+  // OTR Policy category
+  compl_add_category_word(COMPL_OTRPOLICY, "plain");
+  compl_add_category_word(COMPL_OTRPOLICY, "manual");
+  compl_add_category_word(COMPL_OTRPOLICY, "opportunistic");
+  compl_add_category_word(COMPL_OTRPOLICY, "always");
+
+  // Color category
+  compl_add_category_word(COMPL_COLOR, "roster");
+  compl_add_category_word(COMPL_COLOR, "muc");
+  compl_add_category_word(COMPL_COLOR, "mucnick");
+}
+
+#ifdef MODULES_ENABLE
+void cmd_deinit ()
+{
+  GSList *el = loaded_modules;
+  while (el) {
+    loaded_module_t *module = el->data;
+    if (!g_module_close ((GModule *) module->module))
+      scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s",
+                    g_module_error ());
+    g_free (module->name);
+    g_free (module);
+    el = g_slist_next (el);
+  }
+  g_slist_free (loaded_modules);
+}
+#endif
+
+//  expandalias(line)
+// If there is one, expand the alias in line and returns a new allocated line
+// If no alias is found, returns line
+// Note: if the returned pointer is different from line, the caller should
+//       g_free() the pointer after use
+char *expandalias(const char *line)
+{
+  const char *p1, *p2;
+  char *word;
+  const gchar *value;
+  char *newline = (char*)line;
+
+  // Ignore leading COMMAND_CHAR
+  for (p1 = line ; *p1 == COMMAND_CHAR ; p1++)
+    ;
+  // Locate the end of the word
+  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
+    ;
+  // Extract the word and look for an alias in the list
+  word = g_strndup(p1, p2-p1);
+  value = settings_get(SETTINGS_TYPE_ALIAS, (const char*)word);
+  g_free(word);
+
+  if (value)
+    newline = g_strdup_printf("%c%s%s", COMMAND_CHAR, value, p2);
+
+  return newline;
+}
+
+//  cmd_get
+// Finds command in the command list structure.
+// Returns a pointer to the cmd entry, or NULL if command not found.
+cmd *cmd_get(const char *command)
+{
+  const char *p1, *p2;
+  char *com;
+  GSList *sl_com;
+
+  // Ignore leading COMMAND_CHAR
+  for (p1 = command ; *p1 == COMMAND_CHAR ; p1++)
+    ;
+  // Locate the end of the command
+  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
+    ;
+  // Copy the clean command
+  com = g_strndup(p1, p2-p1);
+
+  // Look for command in the list
+  for (sl_com=Commands; sl_com; sl_com = g_slist_next(sl_com)) {
+    if (!strcasecmp(com, ((cmd*)sl_com->data)->name))
+      break;
+  }
+  g_free(com);
+
+  if (sl_com)       // Command has been found.
+    return (cmd*)sl_com->data;
+  return NULL;
+}
+
+//  process_command(line, iscmd)
+// Process a command line.
+// If iscmd is TRUE, process the command even if verbatim mmode is set;
+// it is intended to be used for key bindings.
+// Return 255 if this is the /quit command, and 0 for the other commands.
+int process_command(const char *line, guint iscmd)
+{
+  char *p;
+  char *xpline;
+  cmd *curcmd;
+
+  if (!line)
+    return 0;
+
+  // We do alias expansion here
+  if (iscmd || scr_get_multimode() != 2)
+    xpline = expandalias(line);
+  else
+    xpline = (char*)line; // No expansion in verbatim multi-line mode
+
+  // We want to use a copy
+  if (xpline == line)
+    xpline = g_strdup(line);
+
+  // Remove trailing spaces:
+  for (p=xpline ; *p ; p++)
+    ;
+  for (p-- ; p>xpline && (*p == ' ') ; p--)
+    *p = 0;
+
+  // Command "quit"?
+  if ((iscmd || scr_get_multimode() != 2)
+      && (!strncasecmp(xpline, mkcmdstr("quit"), strlen(mkcmdstr("quit"))))) {
+    if (!xpline[5] || xpline[5] == ' ') {
+      g_free(xpline);
+      return 255;
+    }
+  } else if (iscmd && !strncasecmp(xpline, "quit", 4) &&
+             (!xpline[4] || xpline[4] == ' ')) {
+    // If iscmd is true we can have the command without the command prefix
+    // character (usually '/').
+    g_free(xpline);
+    return 255;
+  }
+
+  // If verbatim multi-line mode, we check if another /msay command is typed
+  if (!iscmd && scr_get_multimode() == 2
+      && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
+    // It isn't an /msay command
+    scr_append_multiline(xpline);
+    g_free(xpline);
+    return 0;
+  }
+
+  // Commands handling
+  curcmd = cmd_get(xpline);
+
+  if (!curcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized command.  "
+                 "Please see the manual for a list of known commands.");
+    g_free(xpline);
+    return 0;
+  }
+  if (!curcmd->func) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "This functionality is not yet implemented, sorry.");
+    g_free(xpline);
+    return 0;
+  }
+  // Lets go to the command parameters
+  for (p = xpline+1; *p && (*p != ' ') ; p++)
+    ;
+  // Skip spaces
+  while (*p && (*p == ' '))
+    p++;
+  // Call command-specific function
+  retval_for_cmds = 0;
+#ifdef MODULES_ENABLE
+  if (curcmd->userdata)
+    (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata);
+  else
+    (*curcmd->func)(p);
+#else
+  (*curcmd->func)(p);
+#endif
+  g_free(xpline);
+  return retval_for_cmds;
+}
+
+//  process_line(line)
+// Process a command/message line.
+// If this isn't a command, this is a message and it is sent to the
+// currently selected buddy.
+// Return 255 if the line is the /quit command, or 0.
+int process_line(const char *line)
+{
+  if (!*line) { // User only pressed enter
+    if (scr_get_multimode()) {
+      scr_append_multiline("");
+      return 0;
+    }
+    if (current_buddy) {
+      if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP)
+	do_group("toggle");
+      else {
+        // Enter chat mode
+        scr_set_chatmode(TRUE);
+        scr_ShowBuddyWindow();
+      }
+    }
+    return 0;
+  }
+
+  if (*line != COMMAND_CHAR) {
+    // This isn't a command
+    if (scr_get_multimode())
+      scr_append_multiline(line);
+    else
+      do_say_internal((char*)line, 0);
+    return 0;
+  }
+
+  /* It is _probably_ a command -- except for verbatim multi-line mode */
+  return process_command(line, FALSE);
+}
+
+// Helper routine for buffer item_{lock,unlock,toggle_lock}
+// "lock" values: 1=lock 0=unlock -1=invert
+static void roster_buddylock(char *bjid, int lock)
+{
+  gpointer bud = NULL;
+  bool may_need_refresh = FALSE;
+
+  // Allow special jid "" or "." (current buddy)
+  if (bjid && (!*bjid || !strcmp(bjid, ".")))
+    bjid = NULL;
+
+  if (bjid) {
+    // The JID has been specified.  Quick check...
+    if (check_jid_syntax(bjid)) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "<%s> is not a valid Jabber ID.", bjid);
+    } else {
+      // Find the buddy
+      GSList *roster_elt;
+      roster_elt = roster_find(bjid, jidsearch,
+                               ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
+      if (roster_elt)
+        bud = roster_elt->data;
+      else
+        scr_LogPrint(LPRINT_NORMAL, "This jid isn't in the roster.");
+      may_need_refresh = TRUE;
+    }
+  } else {
+    // Use the current buddy
+    if (current_buddy)
+      bud = BUDDATA(current_buddy);
+  }
+
+  // Update the ROSTER_FLAG_USRLOCK flag
+  if (bud) {
+    if (lock == -1)
+      lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
+    buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
+    if (may_need_refresh) {
+      buddylist_build();
+      update_roster = TRUE;
+    }
+  }
+}
+
+//  display_and_free_note(note, winId)
+// Display the note information in the winId buffer, and free note
+// (winId is a bare jid or NULL for the status window, in which case we
+// display the note jid too)
+static void display_and_free_note(struct annotation *note, const char *winId)
+{
+  gchar tbuf[128];
+  GString *sbuf;
+  guint msg_flag = HBB_PREFIX_INFO;
+  /* We use the flag prefix_info for the first line, and prefix_cont
+     for the other lines, for better readability */
+
+  if (!note)
+    return;
+
+  sbuf = g_string_new("");
+
+  if (!winId) {
+    // We're writing to the status window, so let's show the jid too.
+    g_string_printf(sbuf, "Annotation on <%s>", note->jid);
+    scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
+    msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT;
+  }
+
+  // If we have the creation date, display it
+  if (note->cdate) {
+    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S",
+             localtime(&note->cdate));
+    g_string_printf(sbuf, "Note created  %s", tbuf);
+    scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
+    msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT;
+  }
+  // If we have the modification date, display it
+  // unless it's the same as the creation date
+  if (note->mdate && note->mdate != note->cdate) {
+    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S",
+             localtime(&note->mdate));
+    g_string_printf(sbuf, "Note modified %s", tbuf);
+    scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
+    msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT;
+  }
+  // Note text
+  g_string_printf(sbuf, "Note: %s", note->text);
+  scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
+
+  g_string_free(sbuf, TRUE);
+  g_free(note->text);
+  g_free(note->jid);
+  g_free(note);
+}
+
+static void display_all_annotations(void)
+{
+  GSList *notes;
+  notes = xmpp_get_all_storage_rosternotes();
+
+  if (!notes)
+    return;
+
+  // Call display_and_free_note() for each note,
+  // with winId = NULL (special window)
+  g_slist_foreach(notes, (GFunc)&display_and_free_note, NULL);
+  scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
+  update_roster = TRUE;
+  g_slist_free(notes);
+}
+
+static void roster_note(char *arg)
+{
+  const char *bjid;
+  guint type;
+
+  if (!current_buddy)
+    return;
+
+  bjid = buddy_getjid(BUDDATA(current_buddy));
+  type = buddy_gettype(BUDDATA(current_buddy));
+
+  if (!bjid && type == ROSTER_TYPE_SPECIAL && !arg) {
+    // We're in the status window (the only special buffer currently)
+    // Let's display all server notes
+    display_all_annotations();
+    return;
+  }
+
+  if (!bjid || (type != ROSTER_TYPE_USER &&
+               type != ROSTER_TYPE_ROOM &&
+               type != ROSTER_TYPE_AGENT)) {
+    scr_LogPrint(LPRINT_NORMAL, "This item can't have a note.");
+    return;
+  }
+
+  if (arg && *arg) {  // Set a note
+    gchar *msg, *notetxt;
+    msg = to_utf8(arg);
+    if (!strcmp(msg, "-"))
+      notetxt = NULL; // delete note
+    else
+      notetxt = msg;
+    xmpp_set_storage_rosternotes(bjid, notetxt);
+    g_free(msg);
+  } else {      // Display a note
+    struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
+    if (note) {
+      display_and_free_note(note, bjid);
+    } else {
+      scr_WriteIncomingMessage(bjid, "This item doesn't have a note.", 0,
+                               HBB_PREFIX_INFO, 0);
+    }
+  }
+}
+
+//  roster_updown(updown, nitems)
+// updown: -1=up, +1=down
+inline static void roster_updown(int updown, char *nitems)
+{
+  int nbitems;
+
+  if (!nitems || !*nitems)
+    nbitems = 1;
+  else
+    nbitems = strtol(nitems, NULL, 10);
+
+  if (nbitems > 0)
+    scr_RosterUpDown(updown, nbitems);
+}
+
+/* Commands callback functions */
+/* All these do_*() functions will be called with a "arg" parameter */
+/* (with arg not null)                                              */
+
+static void do_roster(char *arg)
+{
+  char **paramlst;
+  char *subcmd;
+
+  paramlst = split_arg(arg, 2, 1); // subcmd, arg
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(subcmd, "top")) {
+    scr_RosterTop();
+    update_roster = TRUE;
+  } else if (!strcasecmp(subcmd, "bottom")) {
+    scr_RosterBottom();
+    update_roster = TRUE;
+  } else if (!strcasecmp(subcmd, "hide")) {
+    scr_RosterVisibility(0);
+  } else if (!strcasecmp(subcmd, "show")) {
+    scr_RosterVisibility(1);
+  } else if (!strcasecmp(subcmd, "toggle")) {
+    scr_RosterVisibility(-1);
+  } else if (!strcasecmp(subcmd, "hide_offline")) {
+    buddylist_set_hide_offline_buddies(TRUE);
+    if (current_buddy)
+      buddylist_build();
+    update_roster = TRUE;
+  } else if (!strcasecmp(subcmd, "show_offline")) {
+    buddylist_set_hide_offline_buddies(FALSE);
+    buddylist_build();
+    update_roster = TRUE;
+  } else if (!strcasecmp(subcmd, "toggle_offline")) {
+    buddylist_set_hide_offline_buddies(-1);
+    buddylist_build();
+    update_roster = TRUE;
+  } else if (!strcasecmp(subcmd, "display")) {
+    scr_RosterDisplay(arg);
+  } else if (!strcasecmp(subcmd, "item_lock")) {
+    roster_buddylock(arg, 1);
+  } else if (!strcasecmp(subcmd, "item_unlock")) {
+    roster_buddylock(arg, 0);
+  } else if (!strcasecmp(subcmd, "item_toggle_lock")) {
+    roster_buddylock(arg, -1);
+  } else if (!strcasecmp(subcmd, "unread_first")) {
+    scr_RosterUnreadMessage(0);
+  } else if (!strcasecmp(subcmd, "unread_next")) {
+    scr_RosterUnreadMessage(1);
+  } else if (!strcasecmp(subcmd, "alternate")) {
+    scr_RosterJumpAlternate();
+  } else if (!strncasecmp(subcmd, "search", 6)) {
+    strip_arg_special_chars(arg);
+    if (!arg || !*arg) {
+      scr_LogPrint(LPRINT_NORMAL, "What name or JID are you looking for?");
+      free_arg_lst(paramlst);
+      return;
+    }
+    scr_RosterSearch(arg);
+    update_roster = TRUE;
+  } else if (!strcasecmp(subcmd, "up")) {
+    roster_updown(-1, arg);
+  } else if (!strcasecmp(subcmd, "down")) {
+    roster_updown(1, arg);
+  } else if (!strcasecmp(subcmd, "group_prev")) {
+    scr_RosterPrevGroup();
+  } else if (!strcasecmp(subcmd, "group_next")) {
+    scr_RosterNextGroup();
+  } else if (!strcasecmp(subcmd, "note")) {
+    roster_note(arg);
+  } else
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+  free_arg_lst(paramlst);
+}
+
+void do_color(char *arg)
+{
+  char **paramlst;
+  char *subcmd;
+
+  paramlst = split_arg(arg, 2, 1); // subcmd, arg
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(subcmd, "roster")) {
+    char *status, *wildcard, *color;
+    char **arglist = split_arg(arg, 3, 0);
+
+    status = *arglist;
+    wildcard = to_utf8(arglist[1]);
+    color = arglist[2];
+
+    if (status && !strcmp(status, "clear")) { // Not a color command, clear all
+      scr_RosterClearColor();
+      update_roster = TRUE;
+    } else {
+      if (!status || !*status || !wildcard || !*wildcard || !color || !*color) {
+        scr_LogPrint(LPRINT_NORMAL, "Missing argument");
+      } else {
+        update_roster = scr_RosterColor(status, wildcard, color)
+                        || update_roster;
+      }
+    }
+    free_arg_lst(arglist);
+    g_free(wildcard);
+  } else if (!strcasecmp(subcmd, "muc")) {
+    char **arglist = split_arg(arg, 2, 0);
+    char *free_muc = to_utf8(*arglist);
+    const char *muc = free_muc, *mode = arglist[1];
+    if (!muc || !*muc)
+      scr_LogPrint(LPRINT_NORMAL, "What MUC?");
+    else {
+      if (!strcmp(muc, "."))
+        if (!(muc = CURRENT_JID))
+          scr_LogPrint(LPRINT_NORMAL, "No JID selected");
+      if (muc) {
+        if (check_jid_syntax(muc) && strcmp(muc, "*"))
+          scr_LogPrint(LPRINT_NORMAL, "Not a JID");
+        else {
+          if (!mode || !*mode || !strcasecmp(mode, "on"))
+            scr_MucColor(muc, MC_ALL);
+          else if (!strcasecmp(mode, "preset"))
+            scr_MucColor(muc, MC_PRESET);
+          else if (!strcasecmp(mode, "off"))
+            scr_MucColor(muc, MC_OFF);
+          else if (!strcmp(mode, "-"))
+            scr_MucColor(muc, MC_REMOVE);
+          else
+            scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
+        }
+      }
+    }
+    free_arg_lst(arglist);
+    g_free(free_muc);
+  } else if (!strcasecmp(subcmd, "mucnick")) {
+    char **arglist = split_arg(arg, 2, 0);
+    const char *nick = *arglist, *color = arglist[1];
+    if (!nick || !*nick || !color || !*color)
+      scr_LogPrint(LPRINT_NORMAL, "Missing argument");
+    else
+      scr_MucNickColor(nick, color);
+    free_arg_lst(arglist);
+  } else
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+  free_arg_lst(paramlst);
+}
+
+//  cmd_setstatus(recipient, arg)
+// Set your Jabber status.
+// - if recipient is not NULL, the status is sent to this contact only
+// - arg must be "status message" (message is optional)
+void cmd_setstatus(const char *recipient, const char *arg)
+{
+  char **paramlst;
+  char *status;
+  char *msg;
+  enum imstatus st;
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    return;
+  }
+
+  // It makes sense to reset autoaway before changing the status
+  // (esp. for FIFO or remote commands) or the behaviour could be
+  // unexpected...
+  if (!recipient)
+    scr_CheckAutoAway(TRUE);
+
+  paramlst = split_arg(arg, 2, 1); // status, message
+  status = *paramlst;
+  msg = *(paramlst+1);
+
+  if (!status) {
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if      (!strcasecmp(status, IMSTATUS_OFFLINE))       st = offline;
+  else if (!strcasecmp(status, IMSTATUS_ONLINE))        st = available;
+  else if (!strcasecmp(status, IMSTATUS_AVAILABLE))     st = available;
+  else if (!strcasecmp(status, IMSTATUS_AWAY))          st = away;
+  else if (!strcasecmp(status, IMSTATUS_INVISIBLE))     st = invisible;
+  else if (!strcasecmp(status, IMSTATUS_DONOTDISTURB))  st = dontdisturb;
+  else if (!strcasecmp(status, IMSTATUS_NOTAVAILABLE))  st = notavail;
+  else if (!strcasecmp(status, IMSTATUS_FREE4CHAT))     st = freeforchat;
+  else if (!strcasecmp(status, "message")) {
+    if (!msg || !*msg) {
+      // We want a message.  If there's none, we give up.
+      scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+      free_arg_lst(paramlst);
+      return;
+    }
+    st = xmpp_getstatus();  // Preserve current status
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized status!");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  // Use provided message
+  if (msg && !*msg) {
+    msg = NULL;
+  }
+
+  // If a recipient is specified, let's don't use default status messages
+  if (recipient && !msg)
+    msg = "";
+
+  xmpp_setstatus(st, recipient, msg, FALSE);
+
+  free_arg_lst(paramlst);
+}
+
+static void do_status(char *arg)
+{
+  if (!*arg) {
+    const char *sm = xmpp_getstatusmsg();
+    scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s",
+                 imstatus2char[xmpp_getstatus()],
+                 (sm ? sm : ""));
+    return;
+  }
+  arg = to_utf8(arg);
+  cmd_setstatus(NULL, arg);
+  g_free(arg);
+}
+
+static void do_status_to(char *arg)
+{
+  char **paramlst;
+  char *fjid, *st, *msg;
+  char *jid_utf8 = NULL;
+
+  paramlst = split_arg(arg, 3, 1); // jid, status, [message]
+  fjid = *paramlst;
+  st = *(paramlst+1);
+  msg = *(paramlst+2);
+
+  if (!fjid || !st) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Please specify both a Jabber ID and a status.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  // Allow things like /status_to "" away
+  if (!*fjid || !strcmp(fjid, "."))
+    fjid = NULL;
+
+  if (fjid) {
+    // The JID has been specified.  Quick check...
+    if (check_jid_syntax(fjid)) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "<%s> is not a valid Jabber ID.", fjid);
+      fjid = NULL;
+    } else {
+      // Convert jid to lowercase
+      char *p = fjid;
+      for ( ; *p && *p != JID_RESOURCE_SEPARATOR; p++)
+        *p = tolower(*p);
+      fjid = jid_utf8 = to_utf8(fjid);
+    }
+  } else {
+    // Add the current buddy
+    if (current_buddy)
+      fjid = (char*)buddy_getjid(BUDDATA(current_buddy));
+    if (!fjid)
+      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+  }
+
+  if (fjid) {
+    char *cmdline;
+    if (!msg)
+      msg = "";
+    msg = to_utf8(msg);
+    cmdline = g_strdup_printf("%s %s", st, msg);
+    scr_LogPrint(LPRINT_LOGNORM, "Sending to <%s> /status %s", fjid, cmdline);
+    cmd_setstatus(fjid, cmdline);
+    g_free(msg);
+    g_free(cmdline);
+    g_free(jid_utf8);
+  }
+  free_arg_lst(paramlst);
+}
+
+static void do_add(char *arg)
+{
+  char **paramlst;
+  char *id, *nick;
+  char *jid_utf8 = NULL;
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    return;
+  }
+
+  paramlst = split_arg(arg, 2, 0); // jid, [nickname]
+  id = *paramlst;
+  nick = *(paramlst+1);
+
+  if (!id)
+    nick = NULL; // Allow things like: /add "" nick
+  else if (!*id || !strcmp(id, "."))
+    id = NULL;
+
+  if (id) {
+    // The JID has been specified.  Quick check...
+    if (check_jid_syntax(id)) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "<%s> is not a valid Jabber ID.", id);
+      id = NULL;
+    } else {
+      mc_strtolower(id);
+      // Actually an UTF-8 id isn't needed because only the bare jid will
+      // be used.
+      id = jid_utf8 = to_utf8(id);
+    }
+  } else {
+    // Add the current buddy
+    if (current_buddy)
+      id = (char*)buddy_getjid(BUDDATA(current_buddy));
+    if (!id)
+      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+  }
+
+  if (nick)
+    nick = to_utf8(nick);
+
+  if (id) {
+    // 2nd parameter = optional nickname
+    xmpp_addbuddy(id, nick, NULL);
+    scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.",
+                 id);
+  }
+
+  g_free(jid_utf8);
+  g_free(nick);
+  free_arg_lst(paramlst);
+}
+
+static void do_del(char *arg)
+{
+  const char *bjid;
+
+  if (*arg) {
+    scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; "
+                 "the currently-selected buddy will be deleted.");
+    return;
+  }
+
+  if (!current_buddy)
+    return;
+  bjid = buddy_getjid(BUDDATA(current_buddy));
+  if (!bjid)
+    return;
+
+  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM) {
+    // This is a chatroom
+    if (buddy_getinsideroom(BUDDATA(current_buddy))) {
+      scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!");
+      return;
+    }
+  }
+
+  // Close the buffer
+  scr_BufferPurge(1, NULL);
+
+  scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", bjid);
+  xmpp_delbuddy(bjid);
+  scr_UpdateBuddyWindow();
+}
+
+static void do_group(char *arg)
+{
+  gpointer group = NULL;
+  guint leave_buddywindow;
+  char **paramlst;
+  char *subcmd;
+  enum { group_toggle = -1, group_unfold = 0, group_fold = 1 } group_state = 0;
+
+  if (!*arg) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    return;
+  }
+
+  if (!current_buddy)
+    return;
+
+  paramlst = split_arg(arg, 2, 0); // subcmd, [arg]
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd)
+    goto do_group_return;   // Should not happen anyway
+
+  if (arg && *arg) {
+    GSList *roster_elt;
+    char *group_utf8 = to_utf8(arg);
+    roster_elt = roster_find(group_utf8, namesearch, ROSTER_TYPE_GROUP);
+    g_free(group_utf8);
+    if (roster_elt)
+      group = buddy_getgroup(roster_elt->data);
+  } else {
+    group = buddy_getgroup(BUDDATA(current_buddy));
+  }
+  if (!group)
+    goto do_group_return;
+
+  // We'll have to redraw the chat window if we're not currently on the group
+  // entry itself, because it means we'll have to leave the current buddy
+  // chat window.
+  leave_buddywindow = (group != BUDDATA(current_buddy) &&
+                       group == buddy_getgroup(BUDDATA(current_buddy)));
+
+
+  if (!(buddy_gettype(group) & ROSTER_TYPE_GROUP)) {
+    scr_LogPrint(LPRINT_NORMAL, "You need to select a group.");
+    goto do_group_return;
+  }
+
+  if (!strcasecmp(subcmd, "expand") || !strcasecmp(subcmd, "unfold"))
+    group_state = group_unfold;
+  else if (!strcasecmp(subcmd, "shrink") || !strcasecmp(subcmd, "fold"))
+    group_state = group_fold;
+  else if (!strcasecmp(subcmd, "toggle"))
+    group_state = group_toggle;
+  else {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+    goto do_group_return;
+  }
+
+  if (group_state != group_unfold && leave_buddywindow)
+    scr_RosterPrevGroup();
+
+  buddy_hide_group(group, group_state);
+
+  buddylist_build();
+  update_roster = TRUE;
+
+do_group_return:
+  free_arg_lst(paramlst);
+}
+
+static int send_message_to(const char *fjid, const char *msg, const char *subj,
+                           LmMessageSubType type_overwrite, bool quiet)
+{
+  char *bare_jid, *rp;
+  char *hmsg;
+  gint crypted;
+  gint retval = 0;
+  int isroom;
+  gpointer xep184 = NULL;
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    return 1;
+  }
+  if (!fjid || !*fjid) {
+    scr_LogPrint(LPRINT_NORMAL, "You must specify a Jabber ID.");
+    return 1;
+  }
+  if (!msg || !*msg) {
+    scr_LogPrint(LPRINT_NORMAL, "You must specify a message.");
+    return 1;
+  }
+  if (check_jid_syntax((char*)fjid)) {
+    scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                 "<%s> is not a valid Jabber ID.", fjid);
+    return 1;
+  }
+
+  // We must use the bare jid in hk_message_out()
+  rp = strchr(fjid, JID_RESOURCE_SEPARATOR);
+  if (rp)
+    bare_jid = g_strndup(fjid, rp - fjid);
+  else
+    bare_jid = (char*)fjid;
+
+  if (!quiet) {
+    // Jump to window, create one if needed
+    scr_RosterJumpJid(bare_jid);
+  }
+
+  // Check if we're sending a message to a conference room
+  // If not, we must make sure rp is NULL, for hk_message_out()
+  isroom = !!roster_find(bare_jid, jidsearch, ROSTER_TYPE_ROOM);
+  if (rp) {
+    if (isroom) rp++;
+    else rp = NULL;
+  }
+  isroom = isroom && (!rp || !*rp);
+
+  // local part (UI, logging, etc.)
+  if (subj)
+    hmsg = g_strdup_printf("[%s]\n%s", subj, msg);
+  else
+    hmsg = (char*)msg;
+
+  // Network part
+  xmpp_send_msg(fjid, msg, (isroom ? ROSTER_TYPE_ROOM : ROSTER_TYPE_USER),
+                subj, FALSE, &crypted, type_overwrite, &xep184);
+
+  if (crypted == -1) {
+    scr_LogPrint(LPRINT_LOGNORM, "Encryption error.  Message was not sent.");
+    retval = 1;
+    goto send_message_to_return;
+  }
+
+  // Hook
+  if (!isroom)
+    hk_message_out(bare_jid, rp, 0, hmsg, crypted, xep184);
+
+send_message_to_return:
+  if (hmsg != msg) g_free(hmsg);
+  if (rp) g_free(bare_jid);
+  return retval;
+}
+
+//  send_message(msg, subj, type_overwrite)
+// Write the message in the buddy's window and send the message on
+// the network.
+static void send_message(const char *msg, const char *subj,
+                         LmMessageSubType type_overwrite)
+{
+  const char *bjid;
+
+  if (!current_buddy) {
+    scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected.");
+    return;
+  }
+
+  bjid = CURRENT_JID;
+  if (!bjid) {
+    scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected.");
+    return;
+  }
+
+  send_message_to(bjid, msg, subj, type_overwrite, FALSE);
+}
+
+static LmMessageSubType scan_mtype(char **arg)
+{
+  //Try splitting it
+  char **parlist = split_arg(*arg, 2, 1);
+  LmMessageSubType result = LM_MESSAGE_SUB_TYPE_NOT_SET;
+  //Is it any good parameter?
+  if (parlist && *parlist) {
+    if (!strcmp("-n", *parlist)) {
+      result = LM_MESSAGE_SUB_TYPE_NORMAL;
+    } else if (!strcmp("-h", *parlist)) {
+      result = LM_MESSAGE_SUB_TYPE_HEADLINE;
+    }
+    if (result != LM_MESSAGE_SUB_TYPE_NOT_SET || (!strcmp("--", *parlist)))
+      *arg += strlen(*arg) - (parlist[1] ? strlen(parlist[1]) : 0);
+  }
+  //Anything found? -> skip it
+  free_arg_lst(parlist);
+  return result;
+}
+
+static void do_say_internal(char *arg, int parse_flags)
+{
+  gpointer bud;
+  LmMessageSubType msgtype = LM_MESSAGE_SUB_TYPE_NOT_SET;
+
+  scr_set_chatmode(TRUE);
+  scr_ShowBuddyWindow();
+
+  if (!current_buddy) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Whom are you talking to?  Please select a buddy.");
+    return;
+  }
+
+  bud = BUDDATA(current_buddy);
+  if (!(buddy_gettype(bud) &
+        (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) {
+    scr_LogPrint(LPRINT_NORMAL, "This is not a user.");
+    return;
+  }
+
+  buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
+  if (parse_flags)
+    msgtype = scan_mtype(&arg);
+  arg = to_utf8(arg);
+  send_message(arg, NULL, msgtype);
+  g_free(arg);
+}
+
+static void do_say(char *arg) {
+  do_say_internal(arg, 1);
+}
+
+static void do_msay(char *arg)
+{
+  /* Parameters: begin verbatim abort send send_to */
+  char **paramlst;
+  char *subcmd;
+
+  paramlst = split_arg(arg, 2, 1); // subcmd, arg
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    scr_LogPrint(LPRINT_NORMAL, "Please read the manual before using "
+                 "the /msay command.");
+    scr_LogPrint(LPRINT_NORMAL, "(Use \"%s begin\" to enter "
+                 "multi-line mode...)", mkcmdstr("msay"));
+    goto do_msay_return;
+  }
+
+  if (!strcasecmp(subcmd, "toggle")) {
+    if (scr_get_multimode())
+      subcmd = "send";
+    else
+      subcmd = "begin";
+  } else if (!strcasecmp(subcmd, "toggle_verbatim")) {
+    if (scr_get_multimode())
+      subcmd = "send";
+    else
+      subcmd = "verbatim";
+  }
+
+  if (!strcasecmp(subcmd, "abort")) {
+    if (scr_get_multimode())
+      scr_LogPrint(LPRINT_NORMAL, "Leaving multi-line message mode.");
+    scr_set_multimode(FALSE, NULL);
+    goto do_msay_return;
+  } else if ((!strcasecmp(subcmd, "begin")) ||
+             (!strcasecmp(subcmd, "verbatim"))) {
+    bool verbat;
+    gchar *subj_utf8 = to_utf8(arg);
+    if (!strcasecmp(subcmd, "verbatim")) {
+      scr_set_multimode(2, subj_utf8);
+      verbat = TRUE;
+    } else {
+      scr_set_multimode(1, subj_utf8);
+      verbat = FALSE;
+    }
+
+    scr_LogPrint(LPRINT_NORMAL, "Entered %smulti-line message mode.",
+                 verbat ? "VERBATIM " : "");
+    scr_LogPrint(LPRINT_NORMAL, "Select a buddy and use \"%s send\" "
+                 "when your message is ready.", mkcmdstr("msay"));
+    if (verbat)
+      scr_LogPrint(LPRINT_NORMAL, "Use \"%s abort\" to abort this mode.",
+                   mkcmdstr("msay"));
+    g_free(subj_utf8);
+    goto do_msay_return;
+  } else if (strcasecmp(subcmd, "send") && strcasecmp(subcmd, "send_to")) {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+    goto do_msay_return;
+  }
+
+  /* send/send_to command */
+
+  if (!scr_get_multimode()) {
+    scr_LogPrint(LPRINT_NORMAL, "No message to send.  "
+                 "Use \"%s begin\" first.", mkcmdstr("msay"));
+    goto do_msay_return;
+  }
+
+  scr_set_chatmode(TRUE);
+  scr_ShowBuddyWindow();
+
+  if (!strcasecmp(subcmd, "send_to")) {
+    int err = FALSE;
+    gchar *msg_utf8;
+    LmMessageSubType msg_type = scan_mtype(&arg);
+    // Let's send to the specified JID.  We leave now if there
+    // has been an error (so we don't leave multi-line mode).
+    arg = to_utf8(arg);
+    msg_utf8 = to_utf8(scr_get_multiline());
+    if (msg_utf8) {
+      err = send_message_to(arg, msg_utf8, scr_get_multimode_subj(), msg_type,
+                            FALSE);
+      g_free(msg_utf8);
+    }
+    g_free(arg);
+    if (err)
+      goto do_msay_return;
+  } else { // Send to currently selected buddy
+    gpointer bud;
+    gchar *msg_utf8;
+
+    if (!current_buddy) {
+      scr_LogPrint(LPRINT_NORMAL, "Whom are you talking to?");
+      goto do_msay_return;
+    }
+
+    bud = BUDDATA(current_buddy);
+    if (!(buddy_gettype(bud) &
+          (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) {
+      scr_LogPrint(LPRINT_NORMAL, "This is not a user.");
+      goto do_msay_return;
+    }
+
+    buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
+    msg_utf8 = to_utf8(scr_get_multiline());
+    if (msg_utf8) {
+      send_message(msg_utf8, scr_get_multimode_subj(), scan_mtype(&arg));
+      g_free(msg_utf8);
+    }
+  }
+  scr_set_multimode(FALSE, NULL);
+  scr_LogPrint(LPRINT_NORMAL, "You have left multi-line message mode.");
+do_msay_return:
+  free_arg_lst(paramlst);
+}
+
+//  load_message_from_file(filename)
+// Read the whole content of a file.
+// The data are converted to UTF8, they should be freed by the caller after
+// use.
+char *load_message_from_file(const char *filename)
+{
+  FILE *fd;
+  struct stat buf;
+  char *msgbuf, *msgbuf_utf8;
+  char *p;
+  char *next_utf8_char;
+  size_t len;
+
+  fd = fopen(filename, "r");
+
+  if (!fd || fstat(fileno(fd), &buf)) {
+    scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename);
+    return NULL;
+  }
+  if (!buf.st_size || buf.st_size >= HBB_BLOCKSIZE) {
+    if (!buf.st_size)
+      scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename);
+    else
+      scr_LogPrint(LPRINT_LOGNORM, "Message file is too big (%s)", filename);
+    fclose(fd);
+    return NULL;
+  }
+
+  msgbuf = g_new0(char, HBB_BLOCKSIZE);
+  len = fread(msgbuf, 1, HBB_BLOCKSIZE-1, fd);
+  fclose(fd);
+
+  next_utf8_char = msgbuf;
+
+  // Check there is no binary data.  It must be a *message* file!
+  for (p = msgbuf ; *p ; p++) {
+    if (utf8_mode) {
+      if (p == next_utf8_char) {
+        if (!iswprint(get_char(p)) && *p != '\n' && *p != '\t')
+          break;
+        next_utf8_char = next_char(p);
+      }
+    } else {
+      unsigned char sc = *p;
+      if (!iswprint(sc) && sc != '\n' && sc != '\t')
+        break;
+    }
+  }
+
+  if (*p || (size_t)(p-msgbuf) != len) { // We're not at the End Of Line...
+    scr_LogPrint(LPRINT_LOGNORM, "Message file contains "
+                 "invalid characters (%s)", filename);
+    g_free(msgbuf);
+    return NULL;
+  }
+
+  // p is now at the EOL
+  // Let's strip trailing newlines
+  if (p > msgbuf)
+    p--;
+  while (p > msgbuf && *p == '\n')
+    *p-- = 0;
+
+  // It could be empty, once the trailing newlines are gone
+  if (p == msgbuf && *p == '\n') {
+    scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename);
+    g_free(msgbuf);
+    return NULL;
+  }
+
+  msgbuf_utf8 = to_utf8(msgbuf);
+
+  if (!msgbuf_utf8 && msgbuf)
+    scr_LogPrint(LPRINT_LOGNORM, "Message file charset conversion error (%s)",
+                 filename);
+  g_free(msgbuf);
+  return msgbuf_utf8;
+}
+
+static void do_say_to(char *arg)
+{
+  char **paramlst;
+  char *fjid, *msg;
+  char *file = NULL;
+  LmMessageSubType msg_type = LM_MESSAGE_SUB_TYPE_NOT_SET;
+  bool quiet = FALSE;
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    return;
+  }
+
+  msg_type = scan_mtype(&arg);
+  paramlst = split_arg(arg, 2, 1); // jid, message (or option, jid, message)
+
+  if (!*paramlst) {  // No parameter?
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  // Check for an option parameter
+  while (*paramlst) {
+    if (!strcmp(*paramlst, "-q")) {
+      char **oldparamlst = paramlst;
+      paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, message
+      free_arg_lst(oldparamlst);
+      quiet = TRUE;
+    } else if (!strcmp(*paramlst, "-f")) {
+      char **oldparamlst = paramlst;
+      paramlst = split_arg(*(oldparamlst+1), 2, 1); // filename, jid
+      free_arg_lst(oldparamlst);
+      if (!*paramlst) {
+        scr_LogPrint(LPRINT_NORMAL, "Wrong usage.");
+        return;
+      }
+      file = g_strdup(*paramlst);
+      // One more parameter shift...
+      oldparamlst = paramlst;
+      paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, nothing
+      free_arg_lst(oldparamlst);
+    } else
+      break;
+  }
+
+  if (!*paramlst) {
+    scr_LogPrint(LPRINT_NORMAL, "Wrong usage.");
+    return;
+  }
+
+  fjid = *paramlst;
+  msg = *(paramlst+1);
+
+  if (!strcmp(fjid, ".")) {
+    // Send the message to the current buddy
+    if (current_buddy)
+      fjid = (char*)buddy_getjid(BUDDATA(current_buddy));
+    if (!fjid) {
+      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+      free_arg_lst(paramlst);
+      return;
+    }
+  } else if (check_jid_syntax(fjid)) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  fjid = to_utf8(fjid);
+  if (!file) {
+    msg = to_utf8(msg);
+  } else {
+    char *filename_xp;
+    if (msg)
+      scr_LogPrint(LPRINT_NORMAL, "say_to: extra parameter ignored.");
+    filename_xp = expand_filename(file);
+    msg = load_message_from_file(filename_xp);
+    g_free(filename_xp);
+    g_free(file);
+  }
+
+  send_message_to(fjid, msg, NULL, msg_type, quiet);
+
+  g_free(fjid);
+  g_free(msg);
+  free_arg_lst(paramlst);
+}
+
+//  buffer_updown(updown, nblines)
+// updown: -1=up, +1=down
+inline static void buffer_updown(int updown, char *nlines)
+{
+  int nblines;
+
+  if (!nlines || !*nlines)
+    nblines = 0;
+  else
+    nblines = strtol(nlines, NULL, 10);
+
+  if (nblines >= 0)
+    scr_BufferScrollUpDown(updown, nblines);
+}
+
+static void buffer_search(int direction, char *arg)
+{
+  if (!arg || !*arg) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    return;
+  }
+
+  scr_BufferSearch(direction, arg);
+}
+
+static void buffer_date(char *date)
+{
+  time_t t;
+
+  if (!date || !*date) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    return;
+  }
+
+  strip_arg_special_chars(date);
+
+  t = from_iso8601(date, 0);
+  if (t)
+    scr_BufferDate(t);
+  else
+    scr_LogPrint(LPRINT_NORMAL, "The date you specified is "
+                 "not correctly formatted or invalid.");
+}
+
+static void buffer_percent(char *arg1, char *arg2)
+{
+  // Basically, user has typed "%arg1 arg2"
+  // "%50"  -> arg1 = 50, arg2 null pointer
+  // "% 50" -> arg1 = \0, arg2 = 50
+
+  if (!*arg1 && (!arg2 || !*arg2)) { // No value
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    return;
+  }
+
+  if (*arg1 && arg2 && *arg2) {     // Two values
+    scr_LogPrint(LPRINT_NORMAL, "Wrong parameters.");
+    return;
+  }
+
+  scr_BufferPercent(atoi((*arg1 ? arg1 : arg2)));
+}
+
+static void do_buffer(char *arg)
+{
+  char **paramlst;
+  char *subcmd;
+
+  if (!current_buddy)
+    return;
+
+  paramlst = split_arg(arg, 2, 1); // subcmd, arg
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP &&
+      strcasecmp(subcmd, "close_all")) {
+    scr_LogPrint(LPRINT_NORMAL, "Groups have no buffer.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(subcmd, "top")) {
+    scr_BufferTopBottom(-1);
+  } else if (!strcasecmp(subcmd, "bottom")) {
+    scr_BufferTopBottom(1);
+  } else if (!strcasecmp(subcmd, "clear")) {
+    scr_BufferClear();
+  } else if (!strcasecmp(subcmd, "close")) {
+    scr_BufferPurge(1, arg);
+  } else if (!strcasecmp(subcmd, "close_all")) {
+    scr_BufferPurgeAll(1);
+  } else if (!strcasecmp(subcmd, "purge")) {
+    scr_BufferPurge(0, arg);
+  } else if (!strcasecmp(subcmd, "scroll_lock")) {
+    scr_BufferScrollLock(1);
+  } else if (!strcasecmp(subcmd, "scroll_unlock")) {
+    scr_BufferScrollLock(0);
+  } else if (!strcasecmp(subcmd, "scroll_toggle")) {
+    scr_BufferScrollLock(-1);
+  } else if (!strcasecmp(subcmd, "up")) {
+    buffer_updown(-1, arg);
+  } else if (!strcasecmp(subcmd, "down")) {
+    buffer_updown(1, arg);
+  } else if (!strcasecmp(subcmd, "search_backward")) {
+    strip_arg_special_chars(arg);
+    buffer_search(-1, arg);
+  } else if (!strcasecmp(subcmd, "search_forward")) {
+    strip_arg_special_chars(arg);
+    buffer_search(1, arg);
+  } else if (!strcasecmp(subcmd, "date")) {
+    buffer_date(arg);
+  } else if (*subcmd == '%') {
+    buffer_percent(subcmd+1, arg);
+  } else if (!strcasecmp(subcmd, "save")) {
+    scr_BufferDump(arg);
+  } else if (!strcasecmp(subcmd, "list")) {
+    scr_BufferList();
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+  }
+
+  free_arg_lst(paramlst);
+}
+
+static void do_clear(char *arg)    // Alias for "buffer clear"
+{
+  do_buffer("clear");
+}
+
+static void do_info(char *arg)
+{
+  gpointer bud;
+  const char *bjid, *name;
+  guint type, on_srv;
+  char *buffer;
+  enum subscr esub;
+
+  if (!current_buddy)
+    return;
+  bud = BUDDATA(current_buddy);
+
+  bjid   = buddy_getjid(bud);
+  name   = buddy_getname(bud);
+  type   = buddy_gettype(bud);
+  esub   = buddy_getsubscription(bud);
+  on_srv = buddy_getonserverflag(bud);
+
+  buffer = g_new(char, 4096);
+
+  if (bjid) {
+    GSList *resources, *p_res;
+    char *bstr = "unknown";
+
+    // Enter chat mode
+    scr_set_chatmode(TRUE);
+    scr_ShowBuddyWindow();
+
+    snprintf(buffer, 4095, "jid:  <%s>", bjid);
+    scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+    if (name) {
+      snprintf(buffer, 4095, "Name: %s", name);
+      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+    }
+
+    if (type == ROSTER_TYPE_USER)       bstr = "user";
+    else if (type == ROSTER_TYPE_ROOM)  bstr = "chatroom";
+    else if (type == ROSTER_TYPE_AGENT) bstr = "agent";
+    snprintf(buffer, 127, "Type: %s", bstr);
+    scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+
+    if (!on_srv) {
+      scr_WriteIncomingMessage(bjid, "(Local item, not on the server)",
+                               0, HBB_PREFIX_INFO, 0);
+    }
+
+    if (esub == sub_both)     bstr = "both";
+    else if (esub & sub_from) bstr = "from";
+    else if (esub & sub_to)   bstr = "to";
+    else bstr = "none";
+    snprintf(buffer, 64, "Subscription: %s", bstr);
+    if (esub & sub_pending)
+      strcat(buffer, " (pending)");
+    scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+
+    resources = buddy_getresources(bud);
+    if (!resources && type == ROSTER_TYPE_USER) {
+      // No resource; display last status message, if any.
+      const char *rst_msg = buddy_getstatusmsg(bud, "");
+      if (rst_msg) {
+        snprintf(buffer, 4095, "Last status message: %s", rst_msg);
+        scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+      }
+    }
+    for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
+      gchar rprio;
+      enum imstatus rstatus;
+      const char *rst_msg;
+      time_t rst_time;
+      struct pgp_data *rpgp;
+
+      rprio   = buddy_getresourceprio(bud, p_res->data);
+      rstatus = buddy_getstatus(bud, p_res->data);
+      rst_msg = buddy_getstatusmsg(bud, p_res->data);
+      rst_time = buddy_getstatustime(bud, p_res->data);
+      rpgp = buddy_resource_pgp(bud, p_res->data);
+
+      snprintf(buffer, 4095, "Resource: [%c] (%d) %s", imstatus2char[rstatus],
+               rprio, (char*)p_res->data);
+      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+      if (rst_msg) {
+        snprintf(buffer, 4095, "Status message: %s", rst_msg);
+        scr_WriteIncomingMessage(bjid, buffer,
+                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+      }
+      if (rst_time) {
+        char tbuf[128];
+
+        strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time));
+        snprintf(buffer, 127, "Status timestamp: %s", tbuf);
+        scr_WriteIncomingMessage(bjid, buffer,
+                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+      }
+#ifdef HAVE_GPGME
+      if (rpgp && rpgp->sign_keyid) {
+        snprintf(buffer, 4095, "PGP key id: %s", rpgp->sign_keyid);
+        scr_WriteIncomingMessage(bjid, buffer,
+                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+        if (rpgp->last_sigsum) {
+          gpgme_sigsum_t ss = rpgp->last_sigsum;
+          snprintf(buffer, 4095, "Last PGP signature: %s",
+                  (ss & GPGME_SIGSUM_GREEN ? "good":
+                   (ss & GPGME_SIGSUM_RED ? "bad" : "unknown")));
+          scr_WriteIncomingMessage(bjid, buffer,
+                                   0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+        }
+      }
+#endif
+      g_free(p_res->data);
+    }
+    g_slist_free(resources);
+  } else {  /* Item has no jid */
+    if (name) scr_LogPrint(LPRINT_NORMAL, "Name: %s", name);
+    scr_LogPrint(LPRINT_NORMAL, "Type: %s",
+                 type == ROSTER_TYPE_GROUP ? "group" :
+                 (type == ROSTER_TYPE_SPECIAL ? "special" : "unknown"));
+  }
+  g_free(buffer);
+
+  // Tell the user if this item has an annotation.
+  if (type == ROSTER_TYPE_USER ||
+      type == ROSTER_TYPE_ROOM ||
+      type == ROSTER_TYPE_AGENT) {
+    struct annotation *note = xmpp_get_storage_rosternotes(bjid, TRUE);
+    if (note) {
+      // We do not display the note, we just tell the user.
+      g_free(note->text);
+      g_free(note->jid);
+      g_free(note);
+      scr_WriteIncomingMessage(bjid, "(This item has an annotation)", 0,
+                               HBB_PREFIX_INFO, 0);
+    }
+  }
+}
+
+// room_names() is a variation of do_info(), for chatrooms only
+static void room_names(gpointer bud, char *arg)
+{
+  const char *bjid;
+  char *buffer;
+  GSList *resources, *p_res;
+  enum { style_normal = 0, style_detail, style_short,
+         style_quiet, style_compact } style = 0;
+
+  if (*arg) {
+    if (!strcasecmp(arg, "--short"))
+      style = style_short;
+    else if (!strcasecmp(arg, "--quiet"))
+      style = style_quiet;
+    else if (!strcasecmp(arg, "--detail"))
+      style = style_detail;
+    else if (!strcasecmp(arg, "--compact"))
+      style = style_compact;
+    else {
+      scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+      return;
+    }
+  }
+
+  // Enter chat mode
+  scr_set_chatmode(TRUE);
+  scr_ShowBuddyWindow();
+
+  bjid = buddy_getjid(bud);
+
+  buffer = g_new(char, 4096);
+  strncpy(buffer, "Room members:", 127);
+  scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+
+  resources = buddy_getresources(bud);
+  for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
+    enum imstatus rstatus;
+    const char *rst_msg;
+
+    rstatus = buddy_getstatus(bud, p_res->data);
+    rst_msg = buddy_getstatusmsg(bud, p_res->data);
+
+    if (style == style_short) {
+      snprintf(buffer, 4095, "[%c] %s%s%s", imstatus2char[rstatus],
+               (char*)p_res->data,
+               rst_msg ? " -- " : "", rst_msg ? rst_msg : "");
+      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+    } else if (style == style_compact) {
+        enum imrole role = buddy_getrole(bud, p_res->data);
+        enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
+        bool showaffil = (affil != affil_none);
+
+        snprintf(buffer, 4095, "[%c] %s (%s%s%s)",
+                 imstatus2char[rstatus], (char*)p_res->data,
+                 showaffil ? straffil[affil] : "\0",
+                 showaffil ? "/" : "\0",
+                 strrole[role]);
+        scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+      } else {
+      // (Style "normal", "detail" or "quiet")
+      snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus],
+               (char*)p_res->data);
+      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+      if (rst_msg && style != style_quiet) {
+        snprintf(buffer, 4095, "Status message: %s", rst_msg);
+        scr_WriteIncomingMessage(bjid, buffer,
+                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+      }
+      if (style == style_detail) {
+        enum imrole role = buddy_getrole(bud, p_res->data);
+        enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
+
+        snprintf(buffer, 4095, "Role: %s", strrole[role]);
+        scr_WriteIncomingMessage(bjid, buffer,
+                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+        if (affil != affil_none) {
+          snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]);
+          scr_WriteIncomingMessage(bjid, buffer,
+                                   0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+        }
+      }
+    }
+    g_free(p_res->data);
+  }
+  g_slist_free(resources);
+  g_free(buffer);
+}
+
+static void move_group_member(gpointer bud, void *groupnamedata)
+{
+  const char *bjid, *name, *groupname;
+
+  groupname = (char *)groupnamedata;
+
+  bjid = buddy_getjid(bud);
+  name = buddy_getname(bud);
+
+  xmpp_updatebuddy(bjid, name, *groupname ? groupname : NULL);
+}
+
+static void do_rename(char *arg)
+{
+  gpointer bud;
+  const char *bjid, *group;
+  guint type, on_srv;
+  char *newname, *p;
+  char *name_utf8;
+
+  if (!current_buddy)
+    return;
+  bud = BUDDATA(current_buddy);
+
+  bjid   = buddy_getjid(bud);
+  group  = buddy_getgroupname(bud);
+  type   = buddy_gettype(bud);
+  on_srv = buddy_getonserverflag(bud);
+
+  if (type & ROSTER_TYPE_SPECIAL) {
+    scr_LogPrint(LPRINT_NORMAL, "You can't rename this item.");
+    return;
+  }
+
+  if (!*arg && !(type & ROSTER_TYPE_GROUP)) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a new name.");
+    return;
+  }
+
+  if (!(type & ROSTER_TYPE_GROUP) && !on_srv) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Note: this item will be added to your server roster.");
+    // If this is a MUC room w/o bookmark, let's give a small hint...
+    if ((type & ROSTER_TYPE_ROOM) && !xmpp_is_bookmarked(bjid)) {
+      scr_LogPrint(LPRINT_NORMAL,
+                   "You should add a room bookmark or it will not be "
+                   "recognized as a MUC room next time you run mcabber.");
+    }
+  }
+
+  newname = g_strdup(arg);
+  // Remove trailing space
+  for (p = newname; *p; p++) ;
+  while (p > newname && *p == ' ') *p = 0;
+
+  strip_arg_special_chars(newname);
+
+  name_utf8 = to_utf8(newname);
+
+  if (type & ROSTER_TYPE_GROUP) {
+    // Rename a whole group
+    foreach_group_member(bud, &move_group_member, name_utf8);
+    // Let's jump to the previous buddy, because this group name should
+    // disappear when we receive the server answer.
+    scr_RosterUpDown(-1, 1);
+  } else {
+    // Rename a single buddy
+    guint del_name = 0;
+    if (!*newname || !strcmp(arg, "-"))
+      del_name = TRUE;
+    buddy_setname(bud, (del_name ? (char*)bjid : name_utf8));
+    xmpp_updatebuddy(bjid, (del_name ? NULL : name_utf8), group);
+  }
+
+  g_free(name_utf8);
+  g_free(newname);
+  update_roster = TRUE;
+}
+
+static void do_move(char *arg)
+{
+  gpointer bud;
+  const char *bjid, *name, *oldgroupname;
+  guint type;
+  char *newgroupname, *p;
+  char *group_utf8;
+
+  if (!current_buddy)
+    return;
+  bud = BUDDATA(current_buddy);
+
+  bjid = buddy_getjid(bud);
+  name = buddy_getname(bud);
+  type = buddy_gettype(bud);
+
+  oldgroupname = buddy_getgroupname(bud);
+
+  if (type & ROSTER_TYPE_GROUP) {
+    scr_LogPrint(LPRINT_NORMAL, "You can't move groups!");
+    return;
+  }
+  if (type & ROSTER_TYPE_SPECIAL) {
+    scr_LogPrint(LPRINT_NORMAL, "You can't move this item.");
+    return;
+  }
+
+  newgroupname = g_strdup(arg);
+  // Remove trailing space
+  for (p = newgroupname; *p; p++) ;
+  while (p > newgroupname && *p == ' ') *p-- = 0;
+
+  strip_arg_special_chars(newgroupname);
+
+  group_utf8 = to_utf8(newgroupname);
+  if (strcmp(oldgroupname, group_utf8)) {
+    guint msgflag;
+
+    xmpp_updatebuddy(bjid, name, *group_utf8 ? group_utf8 : NULL);
+    scr_RosterUpDown(-1, 1);
+
+    // If the buddy has a pending message flag,
+    // we remove it temporarily in order to reset the global group
+    // flag.  We set it back once the buddy is in the new group,
+    // which will update the new group's flag.
+    msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG;
+    if (msgflag)
+      roster_msg_setflag(bjid, FALSE, FALSE);
+    buddy_setgroup(bud, group_utf8);
+    if (msgflag)
+      roster_msg_setflag(bjid, FALSE, TRUE);
+  }
+
+  g_free(group_utf8);
+  g_free(newgroupname);
+  update_roster = TRUE;
+}
+
+static void print_option_cb(char *k, char *v, void *f)
+{
+  char *format = (char *)f;
+  scr_LogPrint (LPRINT_NORMAL, format, k, v);
+}
+
+static void do_set(char *arg)
+{
+  guint assign;
+  gchar *option, *value;
+  gchar *option_utf8;
+
+  if (!*arg) {
+    // list all set options
+    settings_foreach(SETTINGS_TYPE_OPTION, print_option_cb, "%s = [%s]");
+    return;
+  }
+
+  assign = parse_assigment(arg, &option, &value);
+  if (!option) {
+    scr_LogPrint(LPRINT_NORMAL, "Set what option?");
+    return;
+  }
+  option_utf8 = to_utf8(option);
+  g_free(option);
+  if (!assign) {  // This is a query
+    const char *val = settings_opt_get(option_utf8);
+    if (val)
+      scr_LogPrint(LPRINT_NORMAL, "%s = [%s]", option_utf8, val);
+    else
+      scr_LogPrint(LPRINT_NORMAL, "Option %s is not set", option_utf8);
+    g_free(option_utf8);
+    return;
+  }
+  // Update the option
+  // Maybe some options should be protected when user is connected (server,
+  // username, etc.).  And we should catch some options here, too
+  // (hide_offline_buddies for ex.)
+  if (!value) {
+    settings_del(SETTINGS_TYPE_OPTION, option_utf8);
+  } else {
+    gchar *value_utf8 = to_utf8(value);
+    settings_set(SETTINGS_TYPE_OPTION, option_utf8, value_utf8);
+    g_free(value_utf8);
+    g_free(value);
+  }
+  g_free(option_utf8);
+}
+
+static void dump_alias(char *k, char *v, void *param)
+{
+  scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "Alias %s = %s", k, v);
+}
+
+static void do_alias(char *arg)
+{
+  guint assign;
+  gchar *alias, *value;
+
+  assign = parse_assigment(arg, &alias, &value);
+  if (!alias) {
+    settings_foreach(SETTINGS_TYPE_ALIAS, &dump_alias, NULL);
+    return;
+  }
+  if (!assign) {  // This is a query
+    const char *val = settings_get(SETTINGS_TYPE_ALIAS, alias);
+    // NOTE: LPRINT_NOTUTF8 here, see below why it isn't encoded...
+    if (val)
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "%s = %s", alias, val);
+    else
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "Alias '%s' does not exist", alias);
+    goto do_alias_return;
+  }
+  // Check the alias does not conflict with a registered command
+  if (cmd_get(alias)) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "'%s' is a reserved word!", alias);
+      goto do_alias_return;
+  }
+  // Update the alias
+  if (!value) {
+    if (settings_get(SETTINGS_TYPE_ALIAS, alias)) {
+      settings_del(SETTINGS_TYPE_ALIAS, alias);
+      // Remove alias from the completion list
+      compl_del_category_word(COMPL_CMD, alias);
+    }
+  } else {
+    /* Add alias to the completion list, if not already in.
+       NOTE: We're not UTF8-encoding "alias" and "value" here because UTF-8 is
+       not yet supported in the UI... (and we use the values in the completion
+       system)
+    */
+    if (!settings_get(SETTINGS_TYPE_ALIAS, alias))
+      compl_add_category_word(COMPL_CMD, alias);
+    settings_set(SETTINGS_TYPE_ALIAS, alias, value);
+    g_free(value);
+  }
+do_alias_return:
+  g_free(alias);
+}
+
+static void dump_bind(char *k, char *v, void *param)
+{
+  scr_LogPrint(LPRINT_NORMAL, "Key %4s is bound to: %s", k, v);
+}
+
+static void do_bind(char *arg)
+{
+  guint assign;
+  gchar *k_code, *value;
+
+  assign = parse_assigment(arg, &k_code, &value);
+  if (!k_code) {
+    settings_foreach(SETTINGS_TYPE_BINDING, &dump_bind, NULL);
+    return;
+  }
+  if (!assign) {  // This is a query
+    const char *val = settings_get(SETTINGS_TYPE_BINDING, k_code);
+    if (val)
+      scr_LogPrint(LPRINT_NORMAL, "Key %s is bound to: %s", k_code, val);
+    else
+      scr_LogPrint(LPRINT_NORMAL, "Key %s is not bound.", k_code);
+    g_free(k_code);
+    return;
+  }
+  // Update the key binding
+  if (!value) {
+    settings_del(SETTINGS_TYPE_BINDING, k_code);
+  } else {
+    gchar *value_utf8 = to_utf8(value);
+    settings_set(SETTINGS_TYPE_BINDING, k_code, value_utf8);
+    g_free(value_utf8);
+    g_free(value);
+  }
+  g_free(k_code);
+}
+
+static void do_rawxml(char *arg)
+{
+  char **paramlst;
+  char *subcmd;
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    return;
+  }
+
+  paramlst = split_arg(arg, 2, 1); // subcmd, arg
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Please read the manual page"
+                 " before using /rawxml :-)");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(subcmd, "send"))  {
+    gchar *buffer;
+
+    if (!subcmd || !*subcmd) {
+      scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+      free_arg_lst(paramlst);
+      return;
+    }
+
+    // We don't strip_arg_special_chars() here, because it would be a pain for
+    // the user to escape quotes in a XML stream...
+
+    buffer = to_utf8(arg);
+    if (buffer) {
+      scr_LogPrint(LPRINT_NORMAL, "Sending XML string");
+      lm_connection_send_raw(lconnection, buffer, NULL);
+      g_free(buffer);
+    } else {
+      scr_LogPrint(LPRINT_NORMAL, "Conversion error in XML string.");
+    }
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+  }
+
+  free_arg_lst(paramlst);
+}
+
+//  check_room_subcommand(arg, param_needed, buddy_must_be_a_room)
+// - Check if this is a room, if buddy_must_be_a_room is not null
+// - Check there is at least 1 parameter, if param_needed is true
+// - Return null if one of the checks fails, or a pointer to the first
+//   non-space character.
+static char *check_room_subcommand(char *arg, bool param_needed,
+                                   gpointer buddy_must_be_a_room)
+{
+  if (buddy_must_be_a_room &&
+      !(buddy_gettype(buddy_must_be_a_room) & ROSTER_TYPE_ROOM)) {
+    scr_LogPrint(LPRINT_NORMAL, "This isn't a conference room.");
+    return NULL;
+  }
+
+  if (param_needed) {
+    if (!arg) {
+      scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+      return NULL;
+    }
+  }
+
+  if (arg)
+    return arg;
+  else
+    return "";
+}
+
+static void room_join(gpointer bud, char *arg)
+{
+  char **paramlst;
+  char *roomname, *nick, *pass;
+  char *roomname_tmp = NULL;
+  char *pass_utf8;
+
+  paramlst = split_arg(arg, 3, 0); // roomid, nickname, password
+  roomname = *paramlst;
+  nick = *(paramlst+1);
+  pass = *(paramlst+2);
+
+  if (!roomname)
+    nick = NULL;
+  if (!nick)
+    pass = NULL;
+
+  if (!roomname || !strcmp(roomname, ".")) {
+    // If the current_buddy is recognized as a room, the room name
+    // can be omitted (or "." can be used).
+    if (!bud || !(buddy_gettype(bud) & ROSTER_TYPE_ROOM)) {
+      scr_LogPrint(LPRINT_NORMAL, "Please specify a room name.");
+      free_arg_lst(paramlst);
+      return;
+    }
+    roomname = (char*)buddy_getjid(bud);
+  } else if (strchr(roomname, '/')) {
+    scr_LogPrint(LPRINT_NORMAL, "Invalid room name.");
+    free_arg_lst(paramlst);
+    return;
+  } else {
+    // The room id has been specified.  Let's convert it and use it.
+    mc_strtolower(roomname);
+    roomname = roomname_tmp = to_utf8(roomname);
+  }
+
+  // If no nickname is provided with the /join command,
+  // we try to get a default nickname.
+  if (!nick || !*nick)
+    nick = default_muc_nickname(roomname);
+  else
+    nick = to_utf8(nick);
+  // If we still have no nickname, give up
+  if (!nick || !*nick) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname.");
+    g_free(nick);
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  pass_utf8 = to_utf8(pass);
+
+  xmpp_room_join(roomname, nick, pass_utf8);
+
+  scr_LogPrint(LPRINT_LOGNORM, "Sent a join request to <%s>...", roomname);
+
+  g_free(roomname_tmp);
+  g_free(nick);
+  g_free(pass_utf8);
+  buddylist_build();
+  update_roster = TRUE;
+  free_arg_lst(paramlst);
+}
+
+static void room_invite(gpointer bud, char *arg)
+{
+  char **paramlst;
+  const gchar *roomname;
+  char* fjid;
+  gchar *reason_utf8;
+
+  paramlst = split_arg(arg, 2, 1); // jid, [reason]
+  fjid = *paramlst;
+  arg = *(paramlst+1);
+  // An empty reason is no reason...
+  if (arg && !*arg)
+    arg = NULL;
+
+  if (!fjid || !*fjid) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing or incorrect Jabber ID.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  roomname = buddy_getjid(bud);
+  reason_utf8 = to_utf8(arg);
+  xmpp_room_invite(roomname, fjid, reason_utf8);
+  scr_LogPrint(LPRINT_LOGNORM, "Invitation sent to <%s>.", fjid);
+  g_free(reason_utf8);
+  free_arg_lst(paramlst);
+}
+
+static void room_affil(gpointer bud, char *arg)
+{
+  char **paramlst;
+  gchar *fjid, *rolename;
+  struct role_affil ra;
+  const char *roomid = buddy_getjid(bud);
+
+  paramlst = split_arg(arg, 3, 1); // jid, new_affil, [reason]
+  fjid = *paramlst;
+  rolename = *(paramlst+1);
+  arg = *(paramlst+2);
+
+  if (!fjid || !*fjid || !rolename || !*rolename) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  ra.type = type_affil;
+  ra.val.affil = affil_none;
+  for (; ra.val.affil < imaffiliation_size; ra.val.affil++)
+    if (!strcasecmp(rolename, straffil[ra.val.affil]))
+      break;
+
+  if (ra.val.affil < imaffiliation_size) {
+    gchar *jid_utf8, *reason_utf8;
+    jid_utf8 = to_utf8(fjid);
+    reason_utf8 = to_utf8(arg);
+    xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8);
+    g_free(jid_utf8);
+    g_free(reason_utf8);
+  } else
+    scr_LogPrint(LPRINT_NORMAL, "Wrong affiliation parameter.");
+
+  free_arg_lst(paramlst);
+}
+
+static void room_role(gpointer bud, char *arg)
+{
+  char **paramlst;
+  gchar *fjid, *rolename;
+  struct role_affil ra;
+  const char *roomid = buddy_getjid(bud);
+
+  paramlst = split_arg(arg, 3, 1); // jid, new_role, [reason]
+  fjid = *paramlst;
+  rolename = *(paramlst+1);
+  arg = *(paramlst+2);
+
+  if (!fjid || !*fjid || !rolename || !*rolename) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  ra.type = type_role;
+  ra.val.role = role_none;
+  for (; ra.val.role < imrole_size; ra.val.role++)
+    if (!strcasecmp(rolename, strrole[ra.val.role]))
+      break;
+
+  if (ra.val.role < imrole_size) {
+    gchar *jid_utf8, *reason_utf8;
+    jid_utf8 = to_utf8(fjid);
+    reason_utf8 = to_utf8(arg);
+    xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8);
+    g_free(jid_utf8);
+    g_free(reason_utf8);
+  } else
+    scr_LogPrint(LPRINT_NORMAL, "Wrong role parameter.");
+
+  free_arg_lst(paramlst);
+}
+
+
+// The expected argument is a Jabber id
+static void room_ban(gpointer bud, char *arg)
+{
+  char **paramlst;
+  gchar *fjid, *bjid;
+  const gchar *banjid;
+  gchar *jid_utf8, *reason_utf8;
+  struct role_affil ra;
+  const char *roomid = buddy_getjid(bud);
+
+  paramlst = split_arg(arg, 2, 1); // jid, [reason]
+  fjid = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!fjid || !*fjid) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  ra.type = type_affil;
+  ra.val.affil = affil_outcast;
+
+  bjid = jidtodisp(fjid);
+  jid_utf8 = to_utf8(bjid);
+
+  // If the argument doesn't look like a jid, we'll try to find a matching
+  // nickname.
+  if (!strchr(bjid, JID_DOMAIN_SEPARATOR) || check_jid_syntax(bjid)) {
+    const gchar *tmp;
+    // We want the initial argument, so the fjid variable, because
+    // we don't want to strip a resource-like string from the nickname!
+    g_free(jid_utf8);
+    jid_utf8 = to_utf8(fjid);
+    tmp = buddy_getrjid(bud, jid_utf8);
+    if (!tmp) {
+      scr_LogPrint(LPRINT_NORMAL, "Wrong JID or nickname");
+      goto room_ban_return;
+    }
+    banjid = jidtodisp(tmp);
+  } else
+    banjid = jid_utf8;
+
+  scr_LogPrint(LPRINT_NORMAL, "Requesting a ban for %s", banjid);
+
+  reason_utf8 = to_utf8(arg);
+  xmpp_room_setattrib(roomid, banjid, NULL, ra, reason_utf8);
+  g_free(reason_utf8);
+
+room_ban_return:
+  g_free(bjid);
+  g_free(jid_utf8);
+  free_arg_lst(paramlst);
+}
+
+// The expected argument is a Jabber id
+static void room_unban(gpointer bud, char *arg)
+{
+  gchar *fjid = arg;
+  gchar *jid_utf8;
+  struct role_affil ra;
+  const char *roomid = buddy_getjid(bud);
+
+  if (!fjid || !*fjid) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+    return;
+  }
+
+  ra.type = type_affil;
+  ra.val.affil = affil_none;
+
+  jid_utf8 = to_utf8(fjid);
+  xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, NULL);
+  g_free(jid_utf8);
+}
+
+// The expected argument is a nickname
+static void room_kick(gpointer bud, char *arg)
+{
+  char **paramlst;
+  gchar *nick;
+  gchar *nick_utf8, *reason_utf8;
+  struct role_affil ra;
+  const char *roomid = buddy_getjid(bud);
+
+  paramlst = split_arg(arg, 2, 1); // nickname, [reason]
+  nick = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!nick || !*nick) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  ra.type = type_role;
+  ra.val.affil = role_none;
+
+  nick_utf8 = to_utf8(nick);
+  reason_utf8 = to_utf8(arg);
+  xmpp_room_setattrib(roomid, NULL, nick_utf8, ra, reason_utf8);
+  g_free(nick_utf8);
+  g_free(reason_utf8);
+
+  free_arg_lst(paramlst);
+}
+
+void cmd_room_leave(gpointer bud, char *arg)
+{
+  gchar *roomid, *desc;
+  const char *nickname;
+
+  nickname = buddy_getnickname(bud);
+  if (!nickname) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not in this room.");
+    return;
+  }
+
+  roomid = g_strdup_printf("%s/%s", buddy_getjid(bud), nickname);
+  desc = to_utf8(arg);
+  xmpp_setstatus(offline, roomid, desc, TRUE);
+  g_free(desc);
+  g_free(roomid);
+}
+
+static void room_nick(gpointer bud, char *arg)
+{
+  if (!buddy_getinsideroom(bud)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not in this room.");
+    return;
+  }
+
+  if (!arg || !*arg) {
+    const char *nick = buddy_getnickname(bud);
+    if (nick)
+      scr_LogPrint(LPRINT_NORMAL, "Your nickname is: %s", nick);
+    else
+      scr_LogPrint(LPRINT_NORMAL, "You have no nickname in this room.");
+  } else {
+    gchar *nick = to_utf8(arg);
+    strip_arg_special_chars(nick);
+    xmpp_room_join(buddy_getjid(bud), nick, NULL);
+    g_free(nick);
+  }
+}
+
+static void room_privmsg(gpointer bud, char *arg)
+{
+  char **paramlst;
+  gchar *fjid_utf8, *nick, *nick_utf8, *msg;
+
+  paramlst = split_arg(arg, 2, 1); // nickname, message
+  nick = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!nick || !*nick || !arg || !*arg) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Please specify both a Jabber ID and a message.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  nick_utf8 = to_utf8(nick);
+  fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8);
+  g_free (nick_utf8);
+  msg = to_utf8(arg);
+  send_message_to(fjid_utf8, msg, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, FALSE);
+  g_free(fjid_utf8);
+  g_free(msg);
+  free_arg_lst(paramlst);
+}
+
+static void room_remove(gpointer bud, char *arg)
+{
+  if (*arg) {
+    scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; "
+                 "the currently-selected room will be removed.");
+    return;
+  }
+
+  // Quick check: if there are resources, we haven't left
+  if (buddy_getinsideroom(bud)) {
+    scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!");
+    return;
+  }
+  // Delete the room
+  roster_del_user(buddy_getjid(bud));
+  scr_UpdateBuddyWindow();
+  buddylist_build();
+  update_roster = TRUE;
+}
+
+static void room_topic(gpointer bud, char *arg)
+{
+  if (!buddy_getinsideroom(bud)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not in this room.");
+    return;
+  }
+
+  // If no parameter is given, display the current topic
+  if (!*arg) {
+    const char *topic = buddy_gettopic(bud);
+    if (topic)
+      scr_LogPrint(LPRINT_NORMAL, "Topic: %s", topic);
+    else
+      scr_LogPrint(LPRINT_NORMAL, "No topic has been set.");
+    return;
+  }
+
+  // If arg is "-", let's clear the topic
+  if (!strcmp(arg, "-"))
+    arg = NULL;
+
+  arg = to_utf8(arg);
+  // Set the topic
+  xmpp_send_msg(buddy_getjid(bud), NULL, ROSTER_TYPE_ROOM, arg ? arg : "",
+                FALSE, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, NULL);
+  g_free(arg);
+}
+
+static void room_destroy(gpointer bud, char *arg)
+{
+  gchar *msg;
+
+  if (arg && *arg)
+    msg = to_utf8(arg);
+  else
+    msg = NULL;
+
+  xmpp_room_destroy(buddy_getjid(bud), NULL, msg);
+  g_free(msg);
+}
+
+static void room_unlock(gpointer bud, char *arg)
+{
+  if (*arg) {
+    scr_LogPrint(LPRINT_NORMAL, "Unknown parameter.");
+    return;
+  }
+
+  xmpp_room_unlock(buddy_getjid(bud));
+}
+
+static void room_setopt(gpointer bud, char *arg)
+{
+  char **paramlst;
+  char *param, *value;
+  enum { opt_none = 0, opt_printstatus, opt_autowhois } option = 0;
+
+  paramlst = split_arg(arg, 2, 1); // param, value
+  param = *paramlst;
+  value = *(paramlst+1);
+  if (!param) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a room option.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(param, "print_status"))
+    option = opt_printstatus;
+  else if (!strcasecmp(param, "auto_whois"))
+    option = opt_autowhois;
+  else {
+    scr_LogPrint(LPRINT_NORMAL, "Wrong option!");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  // If no value is given, display the current value
+  if (!value) {
+    const char *strval;
+    if (option == opt_printstatus)
+      strval = strprintstatus[buddy_getprintstatus(bud)];
+    else
+      strval = strautowhois[buddy_getautowhois(bud)];
+    scr_LogPrint(LPRINT_NORMAL, "%s is set to: %s", param, strval);
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (option == opt_printstatus) {
+    enum room_printstatus eval;
+    if (!strcasecmp(value, "none"))
+      eval = status_none;
+    else if (!strcasecmp(value, "in_and_out"))
+      eval = status_in_and_out;
+    else if (!strcasecmp(value, "all"))
+      eval = status_all;
+    else {
+      eval = status_default;
+      if (strcasecmp(value, "default") != 0)
+        scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default...");
+    }
+    buddy_setprintstatus(bud, eval);
+  } else if (option == opt_autowhois) {
+    enum room_autowhois eval;
+    if (!strcasecmp(value, "on"))
+      eval = autowhois_on;
+    else if (!strcasecmp(value, "off"))
+      eval = autowhois_off;
+    else {
+      eval = autowhois_default;
+      if (strcasecmp(value, "default") != 0)
+        scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default...");
+    }
+    buddy_setautowhois(bud, eval);
+  }
+
+  free_arg_lst(paramlst);
+}
+
+//  cmd_room_whois(..)
+// If interactive is TRUE, chatmode can be enabled.
+void cmd_room_whois(gpointer bud, char *arg, guint interactive)
+{
+  char **paramlst;
+  gchar *nick, *buffer;
+  const char *bjid, *realjid;
+  const char *rst_msg;
+  gchar rprio;
+  enum imstatus rstatus;
+  enum imrole role;
+  enum imaffiliation affil;
+  time_t rst_time;
+  guint msg_flag = HBB_PREFIX_INFO;
+
+  paramlst = split_arg(arg, 1, 0); // nickname
+  nick = *paramlst;
+
+  if (!nick || !*nick) {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  nick = to_utf8(nick);
+
+  if (interactive) {
+    // Enter chat mode
+    scr_set_chatmode(TRUE);
+    scr_ShowBuddyWindow();
+  } else
+    msg_flag |= HBB_PREFIX_NOFLAG;
+
+  bjid = buddy_getjid(bud);
+  rstatus = buddy_getstatus(bud, nick);
+
+  if (rstatus == offline) {
+    scr_LogPrint(LPRINT_NORMAL, "No such member: %s", nick);
+    free_arg_lst(paramlst);
+    g_free(nick);
+    return;
+  }
+
+  rst_time = buddy_getstatustime(bud, nick);
+  rprio   = buddy_getresourceprio(bud, nick);
+  rst_msg = buddy_getstatusmsg(bud, nick);
+  if (!rst_msg) rst_msg = "";
+
+  role = buddy_getrole(bud, nick);
+  affil = buddy_getaffil(bud, nick);
+  realjid = buddy_getrjid(bud, nick);
+
+  buffer = g_new(char, 4096);
+
+  snprintf(buffer, 4095, "Whois [%s]", nick);
+  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag, 0);
+  snprintf(buffer, 4095, "Status   : [%c] %s", imstatus2char[rstatus],
+           rst_msg);
+  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
+
+  if (rst_time) {
+    char tbuf[128];
+
+    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time));
+    snprintf(buffer, 127, "Timestamp: %s", tbuf);
+    scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
+  }
+
+  if (realjid) {
+    snprintf(buffer, 4095, "JID      : <%s>", realjid);
+    scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
+  }
+
+  snprintf(buffer, 4095, "Role     : %s", strrole[role]);
+  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
+  snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]);
+  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
+  snprintf(buffer, 4095, "Priority : %d", rprio);
+  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
+
+  scr_WriteIncomingMessage(bjid, "End of WHOIS", 0, msg_flag, 0);
+
+  g_free(buffer);
+  g_free(nick);
+  free_arg_lst(paramlst);
+}
+
+static void room_bookmark(gpointer bud, char *arg)
+{
+  const char *roomid;
+  const char *name = NULL, *nick = NULL;
+  char *tmpnick = NULL;
+  enum room_autowhois autowhois = 0;
+  enum room_printstatus printstatus = 0;
+  enum { bm_add = 0, bm_del = 1 } action = 0;
+  int autojoin = 0;
+  int nick_set = 0;
+
+  if (arg && *arg) {
+    // /room bookmark [add|del] [[+|-]autojoin] [-|nick]
+    char **paramlst;
+    char **pp;
+
+    paramlst = split_arg(arg, 3, 0); // At most 3 parameters
+    for (pp = paramlst; *pp; pp++) {
+      if (!strcasecmp(*pp, "add"))
+        action = bm_add;
+      else if (!strcasecmp(*pp, "del"))
+        action = bm_del;
+      else if (!strcasecmp(*pp, "-autojoin"))
+        autojoin = 0;
+      else if (!strcasecmp(*pp, "+autojoin") || !strcasecmp(*pp, "autojoin"))
+        autojoin = 1;
+      else if (!strcmp(*pp, "-"))
+        nick_set = 1;
+      else {
+        nick_set = 1;
+        nick = tmpnick = to_utf8 (*pp);
+      }
+    }
+    free_arg_lst(paramlst);
+  }
+
+  roomid = buddy_getjid(bud);
+
+  if (action == bm_add) {
+    name = buddy_getname(bud);
+    if (!nick_set)
+      nick = buddy_getnickname(bud);
+    printstatus = buddy_getprintstatus(bud);
+    autowhois   = buddy_getautowhois(bud);
+  }
+
+  xmpp_set_storage_bookmark(roomid, name, nick, NULL, autojoin,
+                            printstatus, autowhois);
+  g_free (tmpnick);
+}
+
+static void display_all_bookmarks(void)
+{
+  GSList *bm, *bmp;
+  GString *sbuf;
+  struct bookmark *bm_elt;
+
+  bm = xmpp_get_all_storage_bookmarks();
+
+  if (!bm)
+    return;
+
+  sbuf = g_string_new("");
+
+  scr_WriteIncomingMessage(NULL, "List of MUC bookmarks:",
+                           0, HBB_PREFIX_INFO, 0);
+
+  for (bmp = bm; bmp; bmp = g_slist_next(bmp)) {
+    bm_elt = bmp->data;
+    g_string_printf(sbuf, "%c <%s>",
+                    (bm_elt->autojoin ? '*' : ' '), bm_elt->roomjid);
+    if (bm_elt->nick)
+      g_string_append_printf(sbuf, " (%s)", bm_elt->nick);
+    if (bm_elt->name)
+      g_string_append_printf(sbuf, " %s", bm_elt->name);
+    g_free(bm_elt->roomjid);
+    g_free(bm_elt->name);
+    g_free(bm_elt->nick);
+    g_free(bm_elt);
+    scr_WriteIncomingMessage(NULL, sbuf->str,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+  }
+
+  scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
+  update_roster = TRUE;
+  g_string_free(sbuf, TRUE);
+  g_slist_free(bm);
+}
+
+#ifdef MODULES_ENABLE
+static gint module_list_comparator(gconstpointer arg1, gconstpointer arg2)
+{
+  const loaded_module_t *module = arg1;
+  const char *name = arg2;
+  return g_strcmp0(module->name, name);
+}
+
+static void do_load(char *arg)
+{
+  GModule *mod;
+  GSList *lmod;
+  char *mdir, *path;
+  if (!arg || !*arg) {
+    scr_LogPrint(LPRINT_LOGNORM, "Missing modulename.");
+    return;
+  }
+  lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
+  if (lmod) {
+    scr_LogPrint(LPRINT_LOGNORM, "Module %s is already loaded.", arg);
+    return;
+  }
+  mdir = expand_filename(settings_opt_get("modules_dir"));
+  path = g_module_build_path(mdir, arg);
+  mod  = g_module_open(path, G_MODULE_BIND_LAZY);
+  if (!mod)
+    scr_LogPrint(LPRINT_LOGNORM, "Module loading failed: %s",
+                 g_module_error());
+  else {
+    loaded_module_t *module = g_new(loaded_module_t, 1);
+    module->name   = g_strdup(arg);
+    module->module = mod;
+    loaded_modules = g_slist_prepend(loaded_modules, module);
+    scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg);
+  }
+  g_free(path);
+  g_free(mdir);
+}
+
+static void do_unload(char *arg)
+{
+  GSList *module;
+  if (!arg || !*arg) {
+    scr_LogPrint(LPRINT_LOGNORM, "Missing modulename.");
+    return;
+  }
+  module = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
+  if (module) {
+    loaded_module_t *mod = module->data;
+    if (!g_module_close(mod->module))
+      scr_LogPrint(LPRINT_LOGNORM, "Module unloading failed: %s",
+                   g_module_error());
+    else {
+      g_free(mod->name);
+      g_free(mod);
+      loaded_modules = g_slist_delete_link(loaded_modules, module);
+      scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg);
+    }
+  } else
+    scr_LogPrint(LPRINT_LOGNORM, "Module %s not loaded.", arg);
+}
+#endif
+
+static void do_room(char *arg)
+{
+  char **paramlst;
+  char *subcmd;
+  gpointer bud;
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    return;
+  }
+
+  paramlst = split_arg(arg, 2, 1); // subcmd, arg
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (current_buddy) {
+    bud = BUDDATA(current_buddy);
+  } else {
+    if (strcasecmp(subcmd, "join")) {
+      free_arg_lst(paramlst);
+      return;
+    }
+    // "room join" is a special case, we don't need to have a valid
+    // current_buddy.
+    bud = NULL;
+  }
+
+  if (!strcasecmp(subcmd, "join"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, NULL)) != NULL)
+      room_join(bud, arg);
+  } else if (!strcasecmp(subcmd, "invite"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      room_invite(bud, arg);
+  } else if (!strcasecmp(subcmd, "affil"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      room_affil(bud, arg);
+  } else if (!strcasecmp(subcmd, "role"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      room_role(bud, arg);
+  } else if (!strcasecmp(subcmd, "ban"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      room_ban(bud, arg);
+  } else if (!strcasecmp(subcmd, "unban"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      room_unban(bud, arg);
+  } else if (!strcasecmp(subcmd, "kick"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      room_kick(bud, arg);
+  } else if (!strcasecmp(subcmd, "leave"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      cmd_room_leave(bud, arg);
+  } else if (!strcasecmp(subcmd, "names"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_names(bud, arg);
+  } else if (!strcasecmp(subcmd, "nick"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_nick(bud, arg);
+  } else if (!strcasecmp(subcmd, "privmsg"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      room_privmsg(bud, arg);
+  } else if (!strcasecmp(subcmd, "remove"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_remove(bud, arg);
+  } else if (!strcasecmp(subcmd, "destroy"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_destroy(bud, arg);
+  } else if (!strcasecmp(subcmd, "unlock"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_unlock(bud, arg);
+  } else if (!strcasecmp(subcmd, "setopt"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_setopt(bud, arg);
+  } else if (!strcasecmp(subcmd, "topic"))  {
+    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_topic(bud, arg);
+  } else if (!strcasecmp(subcmd, "whois"))  {
+    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
+      cmd_room_whois(bud, arg, TRUE);
+  } else if (!strcasecmp(subcmd, "bookmark"))  {
+    if (!arg && !buddy_getjid(BUDDATA(current_buddy)) &&
+        buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_SPECIAL)
+      display_all_bookmarks();
+    else if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+      room_bookmark(bud, arg);
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+  }
+
+  free_arg_lst(paramlst);
+}
+
+static void do_authorization(char *arg)
+{
+  char **paramlst;
+  char *subcmd;
+  char *jid_utf8;
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    return;
+  }
+
+  paramlst = split_arg(arg, 2, 0); // subcmd, [jid]
+  subcmd = *paramlst;
+  arg = *(paramlst+1);
+
+  if (!subcmd || !*subcmd) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+    goto do_authorization_return;
+  }
+
+  // Use the provided jid, if it looks valid
+  if (arg) {
+    if (!*arg) {
+      // If no jid is provided, we use the current selected buddy
+      arg = NULL;
+    } else {
+      if (check_jid_syntax(arg)) {
+        scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                     "<%s> is not a valid Jabber ID.", arg);
+        goto do_authorization_return;
+      }
+    }
+  }
+
+  if (!arg) {       // Use the current selected buddy's jid
+    gpointer bud;
+    guint type;
+
+    if (!current_buddy)
+      goto do_authorization_return;
+    bud = BUDDATA(current_buddy);
+
+    jid_utf8 = arg  = (char*)buddy_getjid(bud);
+    type = buddy_gettype(bud);
+
+    if (!(type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT))) {
+      scr_LogPrint(LPRINT_NORMAL, "Invalid buddy.");
+      goto do_authorization_return;
+    }
+  } else {
+    jid_utf8 = to_utf8(arg);
+  }
+
+  if (!strcasecmp(subcmd, "allow"))  {
+    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBED);
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Sent presence subscription approval to <%s>.",
+                 jid_utf8);
+  } else if (!strcasecmp(subcmd, "cancel"))  {
+    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "<%s> will no longer receive your presence updates.",
+                 jid_utf8);
+  } else if (!strcasecmp(subcmd, "request"))  {
+    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBE);
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Sent presence notification request to <%s>.", jid_utf8);
+  } else if (!strcasecmp(subcmd, "request_unsubscribe"))  {
+    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE);
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Sent presence notification unsubscription request to <%s>.",
+                 jid_utf8);
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+  }
+
+  // Only free jid_utf8 if it has been allocated, i.e. if != arg.
+  if (jid_utf8 && jid_utf8 != arg)
+    g_free(jid_utf8);
+do_authorization_return:
+  free_arg_lst(paramlst);
+}
+
+static void do_version(char *arg)
+{
+  gchar *ver = mcabber_version();
+  scr_LogPrint(LPRINT_NORMAL, "This is mcabber version %s.", ver);
+  g_free(ver);
+}
+
+static void do_request(char *arg)
+{
+  char **paramlst;
+  char *fjid, *type;
+  enum iqreq_type numtype = iqreq_none;
+  char *jid_utf8 = NULL;
+
+  paramlst = split_arg(arg, 2, 0); // type, jid
+  type = *paramlst;
+  fjid = *(paramlst+1);
+
+  if (type) {
+    // Quick check...
+    if (!strcasecmp(type, "version"))
+      numtype = iqreq_version;
+    else if (!strcasecmp(type, "time"))
+      numtype = iqreq_time;
+    else if (!strcasecmp(type, "last"))
+      numtype = iqreq_last;
+    else if (!strcasecmp(type, "vcard"))
+      numtype = iqreq_vcard;
+  }
+
+  if (!type || !numtype) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Please specify a query type (version, time...).");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  // Allow special jid "" or "." (current buddy)
+  if (fjid && (!*fjid || !strcmp(fjid, ".")))
+    fjid = NULL;
+
+  if (fjid) {
+    // The JID has been specified.  Quick check...
+    if (check_jid_syntax(fjid)) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "<%s> is not a valid Jabber ID.", fjid);
+      fjid = NULL;
+    } else {
+      // Convert jid to lowercase
+      char *p;
+      for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
+        *p = tolower(*p);
+      fjid = jid_utf8 = to_utf8(fjid);
+    }
+  } else {
+    // Add the current buddy
+    if (current_buddy)
+      fjid = (char*)buddy_getjid(BUDDATA(current_buddy));
+    if (!fjid)
+      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+  }
+
+  if (fjid) {
+    switch (numtype) {
+      case iqreq_version:
+      case iqreq_time:
+      case iqreq_last:
+      case iqreq_vcard:
+          xmpp_request(fjid, numtype);
+          break;
+      default:
+          break;
+    }
+  }
+  g_free(jid_utf8);
+  free_arg_lst(paramlst);
+}
+
+static void do_event(char *arg)
+{
+  char **paramlst;
+  char *evid, *subcmd;
+  int action = -1;
+  GSList *evidlst;
+
+  paramlst = split_arg(arg, 2, 0); // id, subcmd
+  evid = *paramlst;
+  subcmd = *(paramlst+1);
+
+  if (!evid || !subcmd) {
+    // Special case: /event list
+    if (evid && !strcasecmp(evid, "list"))
+      evs_display_list();
+    else
+      scr_LogPrint(LPRINT_NORMAL,
+                   "Missing parameter.  Usage: /event num action");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(subcmd, "reject"))
+    action = 0;
+  else if (!strcasecmp(subcmd, "accept"))
+    action = 1;
+  else if (!strcasecmp(subcmd, "ignore"))
+    action = 2;
+
+  if (action == -1) {
+    scr_LogPrint(LPRINT_NORMAL, "Wrong action parameter.");
+  } else if (action >= 0 && action <= 2) {
+    GSList *p;
+
+    if (action == 2) {
+      action = EVS_CONTEXT_CANCEL;
+    } else {
+      action += EVS_CONTEXT_USER;
+    }
+
+    if (!strcmp(evid, "*")) {
+      // Use completion list
+      evidlst = evs_geteventslist(FALSE);
+    } else {
+      // Let's create a slist with the provided event id
+      evidlst = g_slist_append(NULL, g_strdup(evid));
+    }
+    for (p = evidlst; p; p = g_slist_next(p)) {
+      if (evs_callback(p->data, action) == -1) {
+        scr_LogPrint(LPRINT_NORMAL, "Event %s not found.", p->data);
+      }
+      g_free(p->data);
+    }
+    g_slist_free(evidlst);
+  }
+
+  free_arg_lst(paramlst);
+}
+
+static void do_pgp(char *arg)
+{
+  char **paramlst;
+  char *fjid, *subcmd, *keyid;
+  enum {
+    pgp_none,
+    pgp_enable,
+    pgp_disable,
+    pgp_setkey,
+    pgp_force,
+    pgp_info
+  } op = 0;
+  int force = FALSE;
+
+  paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key]
+  subcmd = *paramlst;
+  fjid = *(paramlst+1);
+  keyid = *(paramlst+2);
+
+  if (!subcmd)
+    fjid = NULL;
+  if (!fjid)
+    keyid = NULL;
+
+  if (subcmd) {
+    if (!strcasecmp(subcmd, "enable"))
+      op = pgp_enable;
+    else if (!strcasecmp(subcmd, "disable"))
+      op = pgp_disable;
+    else if (!strcasecmp(subcmd, "setkey"))
+      op = pgp_setkey;
+    else if ((!strcasecmp(subcmd, "force")) ||
+             (!strcasecmp(subcmd, "+force"))) {
+      op = pgp_force;
+      force = TRUE;
+    } else if (!strcasecmp(subcmd, "-force"))
+      op = pgp_force;
+    else if (!strcasecmp(subcmd, "info"))
+      op = pgp_info;
+  }
+
+  if (!op) {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  // Allow special jid "" or "." (current buddy)
+  if (fjid && (!*fjid || !strcmp(fjid, ".")))
+    fjid = NULL;
+
+  if (fjid) {
+    // The JID has been specified.  Quick check...
+    if (check_jid_syntax(fjid) || !strchr(fjid, '@')) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "<%s> is not a valid Jabber ID.", fjid);
+      fjid = NULL;
+    } else {
+      // Convert jid to lowercase and strip resource
+      char *p;
+      for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
+        *p = tolower(*p);
+      if (*p == JID_RESOURCE_SEPARATOR)
+        *p = '\0';
+    }
+  } else {
+    gpointer bud = NULL;
+    if (current_buddy)
+      bud = BUDDATA(current_buddy);
+    if (bud) {
+      guint type = buddy_gettype(bud);
+      if (type & ROSTER_TYPE_USER)  // Is it a user?
+        fjid = (char*)buddy_getjid(bud);
+      else
+        scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user.");
+    }
+  }
+
+  if (fjid) { // fjid is actually a bare jid...
+    guint disabled;
+    GString *sbuf;
+    switch (op) {
+      case pgp_enable:
+      case pgp_disable:
+          settings_pgp_setdisabled(fjid, (op == pgp_disable ? TRUE : FALSE));
+          break;
+      case pgp_force:
+          settings_pgp_setforce(fjid, force);
+          break;
+      case pgp_setkey:
+          settings_pgp_setkeyid(fjid, keyid);
+          break;
+      case pgp_info:
+          sbuf = g_string_new("");
+          if (settings_pgp_getkeyid(fjid)) {
+            g_string_printf(sbuf, "PGP Encryption key id: %s",
+                            settings_pgp_getkeyid(fjid));
+            scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
+          }
+          disabled = settings_pgp_getdisabled(fjid);
+          g_string_printf(sbuf, "PGP encryption is %s",
+                          (disabled ?  "disabled" : "enabled"));
+          scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
+          if (!disabled && settings_pgp_getforce(fjid)) {
+            scr_WriteIncomingMessage(fjid,
+                                     "Encryption enforced (no negotiation)",
+                                     0, HBB_PREFIX_INFO, 0);
+          }
+          g_string_free(sbuf, TRUE);
+          break;
+      default:
+          break;
+    }
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
+  }
+
+  free_arg_lst(paramlst);
+}
+
+static void do_otr(char *arg)
+{
+#ifdef HAVE_LIBOTR
+  char **paramlst;
+  char *fjid, *subcmd, *keyid;
+  enum {
+    otr_none,
+    otr_start,
+    otr_stop,
+    otr_fpr,
+    otr_smpq,
+    otr_smpr,
+    otr_smpa,
+    otr_k,
+    otr_info
+  } op = 0;
+
+  if (!otr_enabled()) {
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Warning: OTR hasn't been enabled -- command ignored.");
+    return;
+  }
+
+  paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key]
+  subcmd = *paramlst;
+  fjid = *(paramlst+1);
+  keyid = *(paramlst+2);
+
+  if (!subcmd)
+    fjid = NULL;
+  if (!fjid)
+    keyid = NULL;
+
+  if (subcmd) {
+    if (!strcasecmp(subcmd, "start"))
+      op = otr_start;
+    else if (!strcasecmp(subcmd, "stop"))
+      op = otr_stop;
+    else if (!strcasecmp(subcmd, "fingerprint"))
+      op = otr_fpr;
+    else if (!strcasecmp(subcmd, "smpq"))
+      op = otr_smpq;
+    else if (!strcasecmp(subcmd, "smpr"))
+      op = otr_smpr;
+    else if (!strcasecmp(subcmd, "smpa"))
+      op = otr_smpa;
+    else if (!strcasecmp(subcmd, "key"))
+      op = otr_k;
+    else if (!strcasecmp(subcmd, "info"))
+      op = otr_info;
+  }
+
+  if (!op) {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (op == otr_k)
+    otr_key();
+  else {
+    // Allow special jid "" or "." (current buddy)
+    if (fjid && (!*fjid || !strcmp(fjid, ".")))
+      fjid = NULL;
+
+    if (fjid) {
+      // The JID has been specified.  Quick check...
+      if (check_jid_syntax(fjid) || !strchr(fjid, '@')) {
+        scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                     "<%s> is not a valid Jabber ID.", fjid);
+        fjid = NULL;
+      } else {
+        // Convert jid to lowercase and strip resource
+        char *p;
+        for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
+          *p = tolower(*p);
+        if (*p == JID_RESOURCE_SEPARATOR)
+          *p = '\0';
+      }
+    } else {
+      gpointer bud = NULL;
+      if (current_buddy)
+        bud = BUDDATA(current_buddy);
+      if (bud) {
+        guint type = buddy_gettype(bud);
+        if (type & ROSTER_TYPE_USER)  // Is it a user?
+          fjid = (char*)buddy_getjid(bud);
+        else
+          scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user.");
+      }
+    }
+
+    if (fjid) { // fjid is actually a bare jid...
+      switch (op) {
+        case otr_start:
+          otr_establish(fjid);          break;
+        case otr_stop:
+          otr_disconnect(fjid);         break;
+        case otr_fpr:
+          otr_fingerprint(fjid, keyid); break;
+        case otr_smpq:
+          otr_smp_query(fjid, keyid);   break;
+        case otr_smpr:
+          otr_smp_respond(fjid, keyid); break;
+        case otr_smpa:
+          otr_smp_abort(fjid);          break;
+        case otr_info:
+          otr_print_info(fjid);         break;
+        default:
+          break;
+      }
+    } else
+      scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
+  }
+  free_arg_lst(paramlst);
+
+#else
+  scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled.");
+#endif /* HAVE_LIBOTR */
+}
+
+#ifdef HAVE_LIBOTR
+static char *string_for_otrpolicy(enum otr_policy p)
+{
+  switch (p) {
+    case plain:         return "plain";
+    case opportunistic: return "opportunistic";
+    case manual:        return "manual";
+    case always:        return "always";
+    default:            return "unknown";
+  }
+}
+
+static void dump_otrpolicy(char *k, char *v, void *nothing)
+{
+  scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "otrpolicy for %s: %s", k,
+               string_for_otrpolicy(*(enum otr_policy*)v));
+}
+#endif
+
+static void do_otrpolicy(char *arg)
+{
+#ifdef HAVE_LIBOTR
+  char **paramlst;
+  char *fjid, *policy;
+  enum otr_policy p;
+
+  paramlst = split_arg(arg, 2, 0); // [jid|default] policy
+  fjid = *paramlst;
+  policy = *(paramlst+1);
+
+  if (!fjid && !policy) {
+    scr_LogPrint(LPRINT_NORMAL, "default otrpolicy: %s",
+                 string_for_otrpolicy(settings_otr_getpolicy(NULL)));
+    settings_foreach(SETTINGS_TYPE_OTR, &dump_otrpolicy, NULL);
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!policy) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Please call otrpolicy correctly: /otrpolicy (default|jid) "
+                 "(plain|manual|opportunistic|always)");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(policy, "plain"))
+    p = plain;
+  else if (!strcasecmp(policy, "manual"))
+    p = manual;
+  else if (!strcasecmp(policy, "opportunistic"))
+    p = opportunistic;
+  else if (!strcasecmp(policy, "always"))
+    p = always;
+  else {
+    /* Fail, we don't know _this_ policy*/
+    scr_LogPrint(LPRINT_NORMAL, "mcabber doesn't support _this_ policy!");
+    free_arg_lst(paramlst);
+    return;
+  }
+
+  if (!strcasecmp(fjid, "default") || !strcasecmp(fjid, "*")) {
+    /*set default policy*/
+    settings_otr_setpolicy(NULL, p);
+    free_arg_lst(paramlst);
+    return;
+  }
+  // Allow special jid "" or "." (current buddy)
+  if (fjid && (!*fjid || !strcmp(fjid, ".")))
+    fjid = NULL;
+
+  if (fjid) {
+    // The JID has been specified.  Quick check...
+    if (check_jid_syntax(fjid) || !strchr(fjid, '@')) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+                   "<%s> is not a valid Jabber ID.", fjid);
+      fjid = NULL;
+    } else {
+      // Convert jid to lowercase and strip resource
+      char *p;
+      for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
+        *p = tolower(*p);
+      if (*p == JID_RESOURCE_SEPARATOR)
+        *p = '\0';
+    }
+  } else {
+    gpointer bud = NULL;
+    if (current_buddy)
+      bud = BUDDATA(current_buddy);
+    if (bud) {
+      guint type = buddy_gettype(bud);
+      if (type & ROSTER_TYPE_USER)  // Is it a user?
+        fjid = (char*)buddy_getjid(bud);
+      else
+        scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user.");
+    }
+  }
+
+  if (fjid)
+    settings_otr_setpolicy(fjid, p);
+  else
+    scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
+
+  free_arg_lst(paramlst);
+#else
+  scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled.");
+#endif /* HAVE_LIBOTR */
+}
+
+/* !!!
+  After changing the /iline arguments names here, you must change ones
+  in init_bindings().
+*/
+static void do_iline(char *arg)
+{
+  if (!strcasecmp(arg, "fword")) {
+    readline_forward_word();
+  } else if (!strcasecmp(arg, "bword")) {
+    readline_backward_word();
+  } else if (!strcasecmp(arg, "word_fdel")) {
+    readline_forward_kill_word();
+  } else if (!strcasecmp(arg, "word_bdel")) {
+    readline_backward_kill_word();
+  } else if (!strcasecmp(arg, "word_upcase")) {
+    readline_updowncase_word(1);
+  } else if (!strcasecmp(arg, "word_downcase")) {
+    readline_updowncase_word(0);
+  } else if (!strcasecmp(arg, "word_capit")) {
+    readline_capitalize_word();
+  } else if (!strcasecmp(arg, "fchar")) {
+    readline_forward_char();
+  } else if (!strcasecmp(arg, "bchar")) {
+    readline_backward_char();
+  } else if (!strcasecmp(arg, "char_fdel")) {
+    readline_forward_kill_char();
+  } else if (!strcasecmp(arg, "char_bdel")) {
+    readline_backward_kill_char();
+  } else if (!strcasecmp(arg, "char_swap")) {
+    readline_transpose_chars();
+  } else if (!strcasecmp(arg, "hist_beginning_search_bwd")) {
+    readline_hist_beginning_search_bwd();
+  } else if (!strcasecmp(arg, "hist_beginning_search_fwd")) {
+    readline_hist_beginning_search_fwd();
+  } else if (!strcasecmp(arg, "hist_prev")) {
+    readline_hist_prev();
+  } else if (!strcasecmp(arg, "hist_next")) {
+    readline_hist_next();
+  } else if (!strcasecmp(arg, "iline_start")) {
+    readline_iline_start();
+  } else if (!strcasecmp(arg, "iline_end")) {
+    readline_iline_end();
+  } else if (!strcasecmp(arg, "iline_fdel")) {
+    readline_forward_kill_iline();
+  } else if (!strcasecmp(arg, "iline_bdel")) {
+    readline_backward_kill_iline();
+  } else if (!strcasecmp(arg, "send_multiline")) {
+    readline_send_multiline();
+  } else if (!strcasecmp(arg, "iline_accept")) {
+    retval_for_cmds = readline_accept_line(FALSE);
+  } else if (!strcasecmp(arg, "iline_accept_down_hist")) {
+    retval_for_cmds = readline_accept_line(TRUE);
+  } else if (!strcasecmp(arg, "compl_cancel")) {
+    readline_cancel_completion();
+  } else if (!strcasecmp(arg, "compl_do")) {
+    readline_do_completion();
+  }
+}
+
+static void do_screen_refresh(char *arg)
+{
+  readline_refresh_screen();
+}
+
+static void do_chat_disable(char *arg)
+{
+  guint show_roster;
+
+  if (arg && !strcasecmp(arg, "--show-roster"))
+    show_roster = 1;
+  else
+    show_roster = 0;
+
+  readline_disable_chat_mode(show_roster);
+}
+
+static void do_source(char *arg)
+{
+  static int recur_level;
+  gchar *filename, *expfname;
+  if (!*arg) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing filename.");
+    return;
+  }
+  if (recur_level > 20) {
+    scr_LogPrint(LPRINT_LOGNORM, "** Too many source commands!");
+    return;
+  }
+  filename = g_strdup(arg);
+  strip_arg_special_chars(filename);
+  expfname = expand_filename(filename);
+  recur_level++;
+  cfg_read_file(expfname, FALSE);
+  recur_level--;
+  g_free(filename);
+  g_free(expfname);
+}
+
+static void do_connect(char *arg)
+{
+  xmpp_connect();
+}
+
+static void do_disconnect(char *arg)
+{
+  xmpp_disconnect();
+}
+
+static void do_help(char *arg)
+{
+  help_process(arg);
+}
+
+static void do_echo(char *arg)
+{
+  if (arg)
+    scr_print_logwindow(arg);
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/commands.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,36 @@
+#ifndef __MCABBER_COMMANDS_H__
+#define __MCABBER_COMMANDS_H__ 1
+
+#include <glib.h>
+
+#include <mcabber/config.h>
+
+// Command structure
+typedef struct {
+  char name[32];
+  const char *help;
+  guint completion_flags[2];
+  void (*func)(char *);
+#ifdef MODULES_ENABLE
+  gpointer userdata;
+#endif
+} cmd;
+
+void cmd_init(void);
+cmd *cmd_get(const char *command);
+int  process_line(const char *line);
+int  process_command(const char *line, guint iscmd);
+char *expandalias(const char *line);
+#ifdef MODULES_ENABLE
+void cmd_deinit(void);
+gpointer cmd_del(const char *name);
+void cmd_add(const char *name, const char *help, guint flags1, guint flags2, void (*f)(char*), gpointer userdata);
+#endif
+
+void cmd_room_whois(gpointer bud, char *nick_locale, guint interactive);
+void cmd_room_leave(gpointer bud, char *arg);
+void cmd_setstatus(const char *recipient, const char *arg);
+
+#endif /* __MCABBER_COMMANDS_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/compl.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,286 @@
+/*
+ * compl.c      -- Completion system
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/*  Usage, basically:
+ * - new_completion();      // 1.   Initialization
+ * - complete();            // 2.   1st completion
+ * - cancel_completion();   // 3a.  2nd completion / cancel previous
+ * - complete();            // 3b.  2nd completion / complete
+ *   ...
+ * - done_completion();     // n.   finished -- free allocated areas
+ *
+ */
+
+#include <string.h>
+
+#include "compl.h"
+#include "utf8.h"
+#include "roster.h"
+#include "events.h"
+
+// Completion structure
+typedef struct {
+  GSList *list;         // list of matches
+  guint len_prefix;     // length of text already typed by the user
+  guint len_compl;      // length of the last completion
+  GSList *next;         // pointer to next completion to try
+} compl;
+
+// Category structure
+typedef struct {
+  guint flag;
+  GSList *words;
+} category;
+
+static GSList *Categories;
+static compl *InputCompl;
+
+#ifdef MODULES_ENABLE
+guint registered_cats = COMPL_CMD|COMPL_JID|COMPL_URLJID|COMPL_NAME| \
+                        COMPL_STATUS|COMPL_FILENAME|COMPL_ROSTER|COMPL_BUFFER| \
+                        COMPL_GROUP|COMPL_GROUPNAME|COMPL_MULTILINE|COMPL_ROOM| \
+                        COMPL_RESOURCE|COMPL_AUTH|COMPL_REQUEST|COMPL_EVENTS| \
+                        COMPL_EVENTSID|COMPL_PGP|COMPL_COLOR| \
+                        COMPL_OTR|COMPL_OTRPOLICY| \
+                        0;
+
+//  compl_new_category()
+// Reserves id for new completion category.
+// Returns 0, if no more categories can be allocated.
+// Note, that user should not make any assumptions about id nature,
+// as it is likely to change in future.
+guint compl_new_category (void)
+{
+  guint i = 0;
+  while ((registered_cats >> i) & 1)
+    i++;
+  if (i >= sizeof (guint)*8)
+    return 0;
+  else {
+    guint id = 1 << i;
+    registered_cats |= id;
+    return id;
+  }
+}
+
+//  compl_del_category (id)
+// Frees reserved id for category.
+// Note, that for now it not validates its input, so, be careful
+// and specify exactly what you get from compl_new_category.
+void compl_del_category (guint id)
+{
+  registered_cats &= ~id;
+}
+#endif
+
+//  new_completion(prefix, compl_cat)
+// . prefix    = beginning of the word, typed by the user
+// . compl_cat = pointer to a completion category list (list of *char)
+// Set the InputCompl pointer to an allocated compl structure.
+// done_completion() must be called when finished.
+// Returns the number of possible completions.
+guint new_completion(char *prefix, GSList *compl_cat)
+{
+  compl *c;
+  GSList *sl_cat;
+  size_t len = strlen(prefix);
+
+  if (InputCompl) { // This should not happen, but hey...
+    cancel_completion();
+  }
+
+  c = g_new0(compl, 1);
+  // Build the list of matches
+  for (sl_cat = compl_cat; sl_cat; sl_cat = g_slist_next(sl_cat)) {
+    char *word = sl_cat->data;
+    if (!strncasecmp(prefix, word, len)) {
+      if (strlen(word) != len)
+        c->list = g_slist_append(c->list, g_strdup(word+len)); // TODO sort
+    }
+  }
+  c->next = c->list;
+  InputCompl = c;
+  return g_slist_length(c->list);
+}
+
+//  done_completion();
+void done_completion(void)
+{
+  GSList *clp;
+
+  if (!InputCompl)  return;
+
+  // Free the current completion list
+  for (clp = InputCompl->list; clp; clp = g_slist_next(clp))
+    g_free(clp->data);
+  g_slist_free(InputCompl->list);
+  g_free(InputCompl);
+  InputCompl = NULL;
+}
+
+//  cancel_completion()
+// Returns the number of chars to delete to cancel the completion
+//guint cancel_completion(compl *c)
+guint cancel_completion(void)
+{
+  if (!InputCompl)  return 0;
+  return InputCompl->len_compl;
+}
+
+// Returns pointer to text to insert, NULL if no completion.
+const char *complete()
+{
+  compl* c = InputCompl;
+  char *r;
+
+  if (!InputCompl)  return NULL;
+
+  if (!c->next) {
+    c->next = c->list;  // back to the beginning
+    c->len_compl = 0;
+    return NULL;
+  }
+  r = (char*)c->next->data;
+  c->next = g_slist_next(c->next);
+  if (!utf8_mode) {
+    c->len_compl = strlen(r);
+  } else {
+    char *wc;
+    c->len_compl = 0;
+    for (wc = r; *wc; wc = next_char(wc))
+      c->len_compl++;
+  }
+  return r;
+}
+
+
+/* Categories functions */
+
+//  compl_add_category_word(categ, command)
+// Adds a keyword as a possible completion in category categ.
+void compl_add_category_word(guint categ, const char *word)
+{
+  GSList *sl_cat;
+  category *cat;
+  char *nword;
+  // Look for category
+  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
+    if (categ == ((category*)sl_cat->data)->flag)
+      break;
+  }
+  if (!sl_cat) {   // Category not found, let's create it
+    cat = g_new0(category, 1);
+    cat->flag = categ;
+    Categories = g_slist_append(Categories, cat);
+  } else
+    cat = (category*)sl_cat->data;
+
+  // If word is not space-terminated, we add one trailing space
+  for (nword = (char*)word; *nword; nword++)
+    ;
+  if (nword > word) nword--;
+  if (*nword != ' ') {  // Add a space
+    nword = g_strdup_printf("%s ", word);
+  } else {              // word is fine
+    nword = g_strdup(word);
+  }
+
+  // TODO Check word does not already exist
+  cat->words = g_slist_append(cat->words, nword); // TODO sort
+}
+
+//  compl_del_category_word(categ, command)
+// Removes a keyword from category categ in completion list.
+void compl_del_category_word(guint categ, const char *word)
+{
+  GSList *sl_cat, *sl_elt;
+  category *cat;
+  char *nword;
+  // Look for category
+  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
+    if (categ == ((category*)sl_cat->data)->flag)
+      break;
+  }
+  if (!sl_cat) return;   // Category not found, finished!
+
+  cat = (category*)sl_cat->data;
+
+  // If word is not space-terminated, we add one trailing space
+  for (nword = (char*)word; *nword; nword++)
+    ;
+  if (nword > word) nword--;
+  if (*nword != ' ') {  // Add a space
+    nword = g_strdup_printf("%s ", word);
+  } else {              // word is fine
+    nword = g_strdup(word);
+  }
+
+  sl_elt = cat->words;
+  while (sl_elt) {
+    if (!strcasecmp((char*)sl_elt->data, nword)) {
+      g_free(sl_elt->data);
+      cat->words = g_slist_delete_link(cat->words, sl_elt);
+      break; // Only remove first occurence
+    }
+    sl_elt = g_slist_next(sl_elt);
+  }
+}
+
+//  compl_get_category_list()
+// Returns a slist of all words in the categories specified by the given flags
+// Iff this function sets *dynlist to TRUE, then the caller must free the
+// whole list after use.
+GSList *compl_get_category_list(guint cat_flags, guint *dynlist)
+{
+  GSList *sl_cat;
+
+  *dynlist = FALSE;
+
+  // Look for category
+  // XXX Actually that's not that simple... cat_flags can be a combination
+  // of several flags!
+  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
+    if (cat_flags == ((category*)sl_cat->data)->flag)
+      break;
+  }
+  if (sl_cat)       // Category was found, easy...
+    return ((category*)sl_cat->data)->words;
+
+  // Handle dynamic SLists
+  *dynlist = TRUE;
+  if (cat_flags == COMPL_GROUPNAME) {
+    return compl_list(ROSTER_TYPE_GROUP);
+  }
+  if (cat_flags == COMPL_JID) {
+    return compl_list(ROSTER_TYPE_USER);
+  }
+  if (cat_flags == COMPL_RESOURCE) {
+    return buddy_getresources_locale(NULL);
+  }
+  if (cat_flags == COMPL_EVENTSID) {
+    return evs_geteventslist(TRUE);
+  }
+
+  *dynlist = FALSE;
+  return NULL;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/compl.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,47 @@
+#ifndef __MCABBER_COMPL_H__
+#define __MCABBER_COMPL_H__ 1
+
+#include <glib.h>
+
+#include <mcabber/config.h>
+
+#define COMPL_CMD         (1U<<0)
+#define COMPL_JID         (1U<<1)
+#define COMPL_URLJID      (1U<<2)   // Not implemented yet
+#define COMPL_NAME        (1U<<3)   // Not implemented yet
+#define COMPL_STATUS      (1U<<4)
+#define COMPL_FILENAME    (1U<<5)   // Not implemented yet
+#define COMPL_ROSTER      (1U<<6)
+#define COMPL_BUFFER      (1U<<7)
+#define COMPL_GROUP       (1U<<8)
+#define COMPL_GROUPNAME   (1U<<9)
+#define COMPL_MULTILINE   (1U<<10)
+#define COMPL_ROOM        (1U<<11)
+#define COMPL_RESOURCE    (1U<<12)
+#define COMPL_AUTH        (1U<<13)
+#define COMPL_REQUEST     (1U<<14)
+#define COMPL_EVENTS      (1U<<15)
+#define COMPL_EVENTSID    (1U<<16)
+#define COMPL_PGP         (1U<<17)
+#define COMPL_COLOR       (1U<<18)
+#define COMPL_OTR         (1U<<19)
+#define COMPL_OTRPOLICY   (1U<<20)
+#ifdef MODULES_ENABLE
+#define COMPL_MAX_BUILTIN (1U<<20)
+
+guint compl_new_category (void);
+void  compl_del_category (guint id);
+#endif
+
+void    compl_add_category_word(guint, const char *command);
+void    compl_del_category_word(guint categ, const char *word);
+GSList *compl_get_category_list(guint cat_flags, guint *dynlist);
+
+guint   new_completion(char *prefix, GSList *compl_cat);
+void    done_completion(void);
+guint   cancel_completion(void);
+const char *complete(void);
+
+#endif /* __MCABBER_COMPL_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/events.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,180 @@
+/*
+ * events.c     -- Events fonctions
+ *
+ * Copyright (C) 2006-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib.h>
+#include <string.h>
+#include "events.h"
+#include "logprint.h"
+
+static GSList *evs_list; // Events list
+
+static eviqs *evs_find(const char *evid);
+
+//  evs_new(type, timeout)
+// Create an events structure.
+eviqs *evs_new(guint8 type, time_t timeout)
+{
+  static guint evs_idn;
+  eviqs *new_evs;
+  time_t now_t;
+  char *stridn;
+
+  if (!++evs_idn)
+    evs_idn = 1;
+  /* Check for wrapping, we shouldn't reuse ids */
+  stridn = g_strdup_printf("%d", evs_idn);
+  if (evs_find(stridn))  {
+    g_free(stridn);
+    // We could try another id but for now giving up should be fine...
+    return NULL;
+  }
+
+  new_evs = g_new0(eviqs, 1);
+  time(&now_t);
+  new_evs->ts_create = now_t;
+  if (timeout)
+    new_evs->ts_expire = now_t + timeout;
+  new_evs->type = type;
+  new_evs->id = stridn;
+
+  if(!g_slist_length(evs_list))
+    g_timeout_add_seconds(20, evs_check_timeout, NULL);
+  evs_list = g_slist_append(evs_list, new_evs);
+  return new_evs;
+}
+
+int evs_del(const char *evid)
+{
+  GSList *p;
+  eviqs *i;
+
+  if (!evid) return 1;
+
+  for (p = evs_list; p; p = g_slist_next(p)) {
+    i = p->data;
+    if (!strcmp(evid, i->id))
+      break;
+  }
+  if (p) {
+    g_free(i->id);
+    g_free(i->data);
+    g_free(i->desc);
+    g_free(i);
+    evs_list = g_slist_remove(evs_list, p->data);
+    return 0; // Ok, deleted
+  }
+  return -1;  // Not found
+}
+
+static eviqs *evs_find(const char *evid)
+{
+  GSList *p;
+  eviqs *i;
+
+  if (!evid) return NULL;
+
+  for (p = evs_list; p; p = g_slist_next(p)) {
+    i = p->data;
+    if (!strcmp(evid, i->id))
+      return i;
+  }
+  return NULL;
+}
+
+//  evs_callback(evid, evcontext)
+// Callback processing for the specified event.
+// Return 0 in case of success, -1 if the evid hasn't been found.
+int evs_callback(const char *evid, guint evcontext)
+{
+  eviqs *i;
+
+  i = evs_find(evid);
+  if (!i) return -1;
+
+  // IQ processing
+  // Note: If xml_result is NULL, this is a timeout
+  if (i->callback)
+    (void)(*i->callback)(i, evcontext);
+
+  evs_del(evid);
+  return 0;
+}
+
+gboolean evs_check_timeout()
+{
+  time_t now_t;
+  GSList *p;
+  eviqs *i;
+
+  time(&now_t);
+  p = evs_list;
+  if (!p)
+    return FALSE;
+  while (p) {
+    i = p->data;
+    // We must get next IQ eviqs element now because the current one
+    // could be freed.
+    p = g_slist_next(p);
+
+    if ((!i->ts_expire && now_t > i->ts_create + EVS_MAX_TIMEOUT) ||
+        (i->ts_expire && now_t > i->ts_expire)) {
+      evs_callback(i->id, EVS_CONTEXT_TIMEOUT);
+    }
+  }
+  return TRUE;
+}
+
+void evs_display_list(void)
+{
+  GSList *p;
+  eviqs *i;
+
+  scr_LogPrint(LPRINT_LOGNORM, "Events list:");
+  for (p = evs_list; p; p = g_slist_next(p)) {
+    i = p->data;
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Id: %-3s %s", i->id, (i->desc ? i->desc : ""));
+  }
+  scr_LogPrint(LPRINT_LOGNORM, "End of events list.");
+}
+
+//  evs_geteventslist(bool comp)
+// Return a singly-linked-list of events ids, for the completion system.
+// If comp is true, the string "list" is added (it's a completion argument).
+// Note: the caller should free the list (and data) after use.
+GSList *evs_geteventslist(int compl)
+{
+  GSList *evidlist = NULL, *p;
+  eviqs *i;
+
+  for (p = evs_list; p; p = g_slist_next(p)) {
+    i = p->data;
+    evidlist = g_slist_append(evidlist, g_strdup(i->id));
+  }
+
+  if (compl) {
+    // Last item is the "list" subcommand.
+    evidlist = g_slist_append(evidlist, g_strdup("list"));
+  }
+  return evidlist;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/events.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,48 @@
+#ifndef __MCABBER_EVENTS_H__
+#define __MCABBER_EVENTS_H__ 1
+
+#include <mcabber/config.h>
+
+#define EVS_DEFAULT_TIMEOUT 90
+#define EVS_MAX_TIMEOUT     432000
+
+#define EVS_CONTEXT_TIMEOUT 0U
+#define EVS_CONTEXT_CANCEL  1U
+#define EVS_CONTEXT_USER    2U
+
+typedef enum {
+  EVS_TYPE_SUBSCRIPTION = 1,
+  EVS_TYPE_INVITATION = 2,
+#ifdef MODULES_ENABLE
+  EVS_TYPE_USER = 3,
+#endif
+} evs_type;
+
+/* Common structure for events (evs) and IQ requests (iqs) */
+typedef struct {
+  char *id;
+  time_t ts_create;
+  time_t ts_expire;
+  guint8 type;
+  gpointer data;
+  int (*callback)();
+  char *desc;
+} eviqs;
+
+typedef struct {
+  char* to;
+  char* from;
+  char* passwd;
+  char* reason;
+} event_muc_invitation;
+
+eviqs   *evs_new(guint8 type, time_t timeout);
+int      evs_del(const char *evid);
+int      evs_callback(const char *evid, guint evcontext);
+gboolean evs_check_timeout();
+void     evs_display_list(void);
+GSList  *evs_geteventslist(int forcompl);
+
+#endif /* __MCABBER_EVENTS_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/fifo.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,178 @@
+/*
+ * fifo.c       -- Read commands from a named pipe
+ *
+ * Copyright (C) 2008,2009 Mikael Berthe <mikael@lilotux.net>
+ * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <stdlib.h>
+#include <glib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "commands.h"
+#include "logprint.h"
+#include "utils.h"
+#include "settings.h"
+#include "main.h"
+
+static char *fifo_name = NULL;
+static GIOChannel *fifo_channel = NULL;
+
+static const char *FIFO_ENV_NAME = "MCABBER_FIFO";
+
+static gboolean attach_fifo(const char *name);
+
+static guint fifo_callback(GIOChannel *channel,
+                           GIOCondition condition,
+                           gpointer data)
+{
+  if (condition & (G_IO_IN|G_IO_PRI)) {
+    GIOStatus  chstat;
+    gchar     *buf;
+    gsize      endpos;
+
+    chstat = g_io_channel_read_line(channel, &buf, NULL, &endpos, NULL);
+    if (chstat == G_IO_STATUS_ERROR || chstat == G_IO_STATUS_EOF) {
+      if (!attach_fifo(fifo_name))
+        scr_LogPrint(LPRINT_LOGNORM,
+                     "Reopening fifo failed! Fifo will not work from now!");
+      return FALSE;
+    }
+    if (buf) {
+      guint logflag;
+      guint fifo_ignore = settings_opt_get_int("fifo_ignore");
+
+      if (endpos)
+        buf[endpos] = '\0';
+
+      if (settings_opt_get_int("fifo_hide_commands"))
+        logflag = LPRINT_LOG;
+      else
+        logflag = LPRINT_LOGNORM;
+      scr_LogPrint(logflag, "%s FIFO command: %s",
+                   (fifo_ignore ? "Ignoring" : "Executing"), buf);
+      if (!fifo_ignore) {
+        if (process_command(buf, TRUE) == 255)
+          mcabber_set_terminate_ui();
+      }
+
+      g_free(buf);
+    }
+  } else if (condition & (G_IO_ERR|G_IO_NVAL|G_IO_HUP)) {
+    if (!attach_fifo(fifo_name))
+      scr_LogPrint(LPRINT_LOGNORM,
+                   "Reopening fifo failed! Fifo will not work from now!");
+    return FALSE;
+  }
+  return TRUE;
+}
+
+static void fifo_destroy_callback(gpointer data)
+{
+  GIOChannel *channel = (GIOChannel *)data;
+  g_io_channel_unref(channel);
+}
+
+static gboolean check_fifo(const char *name)
+{
+  struct stat finfo;
+  if (stat(name, &finfo) == -1) {
+    /* some unknown error */
+    if (errno != ENOENT)
+      return FALSE;
+    /* fifo not yet exists */
+    if (mkfifo(name, S_IRUSR|S_IWUSR) != -1)
+      return check_fifo(name);
+    else
+      return FALSE;
+  }
+
+  /* file exists */
+  if (S_ISFIFO(finfo.st_mode))
+    return TRUE;
+  else
+    return FALSE;
+}
+
+static gboolean attach_fifo(const char *name)
+{
+  GSource *source;
+  int fd = open (name, O_RDONLY|O_NONBLOCK);
+  if (fd == -1)
+    return FALSE;
+
+  fifo_channel = g_io_channel_unix_new(fd);
+
+  g_io_channel_set_flags(fifo_channel, G_IO_FLAG_NONBLOCK, NULL);
+  g_io_channel_set_encoding(fifo_channel, NULL, NULL);
+  g_io_channel_set_close_on_unref(fifo_channel, TRUE);
+
+  source = g_io_create_watch(fifo_channel,
+                             G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL);
+  g_source_set_callback(source, (GSourceFunc)fifo_callback,
+                        (gpointer)fifo_channel,
+                        (GDestroyNotify)fifo_destroy_callback);
+  g_source_attach(source, main_context);
+
+  return TRUE;
+}
+
+int fifo_init(const char *fifo_path)
+{
+  if (fifo_path) {
+    fifo_name = expand_filename(fifo_path);
+
+    if (!check_fifo(fifo_name)) {
+      scr_LogPrint(LPRINT_LOGNORM, "WARNING: Cannot create the FIFO. "
+                   "%s already exists and is not a pipe", fifo_name);
+      g_free(fifo_name);
+      return -1;
+    }
+  } else if (fifo_name)
+    g_source_remove_by_user_data(fifo_channel);
+  else
+    return -1;
+
+  if (!attach_fifo(fifo_name)) {
+    scr_LogPrint(LPRINT_LOGNORM, "Error: Cannot open fifo");
+    return -1;
+  }
+
+  setenv(FIFO_ENV_NAME, fifo_name, 1);
+
+  scr_LogPrint(LPRINT_LOGNORM, "FIFO initialized (%s)", fifo_path);
+  return 1;
+}
+
+void fifo_deinit(void)
+{
+  unsetenv(FIFO_ENV_NAME);
+
+  /* destroy open fifo */
+  unlink(fifo_name);
+  g_source_remove_by_user_data(fifo_channel);
+  /* channel itself should be destroyed by destruction callback */
+  g_free(fifo_name);
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/fifo.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,9 @@
+#ifndef __MCABBER_FIFO_H__
+#define __MCABBER_FIFO_H__ 1
+
+int  fifo_init(const char *fifo_path);
+void fifo_deinit(void);
+
+#endif /* __MCABBER_FIFO_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/hbuf.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,516 @@
+/*
+ * hbuf.c       -- History buffer implementation
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "hbuf.h"
+#include "utils.h"
+#include "utf8.h"
+#include "screen.h"
+
+
+/* This is a private structure type */
+
+typedef struct {
+  char *ptr;
+  char *ptr_end;        // beginning of the block
+  char *ptr_end_alloc;  // end of the current persistent block
+  guchar flags;
+
+  // XXX This should certainly be a pointer, and be allocated only when needed
+  // (for ex. when HBB_FLAG_PERSISTENT is set).
+  struct { // hbuf_line_info
+    time_t timestamp;
+    unsigned mucnicklen;
+    guint  flags;
+    gpointer xep184;
+  } prefix;
+} hbuf_block;
+
+
+//  do_wrap(p_hbuf, first_hbuf_elt, width)
+// Wrap hbuf lines with the specified width.
+// '\n' are handled by this routine (they are removed and persistent lines
+// are created).
+// All hbuf elements are processed, starting from first_hbuf_elt.
+static inline void do_wrap(GList **p_hbuf, GList *first_hbuf_elt,
+                           unsigned int width)
+{
+  GList *curr_elt = first_hbuf_elt;
+
+  // Let's add non-persistent blocs if necessary
+  // - If there are '\n' in the string
+  // - If length > width (and width != 0)
+  while (curr_elt) {
+    hbuf_block *hbuf_b_curr, *hbuf_b_prev;
+    char *c, *end;
+    char *br = NULL; // break pointer
+    char *cr = NULL; // CR pointer
+    unsigned int cur_w = 0;
+
+    // We want to break where we can find a space char or a CR
+
+    hbuf_b_curr = (hbuf_block*)(curr_elt->data);
+    hbuf_b_prev = hbuf_b_curr;
+    c = hbuf_b_curr->ptr;
+
+    while (*c && (!width || cur_w <= width)) {
+      if (*c == '\n') {
+        br = cr = c;
+        *c = 0;
+        break;
+      }
+      if (iswblank(get_char(c)))
+        br = c;
+      cur_w += get_char_width(c);
+      c = next_char(c);
+    }
+
+    if (cr || (*c && cur_w > width)) {
+      if (!br || br == hbuf_b_curr->ptr)
+        br = c;
+      else
+        br = next_char(br);
+      end = hbuf_b_curr->ptr_end;
+      hbuf_b_curr->ptr_end = br;
+      // Create another block
+      hbuf_b_curr = g_new0(hbuf_block, 1);
+      // The block must be persistent after a CR
+      if (cr) {
+        hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end + 1; // == cr+1
+        hbuf_b_curr->flags  = HBB_FLAG_PERSISTENT;
+      } else {
+        hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end; // == br
+        hbuf_b_curr->flags    = 0;
+      }
+      hbuf_b_curr->ptr_end  = end;
+      hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
+      // This is OK because insert_before(NULL) == append():
+      *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
+    }
+    curr_elt = g_list_next(curr_elt);
+  }
+}
+
+//  hbuf_add_line(p_hbuf, text, prefix_flags, width, maxhbufblocks)
+// Add a line to the given buffer.  If width is not null, then lines are
+// wrapped at this length.
+// maxhbufblocks is the maximum number of hbuf blocks we can allocate.  If
+// null, there is no limit.  If non-null, it should be >= 2.
+//
+// Note 1: Splitting according to width won't work if there are tabs; they
+//         should be expanded before.
+// Note 2: width does not include the ending \0.
+void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp,
+        guint prefix_flags, guint width, guint maxhbufblocks,
+        unsigned mucnicklen, gpointer xep184)
+{
+  GList *curr_elt;
+  char *line;
+  guint hbb_blocksize, textlen;
+  hbuf_block *hbuf_block_elt;
+
+  if (!text) return;
+
+  prefix_flags |= (xep184 ? HBB_PREFIX_RECEIPT : 0);
+
+  textlen = strlen(text);
+  hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE);
+
+  hbuf_block_elt = g_new0(hbuf_block, 1);
+  hbuf_block_elt->prefix.timestamp  = timestamp;
+  hbuf_block_elt->prefix.flags      = prefix_flags;
+  hbuf_block_elt->prefix.mucnicklen = mucnicklen;
+  hbuf_block_elt->prefix.xep184     = xep184;
+  if (!*p_hbuf) {
+    hbuf_block_elt->ptr  = g_new(char, hbb_blocksize);
+    if (!hbuf_block_elt->ptr) {
+      g_free(hbuf_block_elt);
+      return;
+    }
+    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
+    hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
+  } else {
+    hbuf_block *hbuf_b_prev;
+    // Set p_hbuf to the end of the list, to speed up history loading
+    // (or CPU time will be used by g_list_last() for each line)
+    *p_hbuf = g_list_last(*p_hbuf);
+    hbuf_b_prev = (*p_hbuf)->data;
+    hbuf_block_elt->ptr    = hbuf_b_prev->ptr_end;
+    hbuf_block_elt->flags  = HBB_FLAG_PERSISTENT;
+    hbuf_block_elt->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
+  }
+  *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
+
+  if (hbuf_block_elt->ptr + textlen >= hbuf_block_elt->ptr_end_alloc) {
+    // Too long for the current allocated bloc, we need another one
+    if (!maxhbufblocks || textlen >= HBB_BLOCKSIZE) {
+      // No limit, let's allocate a new block
+      // If the message text is big, we won't bother to reuse an old block
+      // as well (it could be too small and cause a segfault).
+      hbuf_block_elt->ptr  = g_new0(char, hbb_blocksize);
+      hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
+      // XXX We should check the return value.
+    } else {
+      GList *hbuf_head, *hbuf_elt;
+      hbuf_block *hbuf_b_elt;
+      guint n = 0;
+      hbuf_head = g_list_first(*p_hbuf);
+      // We need at least 2 allocated blocks
+      if (maxhbufblocks == 1)
+        maxhbufblocks = 2;
+      // Let's count the number of allocated areas
+      for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
+        hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
+        if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
+          n++;
+      }
+      // If we can't allocate a new area, reuse the previous block(s)
+      if (n < maxhbufblocks) {
+        hbuf_block_elt->ptr  = g_new0(char, hbb_blocksize);
+        hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
+      } else {
+        // Let's use an old block, and free the extra blocks if needed
+        char *allocated_block = NULL;
+        char *end_of_allocated_block = NULL;
+        while (n >= maxhbufblocks) {
+          int start_of_block = 1;
+          for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = hbuf_head) {
+            hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
+            if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
+              if (start_of_block-- == 0)
+                break;
+              if (n == maxhbufblocks) {
+                allocated_block = hbuf_b_elt->ptr;
+                end_of_allocated_block = hbuf_b_elt->ptr_end_alloc;
+              } else {
+                g_free(hbuf_b_elt->ptr);
+              }
+            }
+            g_free(hbuf_b_elt);
+            hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt);
+          }
+          n--;
+        }
+        memset(allocated_block, 0, end_of_allocated_block-allocated_block);
+        hbuf_block_elt->ptr = allocated_block;
+        hbuf_block_elt->ptr_end_alloc = end_of_allocated_block;
+      }
+    }
+    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
+  }
+
+  line = hbuf_block_elt->ptr;
+  // Ok, now we can copy the text..
+  strcpy(line, text);
+  hbuf_block_elt->ptr_end = line + textlen + 1;
+
+  curr_elt = g_list_last(*p_hbuf);
+
+  // Wrap lines and handle CRs ('\n')
+  do_wrap(p_hbuf, curr_elt, width);
+}
+
+//  hbuf_free()
+// Destroys all hbuf list.
+void hbuf_free(GList **p_hbuf)
+{
+  hbuf_block *hbuf_b_elt;
+  GList *hbuf_elt;
+  GList *first_elt = g_list_first(*p_hbuf);
+
+  for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
+    hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
+    if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
+      g_free(hbuf_b_elt->ptr);
+    }
+    g_free(hbuf_b_elt);
+  }
+
+  g_list_free(first_elt);
+  *p_hbuf = NULL;
+}
+
+//  hbuf_rebuild()
+// Rebuild all hbuf list, with the new width.
+// If width == 0, lines are not wrapped.
+void hbuf_rebuild(GList **p_hbuf, unsigned int width)
+{
+  GList *first_elt, *curr_elt, *next_elt;
+  hbuf_block *hbuf_b_curr, *hbuf_b_next;
+
+  // *p_hbuf needs to be the head of the list
+  first_elt = *p_hbuf = g_list_first(*p_hbuf);
+
+  // #1 Remove non-persistent blocks (ptr_end should be updated!)
+  curr_elt = first_elt;
+  while (curr_elt) {
+    next_elt = g_list_next(curr_elt);
+    // Last element?
+    if (!next_elt)
+      break;
+    hbuf_b_curr = (hbuf_block*)(curr_elt->data);
+    hbuf_b_next = (hbuf_block*)(next_elt->data);
+    // Is next line not-persistent?
+    if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
+      hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
+      g_free(hbuf_b_next);
+      curr_elt = g_list_delete_link(curr_elt, next_elt);
+    } else
+      curr_elt = next_elt;
+  }
+  // #2 Go back to head and create non-persistent blocks when needed
+  if (width)
+    do_wrap(p_hbuf, first_elt, width);
+}
+
+//  hbuf_previous_persistent()
+// Returns the previous persistent block (line).  If the given line is
+// persistent, then it is returned.
+// This function is used for example when resizing a buffer.  If the top of the
+// screen is on a non-persistent block, then a screen resize could destroy this
+// line...
+GList *hbuf_previous_persistent(GList *l_line)
+{
+  hbuf_block *hbuf_b_elt;
+
+  while (l_line) {
+    hbuf_b_elt = (hbuf_block*)l_line->data;
+    if (hbuf_b_elt->flags & HBB_FLAG_PERSISTENT)
+      return l_line;
+    l_line = g_list_previous(l_line);
+  }
+
+  return NULL;
+}
+
+//  hbuf_get_lines(hbuf, n)
+// Returns an array of n hbb_line pointers
+// (The first line will be the line currently pointed by hbuf)
+// Note: The caller should free the array, the hbb_line pointers and the
+// text pointers after use.
+hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n)
+{
+  unsigned int i;
+  hbuf_block *blk;
+  guint last_persist_prefixflags = 0;
+  GList *last_persist;  // last persistent flags
+  hbb_line **array, **array_elt;
+
+  // To be able to correctly highlight multi-line messages,
+  // we need to look at the last non-null prefix, which should be the first
+  // line of the message.
+  last_persist = hbuf_previous_persistent(hbuf);
+  while (last_persist) {
+    blk = (hbuf_block*)last_persist->data;
+    if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
+      last_persist_prefixflags = blk->prefix.flags;
+      break;
+    }
+    last_persist = g_list_previous(last_persist);
+  }
+
+  array = g_new0(hbb_line*, n);
+  array_elt = array;
+
+  for (i = 0 ; i < n ; i++) {
+    if (hbuf) {
+      int maxlen;
+
+      blk = (hbuf_block*)(hbuf->data);
+      maxlen = blk->ptr_end - blk->ptr;
+      *array_elt = (hbb_line*)g_new(hbb_line, 1);
+      (*array_elt)->timestamp  = blk->prefix.timestamp;
+      (*array_elt)->flags      = blk->prefix.flags;
+      (*array_elt)->mucnicklen = blk->prefix.mucnicklen;
+      (*array_elt)->text       = g_strndup(blk->ptr, maxlen);
+
+      if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
+        last_persist_prefixflags = blk->prefix.flags;
+      } else {
+        // Propagate highlighting flags
+        (*array_elt)->flags |= last_persist_prefixflags &
+                               (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
+                                HBB_PREFIX_INFO | HBB_PREFIX_IN);
+        // Continuation of a message - omit the prefix
+        (*array_elt)->flags |= HBB_PREFIX_CONT;
+        (*array_elt)->mucnicklen = 0; // The nick is in the first one
+      }
+
+      hbuf = g_list_next(hbuf);
+    } else
+      break;
+
+    array_elt++;
+  }
+
+  return array;
+}
+
+//  hbuf_search(hbuf, direction, string)
+// Look backward/forward for a line containing string in the history buffer
+// Search starts at hbuf, and goes forward if direction == 1, backward if -1
+GList *hbuf_search(GList *hbuf, int direction, const char *string)
+{
+  hbuf_block *blk;
+
+  for (;;) {
+    if (direction > 0)
+      hbuf = g_list_next(hbuf);
+    else
+      hbuf = g_list_previous(hbuf);
+
+    if (!hbuf) break;
+
+    blk = (hbuf_block*)(hbuf->data);
+    // XXX blk->ptr is (maybe) not really correct, because the match should
+    // not be after ptr_end.  We should check that...
+    if (strcasestr(blk->ptr, string))
+      break;
+  }
+
+  return hbuf;
+}
+
+//  hbuf_jump_date(hbuf, t)
+// Return a pointer to the first line after date t in the history buffer
+GList *hbuf_jump_date(GList *hbuf, time_t t)
+{
+  hbuf_block *blk;
+
+  hbuf = g_list_first(hbuf);
+
+  for ( ; hbuf && g_list_next(hbuf); hbuf = g_list_next(hbuf)) {
+    blk = (hbuf_block*)(hbuf->data);
+    if (blk->prefix.timestamp >= t) break;
+  }
+
+  return hbuf;
+}
+
+//  hbuf_jump_percent(hbuf, pc)
+// Return a pointer to the line at % pc of the history buffer
+GList *hbuf_jump_percent(GList *hbuf, int pc)
+{
+  guint hlen;
+
+  hbuf = g_list_first(hbuf);
+  hlen = g_list_length(hbuf);
+
+  return g_list_nth(hbuf, pc*hlen/100);
+}
+
+//  hbuf_dump_to_file(hbuf, filename)
+// Save the buffer to a file.
+void hbuf_dump_to_file(GList *hbuf, const char *filename)
+{
+  hbuf_block *blk;
+  hbb_line line;
+  guint last_persist_prefixflags = 0;
+  guint prefixwidth;
+  char pref[96];
+  FILE *fp;
+  struct stat statbuf;
+
+  if (!stat(filename, &statbuf)) {
+    scr_LogPrint(LPRINT_NORMAL, "The file already exists.");
+    return;
+  }
+  fp = fopen(filename, "w");
+  if (!fp) {
+    scr_LogPrint(LPRINT_NORMAL, "Unable to open the file.");
+    return;
+  }
+
+  prefixwidth = scr_getprefixwidth();
+  prefixwidth = MIN(prefixwidth, sizeof pref);
+
+  for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
+    int maxlen;
+
+    blk = (hbuf_block*)(hbuf->data);
+    maxlen = blk->ptr_end - blk->ptr;
+
+    memset(&line, 0, sizeof(line));
+    line.timestamp  = blk->prefix.timestamp;
+    line.flags      = blk->prefix.flags;
+    line.mucnicklen = blk->prefix.mucnicklen;
+    line.text       = g_strndup(blk->ptr, maxlen);
+
+    if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
+      last_persist_prefixflags = blk->prefix.flags;
+    } else {
+      // Propagate highlighting flags
+      line.flags |= last_persist_prefixflags &
+                    (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
+                     HBB_PREFIX_INFO | HBB_PREFIX_IN);
+      // Continuation of a message - omit the prefix
+      line.flags |= HBB_PREFIX_CONT;
+      line.mucnicklen = 0; // The nick is in the first one
+    }
+
+    scr_line_prefix(&line, pref, prefixwidth);
+    fprintf(fp, "%s%s\n", pref, line.text);
+  }
+
+  fclose(fp);
+  return;
+}
+
+//  hbuf_remove_receipt(hbuf, xep184)
+// Remove the Receipt Flag for the message with the given xep184 id
+// Returns TRUE if it was found and removed, otherwise FALSE
+gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184)
+{
+  hbuf_block *blk;
+
+  hbuf = g_list_first(hbuf);
+
+  for ( ; hbuf; hbuf = g_list_next(hbuf)) {
+    blk = (hbuf_block*)(hbuf->data);
+    if (blk->prefix.xep184 == xep184) {
+      blk->prefix.xep184 = NULL;
+      blk->prefix.flags ^= HBB_PREFIX_RECEIPT;
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+//  hbuf_get_blocks_number()
+// Returns the number of allocated hbuf_block's.
+guint hbuf_get_blocks_number(GList *hbuf)
+{
+  hbuf_block *hbuf_b_elt;
+  guint count = 0U;
+
+  for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
+    hbuf_b_elt = (hbuf_block*)(hbuf->data);
+    if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
+      count++;
+  }
+  return count;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/hbuf.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,59 @@
+#ifndef __MCABBER_HBUF_H__
+#define __MCABBER_HBUF_H__ 1
+
+#include <time.h>
+#include <glib.h>
+
+// With current implementation a message must fit in a hbuf block,
+// so we shouldn't choose a too small size.
+#define HBB_BLOCKSIZE   8192    // > 20 please
+
+// Flags:
+// - ALLOC: the ptr data has been allocated, it can be freed
+// - PERSISTENT: this is a new history line
+#define HBB_FLAG_ALLOC      1
+#define HBB_FLAG_PERSISTENT 2
+
+#define HBB_PREFIX_IN         (1U<<0)
+#define HBB_PREFIX_OUT        (1U<<1)
+#define HBB_PREFIX_STATUS     (1U<<2)
+#define HBB_PREFIX_AUTH       (1U<<3)
+#define HBB_PREFIX_INFO       (1U<<4)
+#define HBB_PREFIX_ERR        (1U<<5)
+#define HBB_PREFIX_NOFLAG     (1U<<6)
+#define HBB_PREFIX_HLIGHT_OUT (1U<<7)
+#define HBB_PREFIX_HLIGHT     (1U<<8)
+#define HBB_PREFIX_NONE       (1U<<9)
+#define HBB_PREFIX_SPECIAL    (1U<<10)
+#define HBB_PREFIX_PGPCRYPT   (1U<<11)
+#define HBB_PREFIX_OTRCRYPT   (1U<<12)
+#define HBB_PREFIX_CONT       (1U<<13)
+#define HBB_PREFIX_RECEIPT    (1U<<14)
+
+typedef struct {
+  time_t timestamp;
+  guint flags;
+  unsigned mucnicklen;
+  char *text;
+} hbb_line;
+
+void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp,
+        guint prefix_flags, guint width, guint maxhbufblocks,
+        unsigned mucnicklen, gpointer xep184);
+void hbuf_free(GList **p_hbuf);
+void hbuf_rebuild(GList **p_hbuf, unsigned int width);
+GList *hbuf_previous_persistent(GList *l_line);
+
+hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n);
+GList *hbuf_search(GList *hbuf, int direction, const char *string);
+GList *hbuf_jump_date(GList *hbuf, time_t t);
+GList *hbuf_jump_percent(GList *hbuf, int pc);
+gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184);
+
+void hbuf_dump_to_file(GList *hbuf, const char *filename);
+
+guint hbuf_get_blocks_number(GList *p_hbuf);
+
+#endif /* __MCABBER_HBUF_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/help.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,135 @@
+/*
+ * help.c       -- Help command
+ *
+ * Copyright (C) 2006-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <glib.h>
+
+#include "settings.h"
+#include "logprint.h"
+#include "utils.h"
+#include "screen.h"
+
+#define DEFAULT_LANG "en"
+
+//  get_lang()
+// Return the language code string (a 2-letters string).
+static const char *get_lang(void) {
+  static const char *lang_str = DEFAULT_LANG;
+#ifdef DATA_DIR
+  static char lang[3];
+  const char *opt_l;
+  opt_l = settings_opt_get("lang");
+  if (opt_l && strlen(opt_l) == 2 && isalpha(opt_l[0]) && isalpha(opt_l[1])) {
+    strncpy(lang, opt_l, sizeof(lang));
+    mc_strtolower(lang);
+    lang_str = lang;
+  }
+#endif /* DATA_DIR */
+  return lang_str;
+}
+
+//  help_process(string)
+// Display help about the "string" command.
+// If string is null, display general help.
+// Return 0 in case of success.
+int help_process(char *string)
+{
+#ifndef DATA_DIR
+  scr_LogPrint(LPRINT_NORMAL, "Help isn't available.");
+  return -1;
+#else
+  const char *lang;
+  FILE *fp;
+  char *helpfiles_dir, *filename;
+  char *data;
+  const int datasize = 4096;
+  int linecount = 0;
+  char *p;
+
+  // Check string is ok
+  for (p = string; p && *p; p++) {
+    if (!isalnum(*p) && *p != '_' && *p != '-') {
+      scr_LogPrint(LPRINT_NORMAL, "Cannot find help (invalid keyword).");
+      return 1;
+    }
+  }
+
+  // Look for help file
+  lang = get_lang();
+  helpfiles_dir = g_strdup_printf("%s/mcabber/help", DATA_DIR);
+  p = NULL;
+
+  if (string && *string) {
+    p = g_strdup(string);
+    mc_strtolower(p);
+    filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, lang, p);
+  } else
+    filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, lang);
+
+  fp = fopen(filename, "r");
+
+  if (!(fp) && (g_strcmp0(lang, DEFAULT_LANG)) ) {
+    g_free(filename);
+    if (p)
+      filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, DEFAULT_LANG, p);
+    else
+      filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, DEFAULT_LANG);
+
+    fp = fopen(filename, "r");
+  }
+  g_free(p);
+  g_free(filename);
+  g_free(helpfiles_dir);
+
+  if (!fp) {
+    scr_LogPrint(LPRINT_NORMAL, "No help found.");
+    return -1;
+  }
+
+  data = g_new(char, datasize);
+  while (!feof(fp)) {
+    if (fgets(data, datasize, fp) == NULL) break;
+    // Strip trailing newline
+    for (p = data; *p; p++) ;
+    if (p > data)
+      p--;
+    if (*p == '\n' || *p == '\r')
+      *p = '\0';
+    // Displaty the help line
+    scr_LogPrint(LPRINT_NORMAL, "%s", data);
+    linecount++;
+  }
+  fclose(fp);
+  g_free(data);
+
+  if (linecount) {
+    scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
+    update_roster = TRUE;
+  }
+
+  return 0;
+#endif /* DATA_DIR */
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/help.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,8 @@
+#ifndef __MCABBER_HELP_H__
+#define __MCABBER_HELP_H__ 1
+
+int help_process(char *string);
+
+#endif /* __MCABBER_HELP_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/histolog.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,536 @@
+/*
+ * histolog.c   -- File history handling
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "histolog.h"
+#include "hbuf.h"
+#include "utils.h"
+#include "screen.h"
+#include "settings.h"
+#include "utils.h"
+#include "roster.h"
+#include "xmpp.h"
+
+static guint UseFileLogging;
+static guint FileLoadLogs;
+static char *RootDir;
+
+
+//  user_histo_file(jid)
+// Returns history filename for the given jid
+// Note: the caller *must* free the filename after use (if not null).
+static char *user_histo_file(const char *bjid)
+{
+  char *filename;
+  char *lowerid;
+
+  if (!(UseFileLogging || FileLoadLogs))
+    return NULL;
+
+  lowerid = g_strdup(bjid);
+  if (!lowerid)
+    return NULL;
+  mc_strtolower(lowerid);
+
+  filename = g_strdup_printf("%s%s", RootDir, lowerid);
+  g_free(lowerid);
+  return filename;
+}
+
+char *hlog_get_log_jid(const char *bjid)
+{
+  struct stat bufstat;
+  char *path;
+  char *log_jid = NULL;
+
+  path = user_histo_file(bjid);
+  while (path) {
+    if (lstat(path, &bufstat) != 0)
+      break;
+    if (S_ISLNK(bufstat.st_mode)) {
+      g_free(log_jid);
+      log_jid = g_new0(char, bufstat.st_size+1);
+      if (readlink(path, log_jid, bufstat.st_size) < 0) return NULL;
+      g_free(path);
+      path = user_histo_file(log_jid);
+    } else
+      break;
+  }
+
+  g_free(path);
+  return log_jid;
+}
+
+//  write_histo_line()
+// Adds a history (multi-)line to the jid's history logfile
+static void write_histo_line(const char *bjid,
+        time_t timestamp, guchar type, guchar info, const char *data)
+{
+  guint len = 0;
+  FILE *fp;
+  time_t ts;
+  const char *p;
+  char *filename;
+  char str_ts[20];
+  int err;
+
+  if (!UseFileLogging)
+    return;
+
+  // Do not log status messages when 'logging_ignore_status' is set
+  if (type == 'S' && settings_opt_get_int("logging_ignore_status"))
+    return;
+
+  filename = user_histo_file(bjid);
+
+  // If timestamp is null, get current date
+  if (timestamp)
+    ts = timestamp;
+  else
+    time(&ts);
+
+  if (!data)
+    data = "";
+
+  // Count number of extra lines
+  for (p=data ; *p ; p++)
+    if (*p == '\n') len++;
+
+  /* Line format: "TI yyyymmddThh:mm:ssZ LLL [data]"
+   * T=Type, I=Info, yyyymmddThh:mm:ssZ=date, LLL=0-padded-len
+   *
+   * Types:
+   * - M message    Info: S (send) R (receive) I (info)
+   * - S status     Info: [_ofdnai]
+   * We don't check them, we trust the caller.
+   * (Info messages are not sent nor received, they're generated
+   * locally by mcabber.)
+   */
+
+  fp = fopen(filename, "a");
+  g_free(filename);
+  if (!fp) {
+    scr_LogPrint(LPRINT_LOGNORM, "Unable to write history "
+                 "(cannot open logfile)");
+    return;
+  }
+
+  to_iso8601(str_ts, ts);
+  err = fprintf(fp, "%c%c %-18.18s %03d %s\n", type, info, str_ts, len, data);
+  fclose(fp);
+  if (err < 0) {
+    scr_LogPrint(LPRINT_LOGNORM, "Error while writing to log file: %s",
+                 strerror(errno));
+  }
+}
+
+//  hlog_read_history()
+// Reads the jid's history logfile
+void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width)
+{
+  char *filename;
+  guchar type, info;
+  char *data, *tail;
+  guint data_size;
+  char *xtext;
+  time_t timestamp;
+  guint prefix_flags;
+  guint len;
+  FILE *fp;
+  struct stat bufstat;
+  guint err = 0;
+  guint ln = 0; // line number
+  time_t starttime;
+  int max_num_of_blocks;
+
+  if (!FileLoadLogs)
+    return;
+
+  if ((roster_gettype(bjid) & ROSTER_TYPE_ROOM) &&
+      (settings_opt_get_int("load_muc_logs") != 1))
+    return;
+
+  data_size = HBB_BLOCKSIZE+32;
+  data = g_new(char, data_size);
+  if (!data) {
+    scr_LogPrint(LPRINT_LOGNORM, "Not enough memory to read history file");
+    return;
+  }
+
+  filename = user_histo_file(bjid);
+
+  fp = fopen(filename, "r");
+  g_free(filename);
+  if (!fp) {
+    g_free(data);
+    return;
+  }
+
+  // If file is large (> 3MB here), display a message to inform the user
+  // (it can take a while...)
+  if (!fstat(fileno(fp), &bufstat)) {
+    if (bufstat.st_size > 3145728) {
+      scr_LogPrint(LPRINT_NORMAL, "Reading <%s> history file...", bjid);
+      scr_DoUpdate();
+    }
+  }
+
+  max_num_of_blocks = get_max_history_blocks();
+
+  starttime = 0L;
+  if (settings_opt_get_int("max_history_age") > 0) {
+    int maxdays = settings_opt_get_int("max_history_age");
+    time(&starttime);
+    if (maxdays >= starttime/86400L)
+      starttime = 0L;
+    else
+      starttime -= maxdays * 86400L;
+  }
+
+  /* See write_histo_line() for line format... */
+  while (!feof(fp)) {
+    guint dataoffset = 25;
+    guint noeol;
+
+    if (fgets(data, data_size-1, fp) == NULL)
+      break;
+    ln++;
+
+    while (1) {
+      for (tail = data; *tail; tail++) ;
+      noeol = (*(tail-1) != '\n');
+      if (!noeol)
+        break;
+      /* TODO: duplicated code... could do better... */
+      if (tail == data + data_size-2) {
+        // The buffer is too small to contain the whole line.
+        // Let's allocate some more space.
+        if (!max_num_of_blocks ||
+            data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) {
+          guint toffset = tail - data;
+          // Allocate one more block.
+          data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE);
+          data = g_renew(char, data, data_size);
+          // Update the tail pointer, as the data may have been moved.
+          tail = data + toffset;
+          if (fgets(tail, data_size-1 - (tail-data), fp) == NULL)
+            break;
+        } else {
+          scr_LogPrint(LPRINT_LOGNORM, "Line too long in history file!");
+          ln--;
+          break;
+        }
+      }
+    }
+
+    type = data[0];
+    info = data[1];
+
+    if ((type != 'M' && type != 'S') ||
+        ((data[11] != 'T') || (data[20] != 'Z') ||
+         (data[21] != ' ') ||
+         (data[25] != ' ' && data[26] != ' '))) {
+      if (!err) {
+        scr_LogPrint(LPRINT_LOGNORM,
+                     "Error in history file format (%s), l.%u", bjid, ln);
+        err = 1;
+      }
+      continue;
+    }
+    // The number of lines can be written with 3 or 4 bytes.
+    if (data[25] != ' ') dataoffset = 26;
+    data[21] = data[dataoffset] = 0;
+    timestamp = from_iso8601(&data[3], 1);
+    len = (guint) atoi(&data[22]);
+
+    // Some checks
+    if (((type == 'M') && (info != 'S' && info != 'R' && info != 'I')) ||
+        ((type == 'S') && (!strchr("_OFDNAI", info)))) {
+      if (!err) {
+        scr_LogPrint(LPRINT_LOGNORM, "Error in history file format (%s), l.%u",
+                     bjid, ln);
+        err = 1;
+      }
+      continue;
+    }
+
+    while (len--) {
+      ln++;
+      if (fgets(tail, data_size-1 - (tail-data), fp) == NULL)
+        break;
+
+      while (*tail) tail++;
+      noeol = (*(tail-1) != '\n');
+      if (tail == data + data_size-2 && (len || noeol)) {
+        // The buffer is too small to contain the whole message.
+        // Let's allocate some more space.
+        if (!max_num_of_blocks ||
+            data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) {
+          guint toffset = tail - data;
+          // If the line hasn't been read completely and we reallocate the
+          // buffer, we want to read one more time.
+          if (noeol)
+            len++;
+          // Allocate one more block.
+          data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE);
+          data = g_renew(char, data, data_size);
+          // Update the tail pointer, as the data may have been moved.
+          tail = data + toffset;
+        } else {
+          // There will probably be a parse error on next read, because
+          // this message hasn't been read entirely.
+          scr_LogPrint(LPRINT_LOGNORM, "Message too big in history file!");
+        }
+      }
+    }
+    // Remove last CR (we keep it if the line is empty, too)
+    if ((tail > data+dataoffset+1) && (*(tail-1) == '\n'))
+      *(tail-1) = 0;
+
+    // Check if the data is older than max_history_age
+    if (starttime) {
+      if (timestamp > starttime)
+        starttime = 0L; // From now on, load everything
+      else
+        continue;
+    }
+
+    if (type == 'M') {
+      char *converted;
+      if (info == 'S') {
+        prefix_flags = HBB_PREFIX_OUT | HBB_PREFIX_HLIGHT_OUT;
+      } else {
+        prefix_flags = HBB_PREFIX_IN;
+        if (info == 'I')
+          prefix_flags = HBB_PREFIX_INFO;
+      }
+      converted = from_utf8(&data[dataoffset+1]);
+      if (converted) {
+        xtext = ut_expand_tabs(converted); // Expand tabs
+        hbuf_add_line(p_buddyhbuf, xtext, timestamp, prefix_flags, width,
+                      max_num_of_blocks, 0, NULL);
+        if (xtext != converted)
+          g_free(xtext);
+        g_free(converted);
+      }
+      err = 0;
+    }
+  }
+  fclose(fp);
+  g_free(data);
+}
+
+//  hlog_enable()
+// Enable logging to files.  If root_dir is NULL, then $HOME/.mcabber is used.
+// If loadfiles is TRUE, we will try to load buddies history logs from file.
+void hlog_enable(guint enable, const char *root_dir, guint loadfiles)
+{
+  UseFileLogging = enable;
+  FileLoadLogs = loadfiles;
+
+  if (enable || loadfiles) {
+    if (root_dir) {
+      char *xp_root_dir;
+      int l = strlen(root_dir);
+      if (l < 1) {
+        scr_LogPrint(LPRINT_LOGNORM, "Error: logging dir name too short");
+        UseFileLogging = FileLoadLogs = FALSE;
+        return;
+      }
+      xp_root_dir = expand_filename(root_dir);
+      // RootDir must be slash-terminated
+      if (root_dir[l-1] == '/') {
+        RootDir = xp_root_dir;
+      } else {
+        RootDir = g_strdup_printf("%s/", xp_root_dir);
+        g_free(xp_root_dir);
+      }
+    } else {
+      char *home = getenv("HOME");
+      const char *dir = "/.mcabber/histo/";
+      RootDir = g_strdup_printf("%s%s", home, dir);
+    }
+    // Check directory permissions (should not be readable by group/others)
+    if (checkset_perm(RootDir, TRUE) == -1) {
+      // The directory does not actually exists
+      g_free(RootDir);
+      RootDir = NULL;
+      scr_LogPrint(LPRINT_LOGNORM, "ERROR: Cannot access "
+                   "history log directory, logging DISABLED");
+      UseFileLogging = FileLoadLogs = FALSE;
+    }
+  } else {  // Disable history logging
+    g_free(RootDir);
+    RootDir = NULL;
+  }
+}
+
+guint hlog_is_enabled(void)
+{
+  return UseFileLogging;
+}
+
+inline void hlog_write_message(const char *bjid, time_t timestamp, int sent,
+        const char *msg)
+{
+  guchar info;
+  /* sent=1   message sent by mcabber
+   * sent=0   message received by mcabber
+   * sent=-1  local info message
+   */
+  if (sent == 1)
+    info = 'S';
+  else if (sent == 0)
+    info = 'R';
+  else
+    info = 'I';
+  write_histo_line(bjid, timestamp, 'M', info, msg);
+}
+
+inline void hlog_write_status(const char *bjid, time_t timestamp,
+        enum imstatus status, const char *status_msg)
+{
+  // XXX Check status value?
+  write_histo_line(bjid, timestamp, 'S', toupper(imstatus2char[status]),
+          status_msg);
+}
+
+
+//  hlog_save_state()
+// If enabled, save the current state of the roster
+// (i.e. pending messages) to a temporary file.
+void hlog_save_state(void)
+{
+  gpointer unread_ptr, first_unread;
+  const char *bjid;
+  char *statefile_xp;
+  FILE *fp;
+  const char *statefile = settings_opt_get("statefile");
+
+  if (!statefile || !UseFileLogging)
+    return;
+
+  statefile_xp = expand_filename(statefile);
+  fp = fopen(statefile_xp, "w");
+  if (!fp) {
+    scr_LogPrint(LPRINT_NORMAL, "Cannot open state file [%s]",
+                 strerror(errno));
+    goto hlog_save_state_return;
+  }
+
+  if (!lm_connection_is_authenticated(lconnection)) {
+    // We're not connected.  Let's use the unread_jids hash.
+    GList *unread_jid = unread_jid_get_list();
+    unread_ptr = unread_jid;
+    for ( ; unread_jid ; unread_jid = g_list_next(unread_jid))
+      fprintf(fp, "%s\n", (char*)unread_jid->data);
+    g_list_free(unread_ptr);
+    goto hlog_save_state_return;
+  }
+
+  if (!current_buddy) // Safety check -- shouldn't happen.
+    goto hlog_save_state_return;
+
+  // We're connected.  Let's use unread_msg().
+  unread_ptr = first_unread = unread_msg(NULL);
+  if (!first_unread)
+    goto hlog_save_state_return;
+
+  do {
+    guint type = buddy_gettype(unread_ptr);
+    if (type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) {
+      bjid = buddy_getjid(unread_ptr);
+      if (bjid)
+        fprintf(fp, "%s\n", bjid);
+    }
+    unread_ptr = unread_msg(unread_ptr);
+  } while (unread_ptr && unread_ptr != first_unread);
+
+hlog_save_state_return:
+  if (fp) {
+    long filelen = ftell(fp);
+    fclose(fp);
+    if (!filelen)
+      unlink(statefile_xp);
+  }
+  g_free(statefile_xp);
+}
+
+//  hlog_load_state()
+// If enabled, load the current state of the roster
+// (i.e. pending messages) from a temporary file.
+// This function adds the JIDs to the unread_jids hash table,
+// so it should only be called at startup.
+void hlog_load_state(void)
+{
+  char bjid[1024];
+  char *statefile_xp;
+  FILE *fp;
+  const char *statefile = settings_opt_get("statefile");
+
+  if (!statefile || !UseFileLogging)
+    return;
+
+  statefile_xp = expand_filename(statefile);
+  fp = fopen(statefile_xp, "r");
+  if (fp) {
+    char *eol;
+    while (!feof(fp)) {
+      if (fgets(bjid, sizeof bjid, fp) == NULL)
+        break;
+      // Let's remove the trailing newline.
+      // Also remove whitespace, if the file as been (badly) manually modified.
+      for (eol = bjid; *eol; eol++) ;
+      for (eol--; eol >= bjid && (*eol == '\n' || *eol == ' '); *eol-- = 0) ;
+      // Safety checks...
+      if (!bjid[0])
+        continue;
+      if (check_jid_syntax(bjid)) {
+        scr_LogPrint(LPRINT_LOGNORM,
+                     "ERROR: Invalid JID in state file.  Corrupted file?");
+        break;
+      }
+      // Display a warning if there are pending messages but the user
+      // won't see them because load_log isn't set.
+      if (!FileLoadLogs) {
+        scr_LogPrint(LPRINT_LOGNORM, "WARNING: unread message from <%s>.",
+                     bjid);
+        scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
+      }
+      // Add the JID to unread_jids.  It will be used when the contact is
+      // added to the roster.
+      unread_jid_add(bjid);
+    }
+    fclose(fp);
+  }
+  g_free(statefile_xp);
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/histolog.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,20 @@
+#ifndef __MCABBER_HISTOLOG_H__
+#define __MCABBER_HISTOLOG_H__ 1
+
+#include <glib.h>
+
+#include <mcabber/xmpp.h>
+
+void hlog_enable(guint enable, const char *root_dir, guint loadfile);
+char *hlog_get_log_jid(const char *bjid);
+void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width);
+void hlog_write_message(const char *bjid, time_t timestamp, int sent,
+                        const char *msg);
+void hlog_write_status(const char *bjid, time_t timestamp,
+                       enum imstatus status, const char *status_msg);
+void hlog_save_state(void);
+void hlog_load_state(void);
+
+#endif /* __MCABBER_HISTOLOG_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/hooks.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,668 @@
+/*
+ * hooks.c      -- Hooks layer
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <loudmouth/loudmouth.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "hooks.h"
+#include "screen.h"
+#include "roster.h"
+#include "histolog.h"
+#include "hbuf.h"
+#include "settings.h"
+#include "utils.h"
+#include "utf8.h"
+#include "commands.h"
+#include "main.h"
+
+#ifdef MODULES_ENABLE
+#include <glib.h>
+
+typedef struct {
+  hk_handler_t handler;
+  guint32      flags;
+  gpointer     userdata;
+} hook_list_data_t;
+
+static GSList *hk_handler_queue = NULL;
+
+void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata)
+{
+  hook_list_data_t *h = g_new (hook_list_data_t, 1);
+  h->handler  = handler;
+  h->flags    = flags;
+  h->userdata = userdata;
+  hk_handler_queue = g_slist_append (hk_handler_queue, h);
+}
+
+static gint hk_queue_search_cb (hook_list_data_t *a, hook_list_data_t *b)
+{
+  if (a->handler == b->handler && a->userdata == b->userdata)
+    return 0;
+  else
+    return 1;
+}
+
+void hk_del_handler (hk_handler_t handler, gpointer userdata)
+{
+  hook_list_data_t h = { handler, 0, userdata };
+  GSList *el = g_slist_find_custom (hk_handler_queue, &h, (GCompareFunc) hk_queue_search_cb);
+  if (el) {
+    g_free (el->data);
+    hk_handler_queue = g_slist_delete_link (hk_handler_queue, el);
+  }
+}
+#endif
+
+static char *extcmd;
+
+static const char *COMMAND_ME = "/me ";
+
+void hk_message_in(const char *bjid, const char *resname,
+                   time_t timestamp, const char *msg, LmMessageSubType type,
+                   guint encrypted)
+{
+  int new_guy = FALSE;
+  int is_groupchat = FALSE; // groupchat message
+  int is_room = FALSE;      // window is a room window
+  int log_muc_conf = FALSE;
+  int active_window = FALSE;
+  int message_flags = 0;
+  guint rtype = ROSTER_TYPE_USER;
+  char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
+  GSList *roster_usr;
+  unsigned mucnicklen = 0;
+  const char *ename = NULL;
+
+  if (encrypted == ENCRYPTED_PGP)
+    message_flags |= HBB_PREFIX_PGPCRYPT;
+  else if (encrypted == ENCRYPTED_OTR)
+    message_flags |= HBB_PREFIX_OTRCRYPT;
+
+  if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
+    rtype = ROSTER_TYPE_ROOM;
+    is_groupchat = TRUE;
+    log_muc_conf = settings_opt_get_int("log_muc_conf");
+    if (!resname) {
+      message_flags = HBB_PREFIX_INFO | HBB_PREFIX_NOFLAG;
+      resname = "";
+      wmsg = bmsg = g_strdup_printf("~ %s", msg);
+    } else {
+      wmsg = bmsg = g_strdup_printf("<%s> %s", resname, msg);
+      mucnicklen = strlen(resname) + 2;
+      if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME)))
+        wmsg = mmsg = g_strdup_printf("*%s %s", resname, msg+4);
+    }
+  } else {
+    bmsg = g_strdup(msg);
+    if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
+      gchar *shortid = g_strdup(bjid);
+      if (settings_opt_get_int("buddy_me_fulljid") == FALSE) {
+        gchar *p = strchr(shortid, '@'); // Truncate the jid
+        if (p)
+          *p = '\0';
+      }
+      wmsg = mmsg = g_strdup_printf("*%s %s", shortid, msg+4);
+      g_free(shortid);
+    } else
+      wmsg = (char*) msg;
+  }
+
+  // If this user isn't in the roster, we add it
+  roster_usr = roster_find(bjid, jidsearch, 0);
+  if (!roster_usr) {
+    new_guy = TRUE;
+    roster_usr = roster_add_user(bjid, NULL, NULL, rtype, sub_none, -1);
+    if (!roster_usr) { // Shouldn't happen...
+      scr_LogPrint(LPRINT_LOGNORM, "ERROR: unable to add buddy!");
+      g_free(bmsg);
+      g_free(mmsg);
+      return;
+    }
+  } else if (is_groupchat) {
+    // Make sure the type is ROOM
+    buddy_settype(roster_usr->data, ROSTER_TYPE_ROOM);
+  }
+
+  is_room = !!(buddy_gettype(roster_usr->data) & ROSTER_TYPE_ROOM);
+
+  if (is_room) {
+    if (!is_groupchat) {
+      // This is a private message from a room participant
+      g_free(bmsg);
+      if (!resname) {
+        resname = "";
+        wmsg = bmsg = g_strdup(msg);
+      } else {
+        wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", resname, msg);
+        if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
+          g_free(mmsg);
+          wmsg = mmsg = g_strdup_printf("PRIV#*%s %s", resname, msg+4);
+        }
+      }
+      message_flags |= HBB_PREFIX_HLIGHT;
+    } else {
+      // This is a regular chatroom message.
+      const char *nick = buddy_getnickname(roster_usr->data);
+
+      if (nick) {
+        // Let's see if we are the message sender, in which case we'll
+        // highlight it.
+        if (resname && !strcmp(resname, nick)) {
+          message_flags |= HBB_PREFIX_HLIGHT_OUT;
+        } else if (!settings_opt_get_int("muc_disable_nick_hl")) {
+          // We're not the sender.  Can we see our nick?
+          const char *msgptr = msg;
+          while ((msgptr = strcasestr(msgptr, nick)) != NULL) {
+            const char *leftb, *rightb;
+            // The message contains our nick.  Let's check it's not
+            // in the middle of another word (i.e. preceded/followed
+            // immediately by an alphanumeric character or an underscore.
+            rightb = msgptr+strlen(nick);
+            if (msgptr == msg)
+              leftb = NULL;
+            else
+              leftb = prev_char((char*)msgptr, msg);
+            msgptr = next_char((char*)msgptr);
+            // Check left boundary
+            if (leftb && (iswalnum(get_char(leftb)) || get_char(leftb) == '_'))
+              continue;
+            // Check right boundary
+            if (!iswalnum(get_char(rightb)) && get_char(rightb) != '_')
+              message_flags |= HBB_PREFIX_HLIGHT;
+          }
+        }
+      }
+    }
+  }
+
+  if (type  == LM_MESSAGE_SUB_TYPE_ERROR) {
+    message_flags = HBB_PREFIX_ERR | HBB_PREFIX_IN;
+    scr_LogPrint(LPRINT_LOGNORM, "Error message received from <%s>", bjid);
+  }
+
+  // Note: the hlog_write should not be called first, because in some
+  // cases scr_WriteIncomingMessage() will load the history and we'd
+  // have the message twice...
+  scr_WriteIncomingMessage(bjid, wmsg, timestamp, message_flags, mucnicklen);
+
+  // We don't log the modified message, but the original one
+  if (wmsg == mmsg)
+    wmsg = bmsg;
+
+  // - We don't log the message if it is an error message
+  // - We don't log the message if it is a private conf. message
+  // - We don't log the message if it is groupchat message and the log_muc_conf
+  //   option is off (and it is not a history line)
+  if (!(message_flags & HBB_PREFIX_ERR) &&
+      (!is_room || (is_groupchat && log_muc_conf && !timestamp)))
+    hlog_write_message(bjid, timestamp, 0, wmsg);
+
+  if (settings_opt_get_int("events_ignore_active_window") &&
+      current_buddy && scr_get_chatmode()) {
+    gpointer bud = BUDDATA(current_buddy);
+    if (bud) {
+      const char *cjid = buddy_getjid(bud);
+      if (cjid && !strcasecmp(cjid, bjid))
+        active_window = TRUE;
+    }
+  }
+
+  if (settings_opt_get_int("eventcmd_use_nickname"))
+    ename = roster_getname(bjid);
+
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+#if 0
+      hk_arg_t *args = g_new (hk_arg_t, 5);
+      args[0].name = "hook";
+      args[0].value = "hook-message-in";
+      args[1].name = "jid";
+      args[1].value = bjid;
+      args[2].name = "message";
+      args[2].value = wmsg;
+      args[3].name = "groupchat";
+      args[3].value = is_groupchat ? "true" : "false";
+      args[4].name = NULL;
+      args[4].value = NULL;
+#else
+      // We can use a const array for keys/static values, so modules
+      // can do fast known to them args check by just comparing pointers...
+      hk_arg_t args[] = {
+        { "hook", "hook-message-in" },
+        { "jid", bjid },
+        { "message", wmsg },
+        { "groupchat", is_groupchat ? "true" : "false" },
+        { NULL, NULL },
+      };
+#endif
+      while (h) {
+        hook_list_data_t *data = h->data;
+        if (data->flags & HOOK_MESSAGE_IN)
+          (data->handler) (HOOK_MESSAGE_IN, args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
+  // External command
+  // - We do not call hk_ext_cmd() for history lines in MUC
+  // - We do call hk_ext_cmd() for private messages in a room
+  // - We do call hk_ext_cmd() for messages to the current window
+  if (!active_window && ((is_groupchat && !timestamp) || !is_groupchat))
+    hk_ext_cmd(ename ? ename : bjid, (is_groupchat ? 'G' : 'M'), 'R', wmsg);
+
+  // Display the sender in the log window
+  if ((!is_groupchat) && !(message_flags & HBB_PREFIX_ERR) &&
+      settings_opt_get_int("log_display_sender")) {
+    const char *name = roster_getname(bjid);
+    if (!name) name = "";
+    scr_LogPrint(LPRINT_NORMAL, "Message received from %s <%s/%s>",
+                 name, bjid, (resname ? resname : ""));
+  }
+
+  // Beep, if enabled:
+  // - if it's a private message
+  // - if it's a public message and it's highlighted
+  if (settings_opt_get_int("beep_on_message")) {
+    if ((!is_groupchat && !(message_flags & HBB_PREFIX_ERR)) ||
+        (is_groupchat  && (message_flags & HBB_PREFIX_HLIGHT)))
+      scr_Beep();
+  }
+
+  // We need to update the roster if the sender is unknown or
+  // if the sender is offline/invisible and a filter is set.
+  if (new_guy ||
+      (buddy_getstatus(roster_usr->data, NULL) == offline &&
+       buddylist_isset_filter()))
+  {
+    update_roster = TRUE;
+  }
+
+  g_free(bmsg);
+  g_free(mmsg);
+}
+
+//  hk_message_out()
+// nick should be set for private messages in a chat room, and null for
+// normal messages.
+void hk_message_out(const char *bjid, const char *nick,
+                    time_t timestamp, const char *msg,
+                    guint encrypted, gpointer xep184)
+{
+  char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
+  guint cryptflag = 0;
+
+  if (nick) {
+    wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", nick, msg);
+    if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
+      const char *mynick = roster_getnickname(bjid);
+      wmsg = mmsg = g_strdup_printf("PRIV#<%s> *%s %s", nick,
+                                    (mynick ? mynick : "me"), msg+4);
+    }
+  } else {
+    wmsg = (char*)msg;
+    if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
+      char *myid = jid_get_username(settings_opt_get("jid"));
+      if (myid) {
+        wmsg = mmsg = g_strdup_printf("*%s %s", myid, msg+4);
+        g_free(myid);
+      }
+    }
+  }
+
+  // Note: the hlog_write should not be called first, because in some
+  // cases scr_WriteOutgoingMessage() will load the history and we'd
+  // have the message twice...
+  if (encrypted == ENCRYPTED_PGP)
+    cryptflag = HBB_PREFIX_PGPCRYPT;
+  else if (encrypted == ENCRYPTED_OTR)
+    cryptflag = HBB_PREFIX_OTRCRYPT;
+  scr_WriteOutgoingMessage(bjid, wmsg, cryptflag, xep184);
+
+  // We don't log private messages
+  if (!nick)
+    hlog_write_message(bjid, timestamp, 1, msg);
+
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      hk_arg_t args[] = {
+        { "hook", "hook-message-out" },
+        { "jid", bjid },
+        { "message", wmsg },
+        { NULL, NULL },
+      };
+      while (h) {
+        hook_list_data_t *data = h->data;
+        if (data->flags & HOOK_MESSAGE_OUT)
+          (data->handler) (HOOK_MESSAGE_OUT, args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
+  // External command
+  hk_ext_cmd(bjid, 'M', 'S', NULL);
+
+  g_free(bmsg);
+  g_free(mmsg);
+}
+
+void hk_statuschange(const char *bjid, const char *resname, gchar prio,
+                     time_t timestamp, enum imstatus status,
+                     const char *status_msg)
+{
+  int st_in_buf;
+  enum imstatus oldstat;
+  char *bn;
+  char *logsmsg;
+  const char *rn = (resname ? resname : "");
+  const char *ename = NULL;
+
+  if (settings_opt_get_int("eventcmd_use_nickname"))
+    ename = roster_getname(bjid);
+
+  oldstat = roster_getstatus(bjid, resname);
+
+  st_in_buf = settings_opt_get_int("show_status_in_buffer");
+
+  if (settings_opt_get_int("log_display_presence")) {
+    int buddy_format = settings_opt_get_int("buddy_format");
+    bn = NULL;
+    if (buddy_format) {
+      const char *name = roster_getname(bjid);
+      if (name && strcmp(name, bjid)) {
+        if (buddy_format == 1)
+          bn = g_strdup_printf("%s <%s/%s>", name, bjid, rn);
+        else if (buddy_format == 2)
+          bn = g_strdup_printf("%s/%s", name, rn);
+        else if (buddy_format == 3)
+          bn = g_strdup_printf("%s", name);
+      }
+    }
+
+    if (!bn)
+      bn = g_strdup_printf("<%s/%s>", bjid, rn);
+
+    logsmsg = g_strdup(status_msg ? status_msg : "");
+    replace_nl_with_dots(logsmsg);
+
+    scr_LogPrint(LPRINT_LOGNORM, "Buddy status has changed: [%c>%c] %s %s",
+                 imstatus2char[oldstat], imstatus2char[status], bn, logsmsg);
+    g_free(logsmsg);
+    g_free(bn);
+  }
+
+  if (st_in_buf == 2 ||
+      (st_in_buf == 1 && (status == offline || oldstat == offline))) {
+    // Write the status change in the buddy's buffer, only if it already exists
+    if (scr_BuddyBufferExists(bjid)) {
+      bn = g_strdup_printf("Buddy status has changed: [%c>%c] %s",
+                           imstatus2char[oldstat], imstatus2char[status],
+                           ((status_msg) ? status_msg : ""));
+      scr_WriteIncomingMessage(bjid, bn, timestamp,
+                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
+      g_free(bn);
+    }
+  }
+
+  roster_setstatus(bjid, rn, prio, status, status_msg, timestamp,
+                   role_none, affil_none, NULL);
+  buddylist_build();
+  scr_DrawRoster();
+  hlog_write_status(bjid, timestamp, status, status_msg);
+
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      char os[2] = " \0";
+      char ns[2] = " \0";
+      hk_arg_t args[] = {
+        { "hook", "hook-status-change" },
+        { "jid", bjid },
+        { "resource", rn },
+        { "old_status", os },
+        { "new_status", ns },
+        { "message", status_msg ? status_msg : "" },
+        { NULL, NULL },
+      };
+      os[0] = imstatus2char[oldstat];
+      ns[0] = imstatus2char[status];
+      while (h) {
+        hook_list_data_t *data = h->data;
+        if (data->flags & HOOK_STATUS_CHANGE)
+          (data->handler) (HOOK_STATUS_CHANGE, args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
+  // External command
+  hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL);
+}
+
+void hk_mystatuschange(time_t timestamp, enum imstatus old_status,
+                              enum imstatus new_status, const char *msg)
+{
+  scr_LogPrint(LPRINT_LOGNORM, "Your status has been set: [%c>%c] %s",
+               imstatus2char[old_status], imstatus2char[new_status],
+               (msg ? msg : ""));
+
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      char ns[2] = " \0";
+      hk_arg_t args[] = {
+        { "hook", "hook-my-status-change" },
+        { "new_status", ns },
+        { "message", msg ? msg : "" },
+        { NULL, NULL },
+      };
+      ns[0] = imstatus2char[new_status];
+      while (h) {
+        hook_list_data_t *data = h->data;
+        if (data->flags & HOOK_MY_STATUS_CHANGE)
+          (data->handler) (HOOK_MY_STATUS_CHANGE, args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
+  //hlog_write_status(NULL, 0, status);
+}
+
+
+/* Internal commands */
+
+void hook_execute_internal(const char *hookname)
+{
+  const char *hook_command;
+  char *buf;
+  char *cmdline;
+
+#ifdef MODULES_ENABLE
+  {
+    GSList *h = hk_handler_queue;
+    if (h) {
+      hk_arg_t args[] = {
+        { "hook", hookname },
+        { NULL, NULL },
+      };
+      while (h) {
+        hook_list_data_t *data = h->data;
+        if (data->flags & HOOK_INTERNAL)
+          (data->handler) (HOOK_INTERNAL, args, data->userdata);
+        h = g_slist_next (h);
+      }
+    }
+  }
+#endif
+
+  hook_command = settings_opt_get(hookname);
+  if (!hook_command)
+    return;
+
+  buf = g_strdup_printf("Running %s...", hookname);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  cmdline = from_utf8(hook_command);
+  if (process_command(cmdline, TRUE) == 255)
+    mcabber_set_terminate_ui();
+
+  g_free(cmdline);
+  g_free(buf);
+}
+
+
+/* External commands */
+
+//  hk_ext_cmd_init()
+// Initialize external command variable.
+// Can be called with parameter NULL to reset and free memory.
+void hk_ext_cmd_init(const char *command)
+{
+  if (extcmd) {
+    g_free(extcmd);
+    extcmd = NULL;
+  }
+  if (command)
+    extcmd = expand_filename(command);
+}
+
+//  hk_ext_cmd()
+// Launch an external command (process) for the given event.
+// For now, data should be NULL.
+void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data)
+{
+  pid_t pid;
+  char *arg_type = NULL;
+  char *arg_info = NULL;
+  char *arg_data = NULL;
+  char status_str[2];
+  char *datafname = NULL;
+  char unread_str[16];
+
+  if (!extcmd) return;
+
+  // Prepare arg_* (external command parameters)
+  switch (type) {
+    case 'M': /* Normal message */
+        arg_type = "MSG";
+        if (info == 'R')
+          arg_info = "IN";
+        else if (info == 'S')
+          arg_info = "OUT";
+        break;
+    case 'G': /* Groupchat message */
+        arg_type = "MSG";
+        arg_info = "MUC";
+        break;
+    case 'S': /* Status change */
+        arg_type = "STATUS";
+        if (strchr(imstatus2char, tolower(info))) {
+          status_str[0] = toupper(info);
+          status_str[1] = 0;
+          arg_info = status_str;
+        }
+        break;
+    case 'U': /* Unread buffer count */
+        arg_type = "UNREAD";
+        g_snprintf(unread_str, sizeof unread_str, "%d", info);
+        arg_info = unread_str;  /* number of remaining unread bjids */
+        break;
+    default:
+        return;
+  }
+
+  if (!arg_type || !arg_info) return;
+
+  if (strchr("MG", type) && data && settings_opt_get_int("event_log_files")) {
+    int fd;
+    const char *prefix;
+    char *prefix_xp = NULL;
+    char *data_locale;
+
+    data_locale = from_utf8(data);
+    prefix = settings_opt_get("event_log_dir");
+    if (prefix)
+      prefix = prefix_xp = expand_filename(prefix);
+    else
+      prefix = ut_get_tmpdir();
+    datafname = g_strdup_printf("%s/mcabber-%d.XXXXXX", prefix, getpid());
+    g_free(prefix_xp);
+
+    // XXX Some old systems may require us to set umask first.
+    fd = mkstemp(datafname);
+    if (fd == -1) {
+      g_free(datafname);
+      datafname = NULL;
+      scr_LogPrint(LPRINT_LOGNORM,
+                   "Unable to create temp file for external command.");
+    } else {
+      size_t data_locale_len = strlen(data_locale);
+      ssize_t a = write(fd, data_locale, data_locale_len);
+      ssize_t b = write(fd, "\n", 1);
+      if ((size_t)a != data_locale_len || b != 1) {
+        g_free(datafname);
+        datafname = NULL;
+        scr_LogPrint(LPRINT_LOGNORM,
+                     "Unable to write to temp file for external command.");
+      }
+      close(fd);
+      arg_data = datafname;
+    }
+    g_free(data_locale);
+  }
+
+  if ((pid=fork()) == -1) {
+    scr_LogPrint(LPRINT_LOGNORM, "Fork error, cannot launch external command.");
+    g_free(datafname);
+    return;
+  }
+
+  if (pid == 0) { // child
+    // Close standard file descriptors
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    if (execl(extcmd, extcmd, arg_type, arg_info, bjid, arg_data,
+              (char *)NULL) == -1) {
+      // scr_LogPrint(LPRINT_LOGNORM, "Cannot execute external command.");
+      exit(1);
+    }
+  }
+  g_free(datafname);
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/hooks.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,53 @@
+#ifndef __MCABBER_HOOKS_H__
+#define __MCABBER_HOOKS_H__ 1
+
+#include <time.h>
+#include <loudmouth/loudmouth.h>
+#include <mcabber/xmpp.h>
+
+// These two defines are used by hk_message_{in,out} arguments
+#define ENCRYPTED_PGP   1
+#define ENCRYPTED_OTR   2
+
+#include <mcabber/config.h>
+#ifdef MODULES_ENABLE
+#include <glib.h>
+
+#define HOOK_MESSAGE_IN       ( 0x00000001 )
+#define HOOK_MESSAGE_OUT      ( 0x00000002 )
+#define HOOK_STATUS_CHANGE    ( 0x00000004 )
+#define HOOK_MY_STATUS_CHANGE ( 0x00000008 )
+#define HOOK_INTERNAL         ( 0x00000010 )
+
+typedef struct {
+  const char *name;
+  const char *value;
+} hk_arg_t;
+
+typedef void (*hk_handler_t) (guint32 flags, hk_arg_t *args, gpointer userdata);
+
+void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata);
+void hk_del_handler (hk_handler_t handler, gpointer userdata);
+#endif
+
+void hk_message_in(const char *bjid, const char *resname,
+                   time_t timestamp, const char *msg, LmMessageSubType type,
+                   guint encrypted);
+void hk_message_out(const char *bjid, const char *nickname,
+                    time_t timestamp, const char *msg,
+                    guint encrypted,  gpointer xep184);
+void hk_statuschange(const char *bjid, const char *resname, gchar prio,
+                     time_t timestamp, enum imstatus status,
+                     char const *status_msg);
+void hk_mystatuschange(time_t timestamp,
+                              enum imstatus old_status,
+                              enum imstatus new_status, const char *msg);
+
+void hook_execute_internal(const char *hookname);
+
+void hk_ext_cmd_init(const char *command);
+void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data);
+
+#endif /* __MCABBER_HOOKS_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/logprint.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,20 @@
+#ifndef __MCABBER_LOGPRINT_H__
+#define __MCABBER_LOGPRINT_H__ 1
+
+// Flags for scr_LogPrint()
+#define LPRINT_NORMAL   1U  // Display in log window
+#define LPRINT_LOG      2U  // Log to file (if enabled)
+#define LPRINT_DEBUG    4U  // Debug message (log if enabled)
+#define LPRINT_NOTUTF8  8U  // Do not convert from UTF-8 to locale
+
+// For convenience...
+#define LPRINT_LOGNORM  (LPRINT_NORMAL|LPRINT_LOG)
+
+void scr_print_logwindow(const char *string);
+void scr_LogPrint(unsigned int flag, const char *fmt, ...);
+
+void scr_DoUpdate(void);
+
+#endif /* __MCABBER_LOGPRINT_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/main.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,494 @@
+/*
+ * main.c
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ * Parts of this file come from Cabber <cabber@ajmacias.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <termios.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <glib.h>
+#include <config.h>
+#include <poll.h>
+
+#include "caps.h"
+#include "screen.h"
+#include "settings.h"
+#include "roster.h"
+#include "commands.h"
+#include "histolog.h"
+#include "hooks.h"
+#include "utils.h"
+#include "pgp.h"
+#include "otr.h"
+#include "fifo.h"
+#include "xmpp.h"
+
+#ifdef ENABLE_HGCSET
+# include "hgcset.h"
+#endif
+
+#ifndef WAIT_ANY
+# define WAIT_ANY -1
+#endif
+
+static unsigned int terminate_ui;
+GMainContext *main_context;
+
+static gboolean update_screen = TRUE;
+
+static struct termios *backup_termios;
+
+char *mcabber_version(void)
+{
+  char *ver;
+#ifdef HGCSET
+  ver = g_strdup_printf("%s (%s)", PACKAGE_VERSION, HGCSET);
+#else
+  ver = g_strdup(PACKAGE_VERSION);
+#endif
+  return ver;
+}
+
+static void mcabber_terminate(const char *msg)
+{
+  fifo_deinit();
+  xmpp_disconnect();
+  scr_TerminateCurses();
+
+  // Restore term settings, if needed.
+  if (backup_termios)
+    tcsetattr(fileno(stdin), TCSAFLUSH, backup_termios);
+
+  if (msg)
+    fprintf(stderr, "%s\n", msg);
+  printf("Bye!\n");
+  exit(EXIT_SUCCESS);
+}
+
+void sig_handler(int signum)
+{
+  if (signum == SIGCHLD) {
+    int status;
+    pid_t pid;
+    do {
+      pid = waitpid (WAIT_ANY, &status, WNOHANG);
+      // Check the exit status value if 'eventcmd_checkstatus' is set
+      if (settings_opt_get_int("eventcmd_checkstatus")) {
+        if (pid > 0) {
+          // exit status 2 -> beep
+          if (WIFEXITED(status) && WEXITSTATUS(status) == 2) {
+            scr_Beep();
+          }
+        }
+      }
+    } while (pid > 0);
+    signal(SIGCHLD, sig_handler);
+  } else if (signum == SIGTERM) {
+    mcabber_terminate("Killed by SIGTERM");
+  } else if (signum == SIGINT) {
+    mcabber_terminate("Killed by SIGINT");
+#ifdef USE_SIGWINCH
+  } else if (signum == SIGWINCH) {
+    ungetch(KEY_RESIZE);
+#endif
+  } else {
+    scr_LogPrint(LPRINT_LOGNORM, "Caught signal: %d", signum);
+  }
+}
+
+//  ask_password(what)
+// Return the password, or NULL.
+// The string must be freed after use.
+static char *ask_password(const char *what)
+{
+  char *password, *p;
+  size_t passsize = 128;
+  struct termios orig, new;
+
+  password = g_new0(char, passsize);
+
+  /* Turn echoing off and fail if we can't. */
+  if (tcgetattr(fileno(stdin), &orig) != 0) return NULL;
+  backup_termios = &orig;
+
+  new = orig;
+  new.c_lflag &= ~ECHO;
+  if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) return NULL;
+
+  /* Read the password. */
+  printf("Please enter %s: ", what);
+  if (fgets(password, passsize, stdin) == NULL) return NULL;
+
+  /* Restore terminal. */
+  tcsetattr(fileno(stdin), TCSAFLUSH, &orig);
+  printf("\n");
+  backup_termios = NULL;
+
+  for (p = (char*)password; *p; p++)
+    ;
+  for ( ; p > (char*)password ; p--)
+    if (*p == '\n' || *p == '\r') *p = 0;
+
+  return password;
+}
+
+static void credits(void)
+{
+  const char *v_fmt = "MCabber %s -- Email: mcabber [at] lilotux [dot] net\n";
+  char *v = mcabber_version();
+  printf(v_fmt, v);
+  scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, v_fmt, v);
+  g_free(v);
+}
+
+static void compile_options(void)
+{
+  puts("Installation data directory: " DATA_DIR "\n");
+#ifdef HAVE_UNICODE
+  puts("Compiled with unicode support.");
+#endif
+#ifdef MODULES_ENABLE
+  puts ("Compiled with modules support.");
+#endif
+#ifdef HAVE_GPGME
+  puts("Compiled with GPG support.");
+#endif
+#ifdef HAVE_LIBOTR
+  puts("Compiled with OTR support.");
+#endif
+#ifdef WITH_ENCHANT
+  puts("Compiled with Enchant support.");
+#endif
+#ifdef WITH_ASPELL
+  puts("Compiled with Aspell support.");
+#endif
+#ifdef ENABLE_DEBUG
+  puts("Compiled with debugging support.");
+#endif
+}
+
+static void main_init_pgp(void)
+{
+#ifdef HAVE_GPGME
+  const char *pk, *pp;
+  char *typed_passwd = NULL;
+  char *p;
+  bool pgp_invalid = FALSE;
+  bool pgp_agent;
+  int retries;
+
+  p = getenv("GPG_AGENT_INFO");
+  pgp_agent = (p && strchr(p, ':'));
+
+  pk = settings_opt_get("pgp_private_key");
+  pp = settings_opt_get("pgp_passphrase");
+
+  if (settings_opt_get("pgp_passphrase_retries"))
+    retries = settings_opt_get_int("pgp_passphrase_retries");
+  else
+    retries = 2;
+
+  if (!pk) {
+    scr_LogPrint(LPRINT_LOGNORM, "WARNING: unknown PGP private key");
+    pgp_invalid = TRUE;
+  } else if (!(pp || pgp_agent)) {
+    // Request PGP passphrase
+    pp = typed_passwd = ask_password("PGP passphrase");
+  }
+  gpg_init(pk, pp);
+  // Erase password from the settings array
+  if (pp) {
+    memset((char*)pp, 0, strlen(pp));
+    if (typed_passwd)
+      g_free(typed_passwd);
+    else
+      settings_set(SETTINGS_TYPE_OPTION, "pgp_passphrase", NULL);
+  }
+  if (!pgp_agent && pk && pp && gpg_test_passphrase()) {
+    // Let's check the pasphrase
+    int i;
+    for (i = 1; retries < 0 || i <= retries; i++) {
+      typed_passwd = ask_password("PGP passphrase"); // Ask again...
+      if (typed_passwd) {
+        gpg_set_passphrase(typed_passwd);
+        memset(typed_passwd, 0, strlen(typed_passwd));
+        g_free(typed_passwd);
+      }
+      if (!gpg_test_passphrase())
+        break; // Ok
+    }
+    if (i > retries)
+      pgp_invalid = TRUE;
+  }
+  if (pgp_invalid)
+    scr_LogPrint(LPRINT_LOGNORM, "WARNING: PGP key/pass invalid");
+#else /* not HAVE_GPGME */
+  scr_LogPrint(LPRINT_LOGNORM, "WARNING: not compiled with PGP support");
+#endif /* HAVE_GPGME */
+}
+
+void mcabber_set_terminate_ui(void)
+{
+  terminate_ui = TRUE;
+}
+
+typedef struct {
+  GSource source;
+  GPollFD pollfd;
+} mcabber_source_t;
+
+static gboolean mcabber_source_prepare(GSource *source, gint *timeout)
+{
+  *timeout = -1;
+  return FALSE;
+}
+
+static gboolean mcabber_source_check(GSource *source)
+{
+  mcabber_source_t *mc_source = (mcabber_source_t *) source;
+  gushort revents = mc_source->pollfd.revents;
+  if (revents)
+    return TRUE;
+  return FALSE;
+}
+
+static gboolean keyboard_activity(void)
+{
+  keycode kcode;
+
+  if (terminate_ui) {
+    return FALSE;
+  }
+  scr_DoUpdate();
+  scr_Getch(&kcode);
+
+  while (kcode.value != ERR) {
+    process_key(kcode);
+    update_screen = TRUE;
+    scr_Getch(&kcode);
+  }
+  scr_CheckAutoAway(FALSE);
+
+  return TRUE;
+}
+
+static gboolean mcabber_source_dispatch(GSource *source, GSourceFunc callback,
+                                        gpointer udata) {
+  return keyboard_activity();
+}
+
+static GSourceFuncs mcabber_source_funcs = {
+  mcabber_source_prepare,
+  mcabber_source_check,
+  mcabber_source_dispatch,
+  NULL,
+  NULL,
+  NULL
+};
+
+int main(int argc, char **argv)
+{
+  char *configFile = NULL;
+  const char *optstring;
+  int optval, optval2;
+  int ret;
+
+  credits();
+
+  signal(SIGTERM, sig_handler);
+  signal(SIGINT,  sig_handler);
+  signal(SIGCHLD, sig_handler);
+#ifdef USE_SIGWINCH
+  signal(SIGWINCH, sig_handler);
+#endif
+  signal(SIGPIPE, SIG_IGN);
+
+  /* Parse command line options */
+  while (1) {
+    int c = getopt(argc, argv, "hVf:");
+    if (c == -1) {
+      break;
+    } else
+      switch (c) {
+      case 'h':
+      case '?':
+        printf("Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]);
+        return (c == 'h' ? 0 : -1);
+      case 'V':
+        compile_options();
+        return 0;
+      case 'f':
+        configFile = g_strdup(optarg);
+        break;
+      }
+  }
+
+  if (optind < argc) {
+    fprintf(stderr, "Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]);
+    return -1;
+  }
+
+  /* Initialize command system, roster and default key bindings */
+  cmd_init();
+  roster_init();
+  settings_init();
+  scr_init_bindings();
+  caps_init();
+  /* Initialize charset */
+  scr_InitLocaleCharSet();
+
+  /* Parsing config file... */
+  ret = cfg_read_file(configFile, TRUE);
+  /* free() configFile if it has been allocated during options parsing */
+  g_free(configFile);
+  /* Leave if there was an error in the config. file */
+  if (ret == -2)
+    exit(EXIT_FAILURE);
+
+  optstring = settings_opt_get("tracelog_file");
+  if (optstring)
+    ut_InitDebug(settings_opt_get_int("tracelog_level"), optstring);
+
+  /* If no password is stored, we ask for it before entering
+     ncurses mode -- unless the username is unknown. */
+  if (settings_opt_get("jid") && !settings_opt_get("password")) {
+    const char *p;
+    char *pwd;
+    p = settings_opt_get("server");
+    if (p)
+      printf("Server: %s\n", p);
+    p = settings_opt_get("jid");
+    if (p)
+      printf("User JID: %s\n", p);
+
+    pwd = ask_password("Jabber password");
+    settings_set(SETTINGS_TYPE_OPTION, "password", pwd);
+    g_free(pwd);
+  }
+
+  /* Initialize PGP system
+     We do it before ncurses initialization because we may need to request
+     a passphrase. */
+  if (settings_opt_get_int("pgp"))
+    main_init_pgp();
+
+  /* Initialize N-Curses */
+  scr_LogPrint(LPRINT_DEBUG, "Initializing N-Curses...");
+  scr_InitCurses();
+  scr_DrawMainWindow(TRUE);
+
+  optval   = (settings_opt_get_int("logging") > 0);
+  optval2  = (settings_opt_get_int("load_logs") > 0);
+  if (optval || optval2)
+    hlog_enable(optval, settings_opt_get("logging_dir"), optval2);
+
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+  /* Initialize spelling */
+  if (settings_opt_get_int("spell_enable")) {
+    spellcheck_init();
+  }
+#endif
+
+  optstring = settings_opt_get("events_command");
+  if (optstring)
+    hk_ext_cmd_init(optstring);
+
+  optstring = settings_opt_get("roster_display_filter");
+  if (optstring)
+    scr_RosterDisplay(optstring);
+  // Empty filter isn't allowed...
+  if (!buddylist_get_filter())
+    scr_RosterDisplay("*");
+
+  chatstates_disabled = settings_opt_get_int("disable_chatstates");
+
+  /* Initialize FIFO named pipe */
+  fifo_init(settings_opt_get("fifo_name"));
+
+  /* Load previous roster state */
+  hlog_load_state();
+
+  main_context = g_main_context_default();
+
+  if (ret < 0) {
+    scr_LogPrint(LPRINT_NORMAL, "No configuration file has been found.");
+    scr_ShowBuddyWindow();
+  } else {
+    /* Connection */
+    xmpp_connect();
+  }
+
+  { // add keypress processing source
+    GSource *mc_source = g_source_new(&mcabber_source_funcs,
+                                      sizeof(mcabber_source_t));
+    GPollFD *mc_pollfd = &(((mcabber_source_t *)mc_source)->pollfd);
+    mc_pollfd->fd = STDIN_FILENO;
+    mc_pollfd->events = POLLIN|POLLERR|POLLPRI;
+    mc_pollfd->revents = 0;
+    g_source_add_poll(mc_source, mc_pollfd);
+    g_source_attach(mc_source, main_context);
+
+    scr_LogPrint(LPRINT_DEBUG, "Entering into main loop...");
+
+    while(!terminate_ui) {
+      if (g_main_context_iteration(main_context, TRUE) == FALSE)
+        keyboard_activity();
+      if (update_roster)
+        scr_DrawRoster();
+      if(update_screen)
+        scr_DoUpdate();
+    }
+
+    g_source_destroy(mc_source);
+    g_source_unref(mc_source);
+  }
+
+  scr_TerminateCurses();
+#ifdef MODULES_ENABLE
+  cmd_deinit();
+#endif
+  fifo_deinit();
+#ifdef HAVE_LIBOTR
+  otr_terminate();
+#endif
+  xmpp_disconnect();
+#ifdef HAVE_GPGME
+  gpg_terminate();
+#endif
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+  /* Deinitialize spelling */
+  if (settings_opt_get_int("spell_enable"))
+    spellcheck_deinit();
+#endif
+  /* Save pending message state */
+  hlog_save_state();
+  caps_free();
+
+  printf("\n\nThanks for using mcabber!\n");
+
+  return 0;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/main.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,11 @@
+#ifndef __MCABBER_MAIN_H__
+#define __MCABBER_MAIN_H__ 1
+
+extern GMainContext *main_context;
+
+void mcabber_set_terminate_ui(void);
+char *mcabber_version(void);
+
+#endif
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/nohtml.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,162 @@
+/*
+ * nohtml.c     -- (X)HTML helper functions
+ *
+ * Copyright (C) 2008,2009 Mikael Berthe <mikael@lilotux.net>
+ * Some portions come from the jabberd project, see below.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ *
+ * Some parts come from libjabber/str.c:
+ * Copyright (c) 1999-2002 Jabber.com, Inc.  All Rights Reserved.  Contact
+ * information for Jabber.com, Inc. is available at http://www.jabber.com/.
+ * Portions Copyright (c) 1998-1999 Jeremie Miller.
+ */
+
+#include <string.h>
+#include <glib.h>
+#include <config.h>
+
+
+/*  html_strip(htmlbuf)
+ * Remove html entities from htmlbuf and try to convert it to plain text.
+ * The caller must g_free the string after use.
+ * Code mostly derived from strunescape(), in libjabber.
+ */
+char *html_strip(const char *htmlbuf)
+{
+  int i, j=0;
+  char *nohtml;
+
+  if (!htmlbuf) return(NULL);
+
+  nohtml = g_strdup(htmlbuf);
+
+  if (!strchr(htmlbuf, '&') && !strchr(htmlbuf, '<'))
+    return(nohtml);
+
+  for (i = 0; i < (int)strlen(htmlbuf); i++) {
+    if (htmlbuf[i] == '&') {
+      if (!strncmp(&htmlbuf[i],"&amp;",5)) {
+        nohtml[j] = '&';
+        i += 4;
+      } else if (!strncmp(&htmlbuf[i],"&quot;", 6)) {
+        nohtml[j] = '\"';
+        i += 5;
+      } else if (!strncmp(&htmlbuf[i],"&apos;", 6)) {
+        nohtml[j] = '\'';
+        i += 5;
+      } else if (!strncmp(&htmlbuf[i],"&lt;", 4)) {
+        nohtml[j] = '<';
+        i += 3;
+      } else if (!strncmp(&htmlbuf[i],"&gt;", 4)) {
+        nohtml[j] = '>';
+        i += 3;
+      }
+    } else if (!strncmp(&htmlbuf[i],"<br>", 4) ||
+               !strncmp(&htmlbuf[i],"<br/>", 5)) {
+      nohtml[j] = '\n';
+      i += (htmlbuf[i+3] == '/' ? 4 : 3);
+    } else if (htmlbuf[i] == '<') {
+      /* Let's strip all unknown tags */
+      j--;
+      while (htmlbuf[++i] != '>');
+    } else
+      nohtml[j] = htmlbuf[i];
+    j++;
+  }
+  nohtml[j] = '\0';
+  return nohtml;
+}
+
+/*  html_escape(text)
+ * Add (x)html entities to the text.
+ * The caller must g_free the string after use.
+ * Code mostly derived from strescape(), in libjabber.
+ */
+char *html_escape(const char *text)
+{
+  int i, j;
+  int oldlen, newlen;
+  char *html;
+
+  if (!text) return(NULL);
+
+  oldlen = newlen = strlen(text);
+
+  for (i = 0; i < oldlen; i++) {
+    switch(text[i])
+    {
+      case '&':
+          newlen += 5;
+          break;
+      case '\'':
+          newlen += 6;
+          break;
+          case '\"':
+              newlen += 6;
+          break;
+      case '<':
+          newlen += 4;
+          break;
+      case '>':
+          newlen += 4;
+          break;
+      case '\n':
+          newlen += 5;
+    }
+  }
+
+  if (oldlen == newlen)
+    return g_strdup(text);
+
+  html = g_new0(char, newlen+1);
+
+  for (i = j = 0; i < oldlen; i++) {
+    switch(text[i])
+    {
+      case '&':
+          memcpy(&html[j], "&amp;", 5);
+          j += 5;
+          break;
+      case '\'':
+          memcpy(&html[j], "&apos;", 6);
+          j += 6;
+          break;
+      case '\"':
+          memcpy(&html[j], "&quot;", 6);
+          j += 6;
+          break;
+      case '<':
+          memcpy(&html[j], "&lt;", 4);
+          j += 4;
+          break;
+      case '>':
+          memcpy(&html[j], "&gt;", 4);
+          j += 4;
+          break;
+      case '\n':
+          memcpy(&html[j], "<br/>", 5);
+          j += 5;
+          break;
+      default:
+          html[j++] = text[i];
+    }
+  }
+  return html;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/nohtml.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,9 @@
+#ifndef __MCABBER_NOHTML_H__
+#define __MCABBER_NOHTML_H__ 1
+
+char *html_strip(const char *buf);
+char *html_escape(const char *text);
+
+#endif /* __MCABBER_NOHTML_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/otr.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,777 @@
+/*
+ * otr.c        -- Off-The-Record Messaging for mcabber
+ *
+ * Copyright (C) 2007-2009 Frank Zschockelt <mcabber_otr@freakysoft.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <config.h>
+#include <glib.h>
+
+#ifdef HAVE_LIBOTR
+
+#include "hbuf.h"
+#include "logprint.h"
+#include "nohtml.h"
+#include "otr.h"
+#include "roster.h"
+#include "screen.h"
+#include "settings.h"
+#include "utils.h"
+#include "xmpp.h"
+
+#define OTR_PROTOCOL_NAME "jabber"
+
+static OtrlUserState userstate = NULL;
+static char *account = NULL;
+static char *keyfile = NULL;
+static char *fprfile = NULL;
+
+static int otr_is_enabled = FALSE;
+
+static OtrlPolicy cb_policy             (void *opdata, ConnContext *ctx);
+static void       cb_create_privkey     (void *opdata,
+                                         const char *accountname,
+                                         const char *protocol);
+static int        cb_is_logged_in       (void *opdata,
+                                         const char *accountname,
+                                         const char *protocol,
+                                         const char *recipient);
+static void       cb_inject_message     (void *opdata,
+                                         const char *accountname,
+                                         const char *protocol,
+                                         const char *recipient,
+                                         const char *message);
+static void       cb_notify             (void *opdata,
+                                         OtrlNotifyLevel level,
+                                         const char *accountname,
+                                         const char *protocol,
+                                         const char *username,
+                                         const char *title,
+                                         const char *primary,
+                                         const char *secondary);
+static int        cb_display_otr_message(void *opdata,
+                                         const char *accountname,
+                                         const char *protocol,
+                                         const char *username,
+                                         const char *msg);
+static void       cb_update_context_list(void *opdata);
+static const char *cb_protocol_name     (void *opdata, const char *protocol);
+static void       cb_protocol_name_free (void *opdata,
+                                         const char *protocol_name);
+static void       cb_new_fingerprint    (void *opdata, OtrlUserState us,
+                                         const char *accountname,
+                                         const char *protocol,
+                                         const char *username,
+                                         unsigned char fingerprint[20]);
+static void       cb_write_fingerprints (void *opdata);
+static void       cb_gone_secure        (void *opdata, ConnContext *context);
+static void       cb_gone_insecure      (void *opdata, ConnContext *context);
+static void       cb_still_secure       (void *opdata, ConnContext *context,
+                                         int is_reply);
+static void       cb_log_message        (void *opdata, const char *message);
+static int        cb_max_message_size   (void *opdata, ConnContext *context);
+
+static OtrlMessageAppOps ops =
+{
+  cb_policy,
+  cb_create_privkey,
+  cb_is_logged_in,
+  cb_inject_message,
+  cb_notify,
+  cb_display_otr_message,
+  cb_update_context_list,
+  cb_protocol_name,
+  cb_protocol_name_free,
+  cb_new_fingerprint,
+  cb_write_fingerprints,
+  cb_gone_secure,
+  cb_gone_insecure,
+  cb_still_secure,
+  cb_log_message,
+  cb_max_message_size,
+  NULL, /*account_name*/
+  NULL  /*account_name_free*/
+};
+
+static void otr_message_disconnect(ConnContext *ctx);
+static ConnContext *otr_get_context(const char *buddy);
+static void otr_startstop(const char *buddy, int start);
+static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx);
+
+static char *otr_get_dir(void);
+
+void otr_init(const char *fjid)
+{
+  char *root;
+
+  if (userstate) //already initialised
+    return;
+
+  otr_is_enabled = !!settings_opt_get_int("otr");
+
+  if (!otr_is_enabled)
+    return;
+
+  OTRL_INIT;
+
+  userstate = otrl_userstate_create();
+
+  root = otr_get_dir();
+  account = jidtodisp(fjid);
+  keyfile = g_strdup_printf("%s%s.key", root, account);
+  fprfile = g_strdup_printf("%s%s.fpr", root, account);
+  g_free(root);
+
+  if (otrl_privkey_read(userstate, keyfile)){
+    scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR key from %s", keyfile);
+    cb_create_privkey(NULL, account, OTR_PROTOCOL_NAME);
+  }
+  if (otrl_privkey_read_fingerprints(userstate, fprfile, NULL, NULL)){
+    scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR fingerprints from %s",
+                 fprfile);
+  }
+}
+
+void otr_terminate(void)
+{
+  ConnContext *ctx;
+
+  if (!otr_is_enabled)
+    return;
+
+  for (ctx = userstate->context_root; ctx; ctx = ctx->next)
+    if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
+      otr_message_disconnect(ctx);
+
+  g_free(account);
+  account = NULL;
+
+  /* XXX This #ifdef is a quick workaround: when mcabber
+   * is linked to both gnutls and libotr, libgcrypt will
+   * segfault when we call otrl_userstate_free().
+   * This is reported to be a bug in libgcrypt :-/
+   * Mikael
+   */
+#if defined(HAVE_GNUTLS) && !defined(HAVE_OPENSSL) //TODO: broken now
+  if (!settings_opt_get_int("ssl"))
+#endif
+  otrl_userstate_free(userstate);
+
+  userstate = NULL;
+  g_free(keyfile);
+  keyfile = NULL;
+}
+
+static char *otr_get_dir(void)
+{
+  const char *configured_dir = settings_opt_get("otr_dir");
+
+  if (configured_dir && *configured_dir) {
+    char *xp_conf_dir;
+    int l;
+    xp_conf_dir = expand_filename(configured_dir);
+    // The path must be slash-terminated
+    l = strlen(xp_conf_dir);
+    if (xp_conf_dir[l-1] != '/') {
+      char *xp_conf_dir_tmp = xp_conf_dir;
+      xp_conf_dir = g_strdup_printf("%s/", xp_conf_dir_tmp);
+      g_free(xp_conf_dir_tmp);
+    }
+    return xp_conf_dir;
+  } else {
+    return expand_filename("~/.mcabber/otr/");
+  }
+}
+
+static ConnContext *otr_get_context(const char *buddy)
+{
+  int null = 0;
+  ConnContext *ctx;
+  char *lowcasebuddy = g_strdup(buddy);
+
+  mc_strtolower(lowcasebuddy);
+  ctx = otrl_context_find(userstate, lowcasebuddy, account, OTR_PROTOCOL_NAME,
+                          1, &null, NULL, NULL);
+  g_free(lowcasebuddy);
+  return ctx;
+}
+
+static void otr_message_disconnect(ConnContext *ctx)
+{
+  if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
+    cb_gone_insecure(NULL, ctx);
+  otrl_message_disconnect(userstate, &ops, NULL, ctx->accountname,
+                          ctx->protocol, ctx->username);
+}
+
+static void otr_startstop(const char *buddy, int start)
+{
+  char *msg = NULL;
+  ConnContext *ctx = otr_get_context(buddy);
+
+  if (!userstate || !ctx)
+    return;
+
+  if (start && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
+    otr_message_disconnect(ctx);
+
+  if (start) {
+    OtrlPolicy policy = cb_policy(NULL, ctx);
+    if (policy == plain) {
+      scr_LogPrint(LPRINT_LOGNORM, "The OTR policy for this user is set to"
+      " plain. You have to change it first.");
+      return;
+    }
+    msg = otrl_proto_default_query_msg(ctx->accountname, policy);
+    cb_inject_message(NULL, ctx->accountname, ctx->protocol, ctx->username,
+                      msg);
+    free (msg);
+  }
+  else
+    otr_message_disconnect(ctx);
+}
+
+void otr_establish(const char *buddy)
+{
+  otr_startstop(buddy, 1);
+}
+
+void otr_disconnect(const char *buddy)
+{
+  otr_startstop(buddy, 0);
+}
+
+void otr_fingerprint(const char *buddy, const char *trust)
+{
+  char fpr[45], *tr;
+  ConnContext *ctx = otr_get_context(buddy);
+  if (!userstate || !ctx)
+    return;
+
+  if (!ctx->active_fingerprint || !ctx->active_fingerprint->fingerprint) {
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "No active fingerprint - start OTR for this buddy first.");
+    return;
+  }
+
+  otrl_privkey_hash_to_human(fpr, ctx->active_fingerprint->fingerprint);
+  if (trust) {
+    if (strcmp(fpr, trust) == 0)
+      otrl_context_set_trust(ctx->active_fingerprint, "trust");
+    else
+      otrl_context_set_trust(ctx->active_fingerprint, NULL);
+  }
+
+  tr = ctx->active_fingerprint->trust;
+  scr_LogPrint(LPRINT_LOGNORM, "%s [%44s]: %s", ctx->username, fpr,
+               tr && *tr ?  "trusted" : "untrusted");
+  cb_write_fingerprints(NULL);
+}
+
+static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx)
+{
+  OtrlTLV *tlv = NULL;
+  char *sbuf = NULL;
+  NextExpectedSMP nextMsg = ctx->smstate->nextExpected;
+
+  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1);
+  if (tlv) {
+    if (nextMsg != OTRL_SMP_EXPECT1)
+      otr_smp_abort(ctx->username);
+    else {
+      sbuf = g_strdup_printf("OTR: Received SMP Initiation. "
+                             "Answer with /otr smpr %s $secret",
+                             ctx->username);
+    }
+  }
+  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2);
+  if (tlv) {
+    if (nextMsg != OTRL_SMP_EXPECT2)
+      otr_smp_abort(ctx->username);
+    else {
+      sbuf = g_strdup("OTR: Received SMP Response.");
+      /* If we received TLV2, we will send TLV3 and expect TLV4 */
+      ctx->smstate->nextExpected = OTRL_SMP_EXPECT4;
+    }
+  }
+  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3);
+  if (tlv) {
+    if (nextMsg != OTRL_SMP_EXPECT3)
+      otr_smp_abort(ctx->username);
+    else {
+      /* If we received TLV3, we will send TLV4
+       * We will not expect more messages, so prepare for next SMP */
+      ctx->smstate->nextExpected = OTRL_SMP_EXPECT1;
+      /* Report result to user */
+      if (ctx->active_fingerprint && ctx->active_fingerprint->trust &&
+         *ctx->active_fingerprint->trust != '\0')
+        sbuf = g_strdup("OTR: SMP succeeded");
+      else
+        sbuf = g_strdup("OTR: SMP failed");
+    }
+  }
+  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4);
+  if (tlv) {
+    if (nextMsg != OTRL_SMP_EXPECT4)
+      otr_smp_abort(ctx->username);
+    else {
+      /* We will not expect more messages, so prepare for next SMP */
+      ctx->smstate->nextExpected = OTRL_SMP_EXPECT1;
+      /* Report result to user */
+      if (ctx->active_fingerprint && ctx->active_fingerprint->trust &&
+         *ctx->active_fingerprint->trust != '\0')
+        sbuf = g_strdup("OTR: SMP succeeded");
+      else
+        sbuf = g_strdup("OTR: SMP failed");
+    }
+  }
+  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT);
+  if (tlv) {
+    /* The message we are waiting for will not arrive, so reset
+     * and prepare for the next SMP */
+    sbuf = g_strdup("OTR: SMP aborted by your buddy");
+    ctx->smstate->nextExpected = OTRL_SMP_EXPECT1;
+  }
+
+  if (sbuf) {
+    scr_WriteIncomingMessage(ctx->username, sbuf, 0, HBB_PREFIX_INFO, 0);
+    g_free(sbuf);
+  }
+}
+
+/*
+ * returns whether a otr_message was received
+ * sets *otr_data to NULL, when it was an internal otr message
+ */
+int otr_receive(char **otr_data, const char *buddy, int *free_msg)
+{
+  int ignore_message;
+  char *newmessage = NULL;
+  OtrlTLV *tlvs = NULL;
+  OtrlTLV *tlv = NULL;
+  ConnContext *ctx;
+
+  ctx = otr_get_context(buddy);
+  *free_msg = 0;
+  ignore_message = otrl_message_receiving(userstate, &ops, NULL,
+                                          ctx->accountname, ctx->protocol,
+                                          ctx->username, *otr_data,
+                                          &newmessage, &tlvs,NULL, NULL);
+
+
+  tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED);
+  if (tlv) {
+    /* Notify the user that the other side disconnected. */
+    if (ctx) {
+      cb_gone_insecure(NULL, ctx);
+      otr_disconnect(ctx->username);
+    }
+  }
+
+  otr_handle_smp_tlvs(tlvs, ctx);
+
+  if (tlvs != NULL)
+    otrl_tlv_free(tlvs);
+
+  if (ignore_message)
+    *otr_data = NULL;
+
+  if (!ignore_message && newmessage) {
+    *free_msg = 1;
+    *otr_data = html_strip(newmessage);
+    otrl_message_free(newmessage);
+    if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
+      return 1;
+  }
+  return 0;
+}
+
+int otr_send(char **msg, const char *buddy)
+{
+  gcry_error_t err;
+  char *newmessage = NULL;
+  char *htmlmsg;
+  ConnContext *ctx = otr_get_context(buddy);
+
+  if (ctx->msgstate == OTRL_MSGSTATE_PLAINTEXT)
+    err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname,
+                               ctx->protocol, ctx->username, *msg, NULL,
+                               &newmessage, NULL, NULL);
+  else {
+    htmlmsg = html_escape(*msg);
+    err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname,
+                               ctx->protocol, ctx->username, htmlmsg, NULL,
+                               &newmessage, NULL, NULL);
+    g_free(htmlmsg);
+  }
+
+  if (err)
+    *msg = NULL; /*something went wrong, don't send the plain-message! */
+
+  if (!err && newmessage) {
+    *msg = g_strdup(newmessage);
+    otrl_message_free(newmessage);
+    if (cb_policy(NULL, ctx) & OTRL_POLICY_REQUIRE_ENCRYPTION ||
+        ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
+      return 1;
+  }
+  return 0;
+}
+
+/* Prints OTR connection state */
+void otr_print_info(const char *buddy)
+{
+  const char *state, *auth, *policy;
+  ConnContext *ctx = otr_get_context(buddy);
+  OtrlPolicy p = cb_policy(ctx->app_data, ctx);
+
+  if (!userstate || !ctx)
+    return;
+
+  switch (ctx->msgstate) {
+    case OTRL_MSGSTATE_PLAINTEXT: state = "plaintext"; break;
+    case OTRL_MSGSTATE_ENCRYPTED:
+     switch (ctx->protocol_version) {
+       case 1: state = "encrypted V1"; break;
+       case 2: state = "encrypted V2"; break;
+       default:state = "encrypted";
+     };
+     break;
+    case OTRL_MSGSTATE_FINISHED:  state = "finished";  break;
+    default:                      state = "unknown state";
+  }
+  switch (ctx->auth.authstate) {
+    case OTRL_AUTHSTATE_NONE:
+      switch (ctx->otr_offer) {
+        case OFFER_NOT:      auth = "no offer sent";  break;
+        case OFFER_SENT:     auth = "offer sent";     break;
+        case OFFER_ACCEPTED: auth = "offer accepted"; break;
+        case OFFER_REJECTED: auth = "offer rejected"; break;
+        default:             auth = "unknown auth";
+      }
+      break;
+    case OTRL_AUTHSTATE_AWAITING_DHKEY:
+      auth = "awaiting D-H key";          break;
+    case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
+      auth = "awaiting reveal signature"; break;
+    case OTRL_AUTHSTATE_AWAITING_SIG:
+      auth = "awaiting signature";        break;
+    case OTRL_AUTHSTATE_V1_SETUP:
+      auth = "v1 setup";                  break;
+    default:
+      auth = "unknown auth";
+  }
+  if (p == OTRL_POLICY_NEVER)
+    policy = "plain";
+  else if (p == (OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1))
+    policy = "opportunistic";
+  else if (p == (OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1))
+    policy = "manual";
+  else if (p == (OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1))
+    policy = "always";
+  else
+    policy = "unknown";
+
+  scr_LogPrint(LPRINT_LOGNORM, "%s: %s (%s) [%s]",
+               ctx->username, state, auth, policy);
+}
+
+static ConnContext *otr_context_encrypted(const char *buddy)
+{
+  ConnContext *ctx = otr_get_context(buddy);
+
+  if (!userstate || !ctx  || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED){
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "You have to start an OTR channel with %s before you can "
+                 "use SMP.", buddy);
+    return NULL;
+  }
+
+  return ctx;
+}
+
+void otr_smp_query(const char *buddy, const char *secret)
+{
+  ConnContext *ctx = otr_context_encrypted(buddy);
+
+  if (!secret) {
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Using SMP without a secret isn't a good idea.");
+    return;
+  }
+
+  if (ctx) {
+    otrl_message_initiate_smp(userstate, &ops, NULL, ctx,
+                              (const unsigned char *)secret,
+                              strlen(secret));
+    scr_WriteIncomingMessage(ctx->username,
+                             "OTR: Socialist Millionaires' Protocol "
+                             "initiated.", 0, HBB_PREFIX_INFO, 0);
+  }
+}
+
+void otr_smp_respond(const char *buddy, const char *secret)
+{
+  ConnContext *ctx = otr_context_encrypted(buddy);
+
+  if (!secret) {
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Using SMP without a secret isn't a good idea.");
+    return;
+  }
+
+  if (ctx) {
+    if (!ctx->smstate->secret) {
+      scr_LogPrint(LPRINT_LOGNORM,
+                   "Don't call smpr until you have received an SMP "
+                   "Initiation!");
+      return;
+    }
+    otrl_message_respond_smp(userstate, &ops, NULL, ctx,
+                             (const unsigned char *)secret,
+                             strlen(secret));
+    scr_WriteIncomingMessage(ctx->username,
+                             "OTR: Socialist Millionaires' Protocol: "
+                             "response sent", 0, HBB_PREFIX_INFO, 0);
+  }
+}
+
+void otr_smp_abort(const char *buddy)
+{
+  ConnContext *ctx = otr_context_encrypted(buddy);
+
+  if (ctx) {
+    otrl_message_abort_smp(userstate, &ops, NULL, ctx);
+    scr_WriteIncomingMessage(ctx->username,
+                             "OTR: Socialist Millionaires' Protocol aborted.",
+                             0, HBB_PREFIX_INFO, 0);
+  }
+}
+
+void otr_key(void)
+{
+  OtrlPrivKey *key;
+  char readable[45] = "";
+
+  if(!userstate)
+    return;
+  for (key = userstate->privkey_root; key; key = key->next) {
+    otrl_privkey_fingerprint(userstate, readable, key->accountname,
+                             key->protocol);
+    scr_LogPrint(LPRINT_LOGNORM, "%s: %s", key->accountname, readable);
+  }
+}
+
+/* Return the OTR policy for the given context. */
+static OtrlPolicy cb_policy(void *opdata, ConnContext *ctx)
+{
+  enum otr_policy p = settings_otr_getpolicy(NULL);
+
+  if(ctx)
+    if(settings_otr_getpolicy(ctx->username))
+      p = settings_otr_getpolicy(ctx->username);
+
+  switch (p) {
+    case plain:
+      return OTRL_POLICY_NEVER;
+    case opportunistic:
+      return OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1;
+    case manual:
+      return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1;
+    case always:
+      return OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1;
+  }
+
+  return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1;
+}
+
+/* Create a private key for the given accountname/protocol if
+ * desired. */
+static void cb_create_privkey(void *opdata, const char *accountname,
+                              const char *protocol)
+{
+  gcry_error_t e;
+  char *root;
+
+  scr_LogPrint(LPRINT_LOGNORM,
+               "Generating new OTR key for %s. This may take a while...",
+               accountname);
+  scr_DoUpdate();
+
+  e = otrl_privkey_generate(userstate, keyfile, accountname, protocol);
+
+  if (e) {
+    root = otr_get_dir();
+    scr_LogPrint(LPRINT_LOGNORM, "OTR key generation failed! Please mkdir "
+                 "%s if you want to use otr encryption.", root);
+    g_free(root);
+  }
+  else
+    scr_LogPrint(LPRINT_LOGNORM, "OTR key generated.");
+}
+
+/* Report whether you think the given user is online.  Return 1 if
+ * you think he is, 0 if you think he isn't, -1 if you're not sure.
+ * If you return 1, messages such as heartbeats or other
+ * notifications may be sent to the user, which could result in "not
+ * logged in" errors if you're wrong. */
+static int cb_is_logged_in(void *opdata, const char *accountname,
+                           const char *protocol, const char *recipient)
+{
+  int ret = (roster_getstatus(recipient, NULL) != offline);
+  return ret;
+}
+
+/* Send the given IM to the given recipient from the given
+ * accountname/protocol. */
+static void cb_inject_message(void *opdata, const char *accountname,
+                              const char *protocol, const char *recipient,
+                              const char *message)
+{
+  if (roster_gettype(recipient) == ROSTER_TYPE_USER)
+    xmpp_send_msg(recipient, message, ROSTER_TYPE_USER, "", TRUE, NULL,
+                  LM_MESSAGE_SUB_TYPE_NOT_SET, NULL);
+}
+
+/* Display a notification message for a particular
+ * accountname / protocol / username conversation. */
+static void cb_notify(void *opdata, OtrlNotifyLevel level,
+                      const char *accountname, const char *protocol,
+                      const char *username, const char *title,
+                      const char *primary, const char *secondary)
+{
+  char *type;
+  char *sbuf = NULL;
+  switch (level) {
+    case OTRL_NOTIFY_ERROR:   type = "error";   break;
+    case OTRL_NOTIFY_WARNING: type = "warning"; break;
+    case OTRL_NOTIFY_INFO:    type = "info";    break;
+    default:                  type = "unknown";
+  }
+  sbuf = g_strdup_printf("OTR %s:%s\n%s\n%s",type,title, primary, secondary);
+  scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0);
+  g_free(sbuf);
+}
+
+/* Display an OTR control message for a particular
+ * accountname / protocol / username conversation.  Return 0 if you are able
+ * to successfully display it.  If you return non-0 (or if this
+ * function is NULL), the control message will be displayed inline,
+ * as a received message, or else by using the above notify()
+ * callback. */
+static int cb_display_otr_message(void *opdata, const char *accountname,
+                                  const char *protocol, const char *username,
+                                  const char *msg)
+{
+  char *strippedmsg = html_strip(msg);
+  scr_WriteIncomingMessage(username, strippedmsg, 0, HBB_PREFIX_INFO, 0);
+  g_free(strippedmsg);
+  return 0;
+}
+
+/* When the list of ConnContexts changes (including a change in
+ * state), this is called so the UI can be updated. */
+static void cb_update_context_list(void *opdata)
+{
+  /*maybe introduce new status characters for mcabber,
+   * then use this function (?!)*/
+}
+
+/* Return a newly allocated string containing a human-friendly name
+ * for the given protocol id */
+static const char *cb_protocol_name(void *opdata, const char *protocol)
+{
+  return protocol;
+}
+
+/* Deallocate a string allocated by protocol_name */
+static void cb_protocol_name_free (void *opdata, const char *protocol_name)
+{
+  /* We didn't allocated memory, so we don't have to free anything :p */
+}
+
+/* A new fingerprint for the given user has been received. */
+static void cb_new_fingerprint(void *opdata, OtrlUserState us,
+                               const char *accountname, const char *protocol,
+                               const char *username,
+                               unsigned char fingerprint[20])
+{
+  char *sbuf = NULL;
+  char readable[45];
+
+  otrl_privkey_hash_to_human(readable, fingerprint);
+  sbuf = g_strdup_printf("OTR: new fingerprint: %s", readable);
+  scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0);
+  g_free(sbuf);
+}
+
+/* The list of known fingerprints has changed.  Write them to disk. */
+static void cb_write_fingerprints(void *opdata)
+{
+  otrl_privkey_write_fingerprints(userstate, fprfile);
+}
+
+/* A ConnContext has entered a secure state. */
+static void cb_gone_secure(void *opdata, ConnContext *context)
+{
+  scr_WriteIncomingMessage(context->username, "OTR: channel established", 0,
+                           HBB_PREFIX_INFO, 0);
+}
+
+/* A ConnContext has left a secure state. */
+static void cb_gone_insecure(void *opdata, ConnContext *context)
+{
+  scr_WriteIncomingMessage(context->username, "OTR: channel closed", 0,
+                           HBB_PREFIX_INFO, 0);
+}
+
+/* We have completed an authentication, using the D-H keys we
+ * already knew.  is_reply indicates whether we initiated the AKE. */
+static void cb_still_secure(void *opdata, ConnContext *context, int is_reply)
+{
+  scr_WriteIncomingMessage(context->username, "OTR: channel reestablished", 0,
+                           HBB_PREFIX_INFO, 0);
+}
+
+/* Log a message.  The passed message will end in "\n". */
+static void cb_log_message(void *opdata, const char *message)
+{
+  scr_LogPrint(LPRINT_DEBUG, "OTR: %s", message);
+}
+
+/* Find the maximum message size supported by this protocol. */
+static int cb_max_message_size(void *opdata, ConnContext *context)
+{
+  return 8192;
+}
+
+int otr_enabled(void)
+{
+  return otr_is_enabled;
+}
+
+#else /* !HAVE_LIBOTR */
+
+int otr_enabled(void)
+{
+  return FALSE;
+}
+
+#endif /* HAVE_LIBOTR */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/otr.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,42 @@
+#ifndef __MCABBER_OTR_H__
+#define __MCABBER_OTR_H__ 1
+
+#include <mcabber/config.h>
+
+#ifdef HAVE_LIBOTR
+
+#include <libotr/proto.h>
+#include <libotr/message.h>
+#include <libotr/privkey.h>
+
+enum otr_policy {
+  plain,
+  opportunistic,
+  manual,
+  always
+};
+
+void otr_init(const char *jid);
+void otr_terminate(void);
+
+void otr_establish  (const char * buddy);
+void otr_disconnect (const char * buddy);
+void otr_fingerprint(const char * buddy, const char * trust);
+void otr_print_info (const char * buddy);
+
+void otr_smp_query  (const char * buddy, const char * secret);
+void otr_smp_respond(const char * buddy, const char * secret);
+void otr_smp_abort  (const char * buddy);
+
+void otr_key        (void);
+
+int  otr_receive    (char **otr_data, const char * buddy, int * free_msg);
+int  otr_send       (char **msg, const char *buddy);
+
+#endif /* HAVE_LIBOTR */
+
+int  otr_enabled    (void);
+
+#endif /* __MCABBER_OTR_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/pgp.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,475 @@
+/*
+ * pgp.c        -- PGP utility functions
+ *
+ * Copyright (C) 2006-2009 Mikael Berthe <mikael@lilotux.net>
+ * Some parts inspired by centericq (impgp.cc)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <config.h>
+
+#ifdef HAVE_GPGME
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <locale.h>
+#include <sys/mman.h>
+#include <glib.h>
+
+#include "pgp.h"
+#include "logprint.h"
+
+#define MIN_GPGME_VERSION "1.0.0"
+
+static struct gpg_struct
+{
+  int enabled;
+  char *private_key;
+  char *passphrase;
+} gpg;
+
+
+//  gpg_init(priv_key, passphrase)
+// Initialize the GPG sub-systems.  This function must be invoked early.
+// Note: priv_key & passphrase are optional, they can be set later.
+// This function returns 0 if gpgme is available and initialized;
+// if not it returns the gpgme error code.
+int gpg_init(const char *priv_key, const char *passphrase)
+{
+  gpgme_error_t err;
+
+  // Check for version and OpenPGP protocol support.
+  if (!gpgme_check_version(MIN_GPGME_VERSION)) {
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "GPGME initialization error: Bad library version");
+    return -1;
+  }
+
+  err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME initialization error: %s", gpgme_strerror(err));
+    return err;
+  }
+
+  // Set the locale information.
+  gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
+  gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
+
+  // Store private data.
+  gpg_set_private_key(priv_key);
+  gpg_set_passphrase(passphrase);
+
+  gpg.enabled = 1;
+  return 0;
+}
+
+//  gpg_terminate()
+// Destroy data and free memory.
+void gpg_terminate(void)
+{
+  gpg.enabled = 0;
+  gpg_set_passphrase(NULL);
+  gpg_set_private_key(NULL);
+}
+
+//  gpg_set_passphrase(passphrase)
+// Set the current passphrase (use NULL to erase it).
+void gpg_set_passphrase(const char *passphrase)
+{
+  // Remove current passphrase
+  if (gpg.passphrase) {
+    ssize_t len = strlen(gpg.passphrase);
+    memset(gpg.passphrase, 0, len);
+    munlock(gpg.passphrase, len);
+    g_free(gpg.passphrase);
+  }
+  if (passphrase) {
+    gpg.passphrase = g_strdup(passphrase);
+    mlock(gpg.passphrase, strlen(gpg.passphrase));
+  } else {
+    gpg.passphrase = NULL;
+  }
+}
+
+//  gpg_set_private_key(keyid)
+// Set the current private key id (use NULL to unset it).
+void gpg_set_private_key(const char *priv_keyid)
+{
+  g_free(gpg.private_key);
+  if (priv_keyid)
+    gpg.private_key = g_strdup(priv_keyid);
+  else
+    gpg.private_key = NULL;
+}
+
+//  strip_header_footer(data)
+// Remove PGP header & footer from data.
+// Return a new string, or NULL.
+// The string must be freed by the caller with g_free() when no longer needed.
+static char *strip_header_footer(const char *data)
+{
+  char *p, *q;
+
+  if (!data)
+    return NULL;
+
+  // p: beginning of real data
+  // q: end of real data
+
+  // Strip header (to the first empty line)
+  p = strstr(data, "\n\n");
+  if (!p)
+    return g_strdup(data);
+
+  // Strip footer
+  // We want to remove the last lines, until the line beginning with a '-'
+  p += 2;
+  for (q = p ; *q; q++) ;
+  // (q is at the end of data now)
+  for (q--; q > p && (*q != '\n' || *(q+1) != '-'); q--) ;
+
+  if (q <= p)
+    return NULL; // Shouldn't happen...
+
+  return g_strndup(p, q-p);
+}
+
+// GCC ignores casts to void, thus we need to hack around that
+static inline void ignore(void*x) {}
+
+//  passphrase_cb()
+// GPGME passphrase callback function.
+static gpgme_error_t passphrase_cb(void *hook, const char *uid_hint,
+                       const char *passphrase_info, int prev_was_bad, int fd)
+{
+  ssize_t len;
+
+  // Abort if we do not have the password.
+  if (!gpg.passphrase) {
+    ignore((void*)write(fd, "\n", 1)); // We have an error anyway, thus it does
+                                       // not matter if we fail again.
+    return gpg_error(GPG_ERR_CANCELED);
+  }
+
+  // Write the passphrase to the file descriptor.
+  len = strlen(gpg.passphrase);
+  if (write(fd, gpg.passphrase, len) != len)
+    return gpg_error(GPG_ERR_CANCELED);
+  if (write(fd, "\n", 1) != 1)
+    return gpg_error(GPG_ERR_CANCELED);
+
+  return 0; // Success
+}
+
+//  gpg_verify(gpg_data, text, *sigsum)
+// Verify that gpg_data is a correct signature for text.
+// Return the key id (or fingerprint), and set *sigsum to
+// the gpgme signature summary value.
+// The returned string must be freed with g_free() after use.
+char *gpg_verify(const char *gpg_data, const char *text,
+                 gpgme_sigsum_t *sigsum)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t data_sign, data_text;
+  char *data;
+  char *verified_key = NULL;
+  gpgme_key_t key;
+  gpgme_error_t err;
+  const char prefix[] = "-----BEGIN PGP SIGNATURE-----\n\n";
+  const char suffix[] = "\n-----END PGP SIGNATURE-----\n";
+
+  // Reset the summary.
+  *sigsum = 0;
+
+  if (!gpg.enabled)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+
+  // Surround the given data with the prefix & suffix
+  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
+  strcpy(data, prefix);
+  strcat(data, gpg_data);
+  strcat(data, suffix);
+
+  err = gpgme_data_new_from_mem(&data_sign, data, strlen(data), 0);
+  if (!err) {
+    err = gpgme_data_new_from_mem(&data_text, text, strlen(text), 0);
+    if (!err) {
+      err = gpgme_op_verify(ctx, data_sign, data_text, 0);
+      if (!err) {
+        gpgme_verify_result_t vr = gpgme_op_verify_result(ctx);
+        if (vr && vr->signatures) {
+          char *r = vr->signatures->fpr;
+          // Found the fingerprint.  Let's try to get the key id.
+          if (!gpgme_get_key(ctx, r, &key, 0) && key) {
+            r = key->subkeys->keyid;
+            gpgme_key_release(key);
+          }
+          // r is a static variable, let's copy it.
+          verified_key = g_strdup(r);
+          *sigsum = vr->signatures->summary;
+          // For some reason summary could be 0 when status is 0 too,
+          // which means the signature is valid...
+          if (!*sigsum && !vr->signatures->status)
+            *sigsum = GPGME_SIGSUM_GREEN;
+        }
+      }
+      gpgme_data_release(data_text);
+    }
+    gpgme_data_release(data_sign);
+  }
+  if (err)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME verification error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  g_free(data);
+  return verified_key;
+}
+
+//  gpg_sign(gpg_data)
+// Return a signature of gpg_data (or NULL).
+// The returned string must be freed with g_free() after use.
+char *gpg_sign(const char *gpg_data)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t in, out;
+  char *p;
+  char *signed_data = NULL;
+  size_t nread;
+  gpgme_key_t key;
+  gpgme_error_t err;
+
+  if (!gpg.enabled || !gpg.private_key)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+  gpgme_set_textmode(ctx, 0);
+  gpgme_set_armor(ctx, 1);
+
+  p = getenv("GPG_AGENT_INFO");
+  if (!(p && strchr(p, ':')))
+    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);
+
+  err = gpgme_get_key(ctx, gpg.private_key, &key, 1);
+  if (err || !key) {
+    scr_LogPrint(LPRINT_LOGNORM, "GPGME error: private key not found");
+    gpgme_release(ctx);
+    return NULL;
+  }
+
+  gpgme_signers_clear(ctx);
+  gpgme_signers_add(ctx, key);
+  gpgme_key_release(key);
+  err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
+  if (!err) {
+    err = gpgme_data_new(&out);
+    if (!err) {
+      err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH);
+      if (!err) {
+        signed_data = gpgme_data_release_and_get_mem(out, &nread);
+        if (signed_data) {
+          // We need to add a trailing NULL
+          char *dd = g_strndup(signed_data, nread);
+          free(signed_data);
+          signed_data = strip_header_footer(dd);
+          g_free(dd);
+        }
+      } else {
+        gpgme_data_release(out);
+      }
+    }
+    gpgme_data_release(in);
+  }
+  if (err && err != GPG_ERR_CANCELED)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME signature error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  return signed_data;
+}
+
+//  gpg_decrypt(gpg_data)
+// Return decrypted gpg_data (or NULL).
+// The returned string must be freed with g_free() after use.
+char *gpg_decrypt(const char *gpg_data)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t in, out;
+  char *p, *data;
+  char *decrypted_data = NULL;
+  size_t nread;
+  gpgme_error_t err;
+  const char prefix[] = "-----BEGIN PGP MESSAGE-----\n\n";
+  const char suffix[] = "\n-----END PGP MESSAGE-----\n";
+
+  if (!gpg.enabled)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+
+  p = getenv("GPG_AGENT_INFO");
+  if (!(p && strchr(p, ':')))
+    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);
+
+  // Surround the given data with the prefix & suffix
+  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
+  strcpy(data, prefix);
+  strcat(data, gpg_data);
+  strcat(data, suffix);
+
+  err = gpgme_data_new_from_mem(&in, data, strlen(data), 0);
+  if (!err) {
+    err = gpgme_data_new(&out);
+    if (!err) {
+      err = gpgme_op_decrypt(ctx, in, out);
+      if (!err) {
+        decrypted_data = gpgme_data_release_and_get_mem(out, &nread);
+        if (decrypted_data) {
+          // We need to add a trailing NULL
+          char *dd = g_strndup(decrypted_data, nread);
+          free(decrypted_data);
+          decrypted_data = dd;
+        }
+      } else {
+        gpgme_data_release(out);
+      }
+    }
+    gpgme_data_release(in);
+  }
+  if (err && err != GPG_ERR_CANCELED)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME decryption error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  g_free(data);
+  return decrypted_data;
+}
+
+//  gpg_encrypt(gpg_data, keyid)
+// Return encrypted gpg_data with the key keyid (or NULL).
+// The returned string must be freed with g_free() after use.
+char *gpg_encrypt(const char *gpg_data, const char *keyid)
+{
+  gpgme_ctx_t ctx;
+  gpgme_data_t in, out;
+  char *encrypted_data = NULL, *edata;
+  size_t nread;
+  gpgme_key_t key;
+  gpgme_error_t err;
+
+  if (!gpg.enabled)
+    return NULL;
+
+  err = gpgme_new(&ctx);
+  if (err) {
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME error: %s", gpgme_strerror(err));
+    return NULL;
+  }
+
+  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
+  gpgme_set_textmode(ctx, 0);
+  gpgme_set_armor(ctx, 1);
+
+  err = gpgme_get_key(ctx, keyid, &key, 0);
+  if (!err && key) {
+    gpgme_key_t keys[] = { key, 0 };
+    err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
+    if (!err) {
+      err = gpgme_data_new(&out);
+      if (!err) {
+        err = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
+        if (!err)
+          encrypted_data = gpgme_data_release_and_get_mem(out, &nread);
+        else
+          gpgme_data_release(out);
+      }
+      gpgme_data_release(in);
+    }
+    gpgme_key_release(key);
+  } else {
+    scr_LogPrint(LPRINT_LOGNORM, "GPGME encryption error: key not found");
+    err = 0;
+  }
+  if (err && err != GPG_ERR_CANCELED)
+    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
+                 "GPGME encryption error: %s", gpgme_strerror(err));
+  gpgme_release(ctx);
+  edata = strip_header_footer(encrypted_data);
+  if (encrypted_data)
+    free(encrypted_data);
+  return edata;
+}
+
+//  gpg_test_passphrase()
+// Test the current gpg.passphrase with gpg.private_key.
+// If the test doesn't succeed, the passphrase is cleared and a non-null
+// value is returned.
+int gpg_test_passphrase(void)
+{
+  char *s;
+
+  if (!gpg.private_key)
+    return -1; // No private key...
+
+  s = gpg_sign("test");
+  if (s) {
+    free(s);
+    return 0; // Ok, test successful
+  }
+  // The passphrase is wrong (if provided)
+  gpg_set_passphrase(NULL);
+  return -1;
+}
+
+int gpg_enabled(void)
+{
+  return gpg.enabled;
+}
+
+#else  /* not HAVE_GPGME */
+
+int gpg_enabled(void)
+{
+  return 0;
+}
+
+#endif /* HAVE_GPGME */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/pgp.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,29 @@
+#ifndef __MCABBER_PGP_H__
+#define __MCABBER_PGP_H__ 1
+
+#include <mcabber/config.h>
+
+#ifdef HAVE_GPGME
+
+#define GPGME_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_USER_1
+#include <gpgme.h>
+
+int   gpg_init(const char *priv_key, const char *passphrase);
+void  gpg_terminate(void);
+void  gpg_set_passphrase(const char *passphrase);
+void  gpg_set_private_key(const char *priv_keyid);
+char *gpg_verify(const char *gpg_data, const char *text,
+                 gpgme_sigsum_t *sigsum);
+char *gpg_sign(const char *gpg_data);
+char *gpg_decrypt(const char *gpg_data);
+char *gpg_encrypt(const char *gpg_data, const char *keyid);
+
+int   gpg_test_passphrase(void);
+
+#endif /* HAVE_GPGME */
+
+int gpg_enabled(void);
+
+#endif /* __MCABBER_PGP_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/roster.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,1624 @@
+/*
+ * roster.c     -- Local roster implementation
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+
+#include "roster.h"
+#include "utils.h"
+#include "hooks.h"
+
+extern void hlog_save_state(void);
+
+char *strrole[] = {   /* Should match enum in roster.h */
+  "none",
+  "moderator",
+  "participant",
+  "visitor"
+};
+
+char *straffil[] = {  /* Should match enum in roster.h */
+  "none",
+  "owner",
+  "admin",
+  "member",
+  "outcast"
+};
+
+char *strprintstatus[] = {  /* Should match enum in roster.h */
+  "default",
+  "none",
+  "in_and_out",
+  "all"
+};
+
+char *strautowhois[] = {    /* Should match enum in roster.h */
+  "default",
+  "off",
+  "on",
+};
+
+/* Resource structure */
+
+typedef struct {
+  gchar *name;
+  gchar prio;
+  enum imstatus status;
+  gchar *status_msg;
+  time_t status_timestamp;
+  enum imrole role;
+  enum imaffiliation affil;
+  gchar *realjid;       /* for chatrooms, if buddy's real jid is known */
+  guint events;
+  char *caps;
+#ifdef JEP0022
+  struct jep0022 jep22;
+#endif
+#ifdef JEP0085
+  struct jep0085 jep85;
+#endif
+#ifdef HAVE_GPGME
+  struct pgp_data pgpdata;
+#endif
+} res;
+
+/* This is a private structure type for the roster */
+
+typedef struct {
+  gchar *name;
+  gchar *jid;
+  guint type;
+  enum subscr subscription;
+  GSList *resource;
+
+  /* For groupchats */
+  gchar *nickname;
+  gchar *topic;
+  guint inside_room;
+  guint print_status;
+  guint auto_whois;
+
+  /* on_server is TRUE if the item is present on the server roster */
+  guint on_server;
+
+  /* To keep track of last status message */
+  gchar *offline_status_message;
+
+  /* Flag used for the UI */
+  guint flags;
+
+  // list: user -> points to his group; group -> points to its users list
+  GSList *list;
+} roster;
+
+
+/* ### Variables ### */
+
+static guchar display_filter;
+static GSList *groups;
+static GSList *unread_list;
+static GHashTable *unread_jids;
+GList *buddylist;
+GList *current_buddy;
+GList *alternate_buddy;
+
+static roster roster_special;
+
+static int  unread_jid_del(const char *jid);
+
+#define DFILTER_ALL     63
+#define DFILTER_ONLINE  62
+
+
+/* ### Initialization ### */
+
+void roster_init(void)
+{
+  roster_special.name = SPECIAL_BUFFER_STATUS_ID;
+  roster_special.type = ROSTER_TYPE_SPECIAL;
+}
+
+/* ### Resources functions ### */
+
+static inline void free_resource_data(res *p_res)
+{
+  if (!p_res)
+    return;
+  g_free((gchar*)p_res->status_msg);
+  g_free((gchar*)p_res->name);
+  g_free((gchar*)p_res->realjid);
+#ifdef JEP0022
+  g_free(p_res->jep22.last_msgid_sent);
+  g_free(p_res->jep22.last_msgid_rcvd);
+#endif
+#ifdef HAVE_GPGME
+  g_free(p_res->pgpdata.sign_keyid);
+#endif
+  g_free(p_res->caps);
+  g_free(p_res);
+}
+
+static void free_all_resources(GSList **reslist)
+{
+  GSList *lip;
+
+  for (lip = *reslist; lip ; lip = g_slist_next(lip))
+    free_resource_data((res*)lip->data);
+  // Free all nodes but the first (which is static)
+  g_slist_free(*reslist);
+  *reslist = NULL;
+}
+
+// Resources are sorted in ascending order
+static gint resource_compare_prio(res *a, res *b) {
+  //return (a->prio - b->prio);
+  if (a->prio < b->prio) return -1;
+  else                   return 1;
+}
+
+//  get_resource(rost, resname)
+// Return a pointer to the resource with name resname, in rost's resources list
+// - if rost has no resources, return NULL
+// - if resname is defined, return the match or NULL
+// - if resname is NULL, the last resource is returned, currently
+//   This could change in the future, because we should return the best one
+//   (priority? last used? and fall back to the first resource)
+//
+static res *get_resource(roster *rost, const char *resname)
+{
+  GSList *p;
+  res *r = NULL;
+
+  for (p = rost->resource; p; p = g_slist_next(p)) {
+    r = p->data;
+    if (resname && !strcmp(r->name, resname))
+      return r;
+  }
+
+  // The last resource is one of the resources with the highest priority,
+  // however, we don't know if it is the more-recently-used.
+  if (!resname) return r;
+  return NULL;
+}
+
+//  get_or_add_resource(rost, resname, priority)
+// - if there is a "resname" resource in rost's resources, return a pointer
+//   on this resource
+// - if not, add the resource, set the name, and return a pointer on this
+//   new resource
+static res *get_or_add_resource(roster *rost, const char *resname, gchar prio)
+{
+  GSList *p;
+  res *nres;
+
+  if (!resname) return NULL;
+
+  for (p = rost->resource; p; p = g_slist_next(p)) {
+    res *r = p->data;
+    if (!strcmp(r->name, resname)) {
+      if (prio != r->prio) {
+        r->prio = prio;
+        rost->resource = g_slist_sort(rost->resource,
+                                      (GCompareFunc)&resource_compare_prio);
+      }
+      return r;
+    }
+  }
+
+  // Resource not found
+  nres = g_new0(res, 1);
+  nres->name = g_strdup(resname);
+  nres->prio = prio;
+  rost->resource = g_slist_insert_sorted(rost->resource, nres,
+                                         (GCompareFunc)&resource_compare_prio);
+  return nres;
+}
+
+static void del_resource(roster *rost, const char *resname)
+{
+  GSList *p;
+  GSList *p_res_elt = NULL;
+  res *p_res;
+
+  if (!resname) return;
+
+  for (p = rost->resource; p; p = g_slist_next(p)) {
+    res *r = p->data;
+    if (!strcmp(r->name, resname))
+      p_res_elt = p;
+  }
+
+  if (!p_res_elt) return;   // Resource not found
+
+  p_res = p_res_elt->data;
+
+  // Keep a copy of the status message when a buddy goes offline
+  if (g_slist_length(rost->resource) == 1) {
+    g_free(rost->offline_status_message);
+    rost->offline_status_message = p_res->status_msg;
+    p_res->status_msg = NULL;
+  }
+
+  // Free allocations and delete resource node
+  free_resource_data(p_res);
+  rost->resource = g_slist_delete_link(rost->resource, p_res_elt);
+  return;
+}
+
+
+/* ### Roster functions ### */
+
+static inline void free_roster_user_data(roster *roster_usr)
+{
+  if (!roster_usr)
+    return;
+  g_free((gchar*)roster_usr->jid);
+  g_free((gchar*)roster_usr->name);
+  g_free((gchar*)roster_usr->nickname);
+  g_free((gchar*)roster_usr->topic);
+  g_free((gchar*)roster_usr->offline_status_message);
+  free_all_resources(&roster_usr->resource);
+  g_free(roster_usr);
+}
+
+// Comparison function used to search in the roster (compares jids and types)
+static gint roster_compare_jid_type(roster *a, roster *b) {
+  if (! (a->type & b->type))
+    return -1; // arbitrary (but should be != 0, of course)
+  return strcasecmp(a->jid, b->jid);
+}
+
+// Comparison function used to search in the roster (compares names and types)
+static gint roster_compare_name_type(roster *a, roster *b) {
+  if (! (a->type & b->type))
+    return -1; // arbitrary (but should be != 0, of course)
+  return strcmp(a->name, b->name);
+}
+
+// Comparison function used to sort the roster (by name)
+static gint roster_compare_name(roster *a, roster *b) {
+  return strcmp(a->name, b->name);
+}
+
+// Finds a roster element (user, group, agent...), by jid or name
+// If roster_type is 0, returns match of any type.
+// Returns the roster GSList element, or NULL if jid/name not found
+GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type)
+{
+  GSList *sl_roster_elt = groups;
+  GSList *resource;
+  roster sample;
+  GCompareFunc comp;
+
+  if (!jidname) return NULL;
+
+  if (!roster_type)
+    roster_type = ROSTER_TYPE_USER  | ROSTER_TYPE_ROOM |
+                  ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP;
+
+  sample.type = roster_type;
+  if (type == jidsearch) {
+    sample.jid = (gchar*)jidname;
+    comp = (GCompareFunc)&roster_compare_jid_type;
+  } else if (type == namesearch) {
+    sample.name = (gchar*)jidname;
+    comp = (GCompareFunc)&roster_compare_name_type;
+  } else
+    return NULL;    // Should not happen...
+
+  while (sl_roster_elt) {
+    roster *roster_elt = (roster*)sl_roster_elt->data;
+    if (roster_type & ROSTER_TYPE_GROUP) {
+      if ((type == namesearch) && !strcmp(jidname, roster_elt->name))
+        return sl_roster_elt;
+    }
+    resource = g_slist_find_custom(roster_elt->list, &sample, comp);
+    if (resource) return resource;
+    sl_roster_elt = g_slist_next(sl_roster_elt);
+  }
+  return NULL;
+}
+
+// Returns pointer to new group, or existing group with that name
+GSList *roster_add_group(const char *name)
+{
+  roster *roster_grp;
+  GSList *p_group;
+
+  // #1 Check name doesn't already exist
+  p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP);
+  if (!p_group) {
+    // #2 Create the group node
+    roster_grp = g_new0(roster, 1);
+    roster_grp->name = g_strdup(name);
+    roster_grp->type = ROSTER_TYPE_GROUP;
+    // #3 Insert (sorted)
+    groups = g_slist_insert_sorted(groups, roster_grp,
+            (GCompareFunc)&roster_compare_name);
+    p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP);
+  }
+  return p_group;
+}
+
+// Returns a pointer to the new user, or existing user with that name
+// Note: if onserver is -1, the flag won't be changed.
+GSList *roster_add_user(const char *jid, const char *name, const char *group,
+                        guint type, enum subscr esub, gint onserver)
+{
+  roster *roster_usr;
+  roster *my_group;
+  GSList *slist;
+
+  if ((type != ROSTER_TYPE_USER) &&
+      (type != ROSTER_TYPE_ROOM) &&
+      (type != ROSTER_TYPE_AGENT)) {
+    // XXX Error message?
+    return NULL;
+  }
+
+  // Let's be arbitrary: default group has an empty name ("").
+  if (!group)  group = "";
+
+  // #1 Check this user doesn't already exist
+  slist = roster_find(jid, jidsearch, 0);
+  if (slist) {
+    char *oldgroupname;
+    // That's an update
+    roster_usr = slist->data;
+    roster_usr->subscription = esub;
+    if (onserver >= 0)
+      buddy_setonserverflag(slist->data, onserver);
+    if (name)
+      buddy_setname(slist->data, (char*)name);
+    // Let's check if the group name has changed
+    oldgroupname = ((roster*)((GSList*)roster_usr->list)->data)->name;
+    if (group && strcmp(oldgroupname, group)) {
+      buddy_setgroup(slist->data, (char*)group);
+      // Note: buddy_setgroup() updates the user lists so we cannot
+      // use slist anymore.
+      return roster_find(jid, jidsearch, 0);
+    }
+    return slist;
+  }
+  // #2 add group if necessary
+  slist = roster_add_group(group);
+  if (!slist) return NULL;
+  my_group = (roster*)slist->data;
+  // #3 Create user node
+  roster_usr = g_new0(roster, 1);
+  roster_usr->jid   = g_strdup(jid);
+  if (name) {
+    roster_usr->name  = g_strdup(name);
+  } else {
+    gchar *p, *str = g_strdup(jid);
+    p = strchr(str, JID_RESOURCE_SEPARATOR);
+    if (p)  *p = '\0';
+    roster_usr->name = g_strdup(str);
+    g_free(str);
+  }
+  if (unread_jid_del(jid)) {
+    roster_usr->flags |= ROSTER_FLAG_MSG;
+    // Append the roster_usr to unread_list
+    unread_list = g_slist_append(unread_list, roster_usr);
+  }
+  roster_usr->type = type;
+  roster_usr->subscription = esub;
+  roster_usr->list = slist;    // (my_group SList element)
+  if (onserver == 1)
+    roster_usr->on_server = TRUE;
+  // #4 Insert node (sorted)
+  my_group->list = g_slist_insert_sorted(my_group->list, roster_usr,
+                                         (GCompareFunc)&roster_compare_name);
+  return roster_find(jid, jidsearch, type);
+}
+
+// Removes user (jid) from roster, frees allocated memory
+void roster_del_user(const char *jid)
+{
+  GSList *sl_user, *sl_group;
+  GSList **sl_group_listptr;
+  roster *roster_usr;
+  GSList *node;
+
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
+  if (sl_user == NULL)
+    return;
+  roster_usr = (roster*)sl_user->data;
+
+  // Remove (if present) from unread messages list
+  node = g_slist_find(unread_list, roster_usr);
+  if (node) unread_list = g_slist_delete_link(unread_list, node);
+  // If there is a pending unread message, keep track of it
+  if (roster_usr->flags & ROSTER_FLAG_MSG)
+    unread_jid_add(roster_usr->jid);
+
+  sl_group = roster_usr->list;
+
+  // Let's free roster_usr memory (jid, name, status message...)
+  free_roster_user_data(roster_usr);
+
+  // That's a little complex, we need to dereference twice
+  sl_group_listptr = &((roster*)(sl_group->data))->list;
+  *sl_group_listptr = g_slist_delete_link(*sl_group_listptr, sl_user);
+
+  // We need to rebuild the list
+  if (current_buddy)
+    buddylist_build();
+  // TODO What we could do, too, is to check if the deleted node is
+  // current_buddy, in which case we could move current_buddy to the
+  // previous (or next) node.
+}
+
+// Free all roster data and call buddylist_build() to free the buddylist.
+void roster_free(void)
+{
+  GSList *sl_grp = groups;
+
+  // Free unread_list
+  if (unread_list) {
+    g_slist_free(unread_list);
+    unread_list = NULL;
+  }
+
+  // Walk through groups
+  while (sl_grp) {
+    roster *roster_grp = (roster*)sl_grp->data;
+    GSList *sl_usr = roster_grp->list;
+    // Walk through this group users
+    while (sl_usr) {
+      roster *roster_usr = (roster*)sl_usr->data;
+      // If there is a pending unread message, keep track of it
+      if (roster_usr->flags & ROSTER_FLAG_MSG)
+        unread_jid_add(roster_usr->jid);
+      // Free roster_usr data (jid, name, status message...)
+      free_roster_user_data(roster_usr);
+      sl_usr = g_slist_next(sl_usr);
+    }
+    // Free group's users list
+    if (roster_grp->list)
+      g_slist_free(roster_grp->list);
+    // Free group's name and jid
+    g_free((gchar*)roster_grp->jid);
+    g_free((gchar*)roster_grp->name);
+    g_free(roster_grp);
+    sl_grp = g_slist_next(sl_grp);
+  }
+  // Free groups list
+  if (groups) {
+    g_slist_free(groups);
+    groups = NULL;
+    // Update (i.e. free) buddylist
+    if (buddylist)
+      buddylist_build();
+  }
+}
+
+//  roster_setstatus()
+// Note: resname, role, affil and realjid are for room members only
+void roster_setstatus(const char *jid, const char *resname, gchar prio,
+                      enum imstatus bstat, const char *status_msg,
+                      time_t status_time,
+                      enum imrole role, enum imaffiliation affil,
+                      const char *realjid)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+  res *p_res;
+
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
+  // If we can't find it, we add it
+  if (sl_user == NULL)
+    sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER,
+                              sub_none, -1);
+
+  // If there is no resource name, we can leave now
+  if (!resname) return;
+
+  roster_usr = (roster*)sl_user->data;
+
+  // New or updated resource
+  p_res = get_or_add_resource(roster_usr, resname, prio);
+  p_res->status = bstat;
+  if (p_res->status_msg) {
+    g_free((gchar*)p_res->status_msg);
+    p_res->status_msg = NULL;
+  }
+  if (status_msg)
+    p_res->status_msg = g_strdup(status_msg);
+  if (!status_time)
+    time(&status_time);
+  p_res->status_timestamp = status_time;
+
+  p_res->role = role;
+  p_res->affil = affil;
+
+  if (p_res->realjid) {
+    g_free((gchar*)p_res->realjid);
+    p_res->realjid = NULL;
+  }
+  if (realjid)
+    p_res->realjid = g_strdup(realjid);
+
+  // If bstat is offline, we MUST delete the resource, actually
+  if (bstat == offline) {
+    del_resource(roster_usr, resname);
+    return;
+  }
+}
+
+//  roster_setflags()
+// Set one or several flags to value (TRUE/FALSE)
+void roster_setflags(const char *jid, guint flags, guint value)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
+  if (sl_user == NULL)
+    return;
+
+  roster_usr = (roster*)sl_user->data;
+  if (value)
+    roster_usr->flags |= flags;
+  else
+    roster_usr->flags &= ~flags;
+}
+
+//  roster_msg_setflag()
+// Set the ROSTER_FLAG_MSG to the given value for the given jid.
+// It will update the buddy's group message flag.
+// Update the unread messages list too.
+void roster_msg_setflag(const char *jid, guint special, guint value)
+{
+  GSList *sl_user;
+  roster *roster_usr, *roster_grp;
+  int new_roster_item = FALSE;
+  guint unread_list_modified = FALSE;
+
+  if (special) {
+    //sl_user = roster_find(jid, namesearch, ROSTER_TYPE_SPECIAL);
+    //if (!sl_user) return;
+    //roster_usr = (roster*)sl_user->data;
+    roster_usr = &roster_special;
+    if (value) {
+      if (!(roster_usr->flags & ROSTER_FLAG_MSG))
+        unread_list_modified = TRUE;
+      roster_usr->flags |= ROSTER_FLAG_MSG;
+      // Append the roster_usr to unread_list, but avoid duplicates
+      if (!g_slist_find(unread_list, roster_usr))
+        unread_list = g_slist_append(unread_list, roster_usr);
+    } else {
+      if (roster_usr->flags & ROSTER_FLAG_MSG)
+        unread_list_modified = TRUE;
+      roster_usr->flags &= ~ROSTER_FLAG_MSG;
+      if (unread_list) {
+        GSList *node = g_slist_find(unread_list, roster_usr);
+        if (node)
+          unread_list = g_slist_delete_link(unread_list, node);
+      }
+    }
+    goto roster_msg_setflag_return;
+  }
+
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
+  // If we can't find it, we add it
+  if (sl_user == NULL) {
+    sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, sub_none, -1);
+    new_roster_item = TRUE;
+  }
+
+  roster_usr = (roster*)sl_user->data;
+  roster_grp = (roster*)roster_usr->list->data;
+  if (value) {
+    if (!(roster_usr->flags & ROSTER_FLAG_MSG))
+      unread_list_modified = TRUE;
+    // Message flag is TRUE.  This is easy, we just have to set both flags
+    // to TRUE...
+    roster_usr->flags |= ROSTER_FLAG_MSG;
+    roster_grp->flags |= ROSTER_FLAG_MSG; // group
+    // Append the roster_usr to unread_list, but avoid duplicates
+    if (!g_slist_find(unread_list, roster_usr))
+      unread_list = g_slist_append(unread_list, roster_usr);
+  } else {
+    // Message flag is FALSE.
+    guint msg = FALSE;
+    if (roster_usr->flags & ROSTER_FLAG_MSG)
+      unread_list_modified = TRUE;
+    roster_usr->flags &= ~ROSTER_FLAG_MSG;
+    if (unread_list) {
+      GSList *node = g_slist_find(unread_list, roster_usr);
+      if (node)
+        unread_list = g_slist_delete_link(unread_list, node);
+    }
+    // For the group value we need to watch all buddies in this group;
+    // if one is flagged, then the group will be flagged.
+    // I will re-use sl_user and roster_usr here, as they aren't used
+    // anymore.
+    sl_user = roster_grp->list;
+    while (sl_user) {
+      roster_usr = (roster*)sl_user->data;
+      if (roster_usr->flags & ROSTER_FLAG_MSG) {
+        msg = TRUE;
+        break;
+      }
+      sl_user = g_slist_next(sl_user);
+    }
+    if (!msg)
+      roster_grp->flags &= ~ROSTER_FLAG_MSG;
+    else
+      roster_grp->flags |= ROSTER_FLAG_MSG;
+      // Actually the "else" part is useless, because the group
+      // ROSTER_FLAG_MSG should already be set...
+  }
+
+  if (buddylist && (new_roster_item || !g_list_find(buddylist, roster_usr)))
+    buddylist_build();
+
+roster_msg_setflag_return:
+  if (unread_list_modified) {
+    guint unread_count = g_slist_length(unread_list);
+    hlog_save_state();
+    /* Call external command */
+    hk_ext_cmd("", 'U', (guchar)MIN(255, unread_count), NULL);
+  }
+}
+
+const char *roster_getname(const char *jid)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
+  if (sl_user == NULL)
+    return NULL; // Not in the roster...
+
+  roster_usr = (roster*)sl_user->data;
+  return roster_usr->name;
+}
+
+const char *roster_getnickname(const char *jid)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
+  if (sl_user == NULL)
+    return NULL; // Not in the roster...
+
+  roster_usr = (roster*)sl_user->data;
+  return roster_usr->nickname;
+}
+
+void roster_settype(const char *jid, guint type)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+
+  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
+    return;
+
+  roster_usr = (roster*)sl_user->data;
+  roster_usr->type = type;
+}
+
+enum imstatus roster_getstatus(const char *jid, const char *resname)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+  res *p_res;
+
+  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  if (sl_user == NULL)
+    return offline; // Not in the roster, anyway...
+
+  roster_usr = (roster*)sl_user->data;
+  p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status;
+  return offline;
+}
+
+const char *roster_getstatusmsg(const char *jid, const char *resname)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+  res *p_res;
+
+  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  if (sl_user == NULL)
+    return NULL; // Not in the roster, anyway...
+
+  roster_usr = (roster*)sl_user->data;
+  p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status_msg;
+  return roster_usr->offline_status_message;
+}
+
+guint roster_gettype(const char *jid)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+
+  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
+    return 0;
+
+  roster_usr = (roster*)sl_user->data;
+  return roster_usr->type;
+}
+
+guint roster_getsubscription(const char *jid)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+
+  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
+    return 0;
+
+  roster_usr = (roster*)sl_user->data;
+  return roster_usr->subscription;
+}
+
+//  roster_unsubscribed()
+// We have lost buddy's presence updates; this function clears the status
+// message, sets the buddy offline and frees the resources
+void roster_unsubscribed(const char *jid)
+{
+  GSList *sl_user;
+  roster *roster_usr;
+
+  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  if (sl_user == NULL)
+    return;
+
+  roster_usr = (roster*)sl_user->data;
+  free_all_resources(&roster_usr->resource);
+}
+
+
+/* ### BuddyList functions ### */
+
+//  buddylist_set_hide_offline_buddies(hide)
+// "hide" values: 1=hide 0=show_all -1=invert
+void buddylist_set_hide_offline_buddies(int hide)
+{
+  if (hide < 0) {               // NEG   (invert)
+    if (display_filter == DFILTER_ALL)
+      display_filter = DFILTER_ONLINE;
+    else
+      display_filter = DFILTER_ALL;
+  } else if (hide == 0) {       // FALSE (don't hide -- andfo_)
+    display_filter = DFILTER_ALL;
+  } else {                      // TRUE  (hide -- andfo)
+    display_filter = DFILTER_ONLINE;
+  }
+}
+
+int buddylist_isset_filter(void)
+{
+  return (display_filter != DFILTER_ALL);
+}
+
+int buddylist_is_status_filtered(enum imstatus status)
+{
+  return display_filter & (1 << status);
+}
+
+void buddylist_set_filter(guchar filter)
+{
+  display_filter = filter;
+}
+
+guchar buddylist_get_filter(void)
+{
+  return display_filter;
+}
+
+//  buddylist_build()
+// Creates the buddylist from the roster entries.
+void buddylist_build(void)
+{
+  GSList *sl_roster_elt = groups;
+  roster *roster_elt;
+  roster *roster_current_buddy = NULL;
+  roster *roster_alternate_buddy = NULL;
+  int shrunk_group;
+
+  // We need to remember which buddy is selected.
+  if (current_buddy)
+    roster_current_buddy = BUDDATA(current_buddy);
+  current_buddy = NULL;
+  if (alternate_buddy)
+    roster_alternate_buddy = BUDDATA(alternate_buddy);
+  alternate_buddy = NULL;
+
+  // Destroy old buddylist
+  if (buddylist) {
+    g_list_free(buddylist);
+    buddylist = NULL;
+  }
+
+  buddylist = g_list_append(buddylist, &roster_special);
+
+  // Create the new list
+  while (sl_roster_elt) {
+    GSList *sl_roster_usrelt;
+    roster *roster_usrelt;
+    guint pending_group = TRUE;
+    roster_elt = (roster*) sl_roster_elt->data;
+
+    shrunk_group = roster_elt->flags & ROSTER_FLAG_HIDE;
+
+    sl_roster_usrelt = roster_elt->list;
+    while (sl_roster_usrelt) {
+      roster_usrelt = (roster*) sl_roster_usrelt->data;
+
+      // Buddy will be added if either:
+      // - buddy's status matches the display_filter
+      // - buddy has a lock (for example the buddy window is currently open)
+      // - buddy has a pending (non-read) message
+      // - group isn't hidden (shrunk)
+      // - this is the current_buddy
+      if (roster_usrelt == roster_current_buddy ||
+          buddylist_is_status_filtered(buddy_getstatus((gpointer)roster_usrelt,
+                                                       NULL)) ||
+          (buddy_getflags((gpointer)roster_usrelt) &
+               (ROSTER_FLAG_LOCK | ROSTER_FLAG_USRLOCK | ROSTER_FLAG_MSG))) {
+        // This user should be added.  Maybe the group hasn't been added yet?
+        if (pending_group) {
+          // It hasn't been done yet
+          buddylist = g_list_append(buddylist, roster_elt);
+          pending_group = FALSE;
+        }
+        // Add user
+        // XXX Should we add the user if there is a message and
+        //     the group is shrunk? If so, we'd need to check LOCK flag too,
+        //     perhaps...
+        if (!shrunk_group)
+          buddylist = g_list_append(buddylist, roster_usrelt);
+      }
+
+      sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
+    }
+    sl_roster_elt = g_slist_next(sl_roster_elt);
+  }
+
+  // Check if we can find our saved current_buddy...
+  if (roster_current_buddy)
+    current_buddy = g_list_find(buddylist, roster_current_buddy);
+  if (roster_alternate_buddy)
+    alternate_buddy = g_list_find(buddylist, roster_alternate_buddy);
+  // current_buddy initialization
+  if (!current_buddy || (g_list_position(buddylist, current_buddy) == -1))
+    current_buddy = g_list_first(buddylist);
+}
+
+//  buddy_hide_group(roster, hide)
+// "hide" values: 1=hide 0=show_all -1=invert
+void buddy_hide_group(gpointer rosterdata, int hide)
+{
+  roster *roster_usr = rosterdata;
+  if (hide > 0)                     // TRUE   (hide)
+    roster_usr->flags |= ROSTER_FLAG_HIDE;
+  else if (hide < 0)                // NEG    (invert)
+    roster_usr->flags ^= ROSTER_FLAG_HIDE;
+  else                              // FALSE  (don't hide)
+    roster_usr->flags &= ~ROSTER_FLAG_HIDE;
+}
+
+const char *buddy_getjid(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  if (!rosterdata)
+    return NULL;
+  return roster_usr->jid;
+}
+
+//  buddy_setgroup()
+// Change the group of current buddy
+//
+// Note: buddy_setgroup() updates the user lists.
+//
+void buddy_setgroup(gpointer rosterdata, char *newgroupname)
+{
+  roster *roster_usr = rosterdata;
+  GSList **sl_group;
+  GSList *sl_newgroup;
+  roster *my_newgroup;
+
+  // A group has no group :)
+  if (roster_usr->type & ROSTER_TYPE_GROUP) return;
+
+  // Add newgroup if necessary
+  if (!newgroupname)  newgroupname = "";
+  sl_newgroup = roster_add_group(newgroupname);
+  if (!sl_newgroup) return;
+  my_newgroup = (roster*)sl_newgroup->data;
+
+  // Remove the buddy from current group
+  sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list;
+  *sl_group = g_slist_remove(*sl_group, rosterdata);
+
+  // Remove old group if it is empty
+  if (!*sl_group) {
+    roster *roster_grp = (roster*)((GSList*)roster_usr->list)->data;
+    g_free((gchar*)roster_grp->jid);
+    g_free((gchar*)roster_grp->name);
+    g_free(roster_grp);
+    groups = g_slist_remove(groups, roster_grp);
+  }
+
+  // Add the buddy to its new group
+  roster_usr->list = sl_newgroup;    // (my_newgroup SList element)
+  my_newgroup->list = g_slist_insert_sorted(my_newgroup->list, roster_usr,
+                                            (GCompareFunc)&roster_compare_name);
+
+  buddylist_build();
+}
+
+void buddy_setname(gpointer rosterdata, char *newname)
+{
+  roster *roster_usr = rosterdata;
+  GSList **sl_group;
+
+  // TODO For groups, we need to check for unicity
+  // However, renaming a group boils down to moving all its buddies to
+  // another group, so calling this function is not really necessary...
+  if (roster_usr->type & ROSTER_TYPE_GROUP) return;
+
+  if (roster_usr->name) {
+    g_free((gchar*)roster_usr->name);
+    roster_usr->name = NULL;
+  }
+  if (newname)
+    roster_usr->name = g_strdup(newname);
+
+  // We need to resort the group list
+  sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list;
+  *sl_group = g_slist_sort(*sl_group, (GCompareFunc)&roster_compare_name);
+
+  buddylist_build();
+}
+
+const char *buddy_getname(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->name;
+}
+
+//  buddy_setnickname(buddy, newnickname)
+// Only for chatrooms
+void buddy_setnickname(gpointer rosterdata, const char *newname)
+{
+  roster *roster_usr = rosterdata;
+
+  if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; // XXX Error message?
+
+  if (roster_usr->nickname) {
+    g_free((gchar*)roster_usr->nickname);
+    roster_usr->nickname = NULL;
+  }
+  if (newname)
+    roster_usr->nickname = g_strdup(newname);
+}
+
+const char *buddy_getnickname(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->nickname;
+}
+
+//  buddy_setinsideroom(buddy, inside)
+// Only for chatrooms
+void buddy_setinsideroom(gpointer rosterdata, guint inside)
+{
+  roster *roster_usr = rosterdata;
+
+  if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return;
+
+  roster_usr->inside_room = inside;
+}
+
+guint buddy_getinsideroom(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->inside_room;
+}
+
+//  buddy_settopic(buddy, newtopic)
+// Only for chatrooms
+void buddy_settopic(gpointer rosterdata, const char *newtopic)
+{
+  roster *roster_usr = rosterdata;
+
+  if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return;
+
+  if (roster_usr->topic) {
+    g_free((gchar*)roster_usr->topic);
+    roster_usr->topic = NULL;
+  }
+  if (newtopic)
+    roster_usr->topic = g_strdup(newtopic);
+}
+
+const char *buddy_gettopic(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->topic;
+}
+
+void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus pstatus)
+{
+  roster *roster_usr = rosterdata;
+  roster_usr->print_status = pstatus;
+}
+
+enum room_printstatus buddy_getprintstatus(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->print_status;
+}
+
+void buddy_setautowhois(gpointer rosterdata, enum room_autowhois awhois)
+{
+  roster *roster_usr = rosterdata;
+  roster_usr->auto_whois = awhois;
+}
+
+enum room_autowhois buddy_getautowhois(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->auto_whois;
+}
+
+//  buddy_getgroupname()
+// Returns a pointer on buddy's group name.
+const char *buddy_getgroupname(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+
+  if (roster_usr->type & ROSTER_TYPE_GROUP)
+    return roster_usr->name;
+
+  if (roster_usr->type & ROSTER_TYPE_SPECIAL)
+    return NULL;
+
+  // This is a user
+  return ((roster*)((GSList*)roster_usr->list)->data)->name;
+}
+
+//  buddy_getgroup()
+// Returns a pointer on buddy's group.
+gpointer buddy_getgroup(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+
+  if (roster_usr->type & ROSTER_TYPE_GROUP)
+    return rosterdata;
+
+  if (roster_usr->type & ROSTER_TYPE_SPECIAL)
+    return NULL;
+
+  // This is a user
+  return (gpointer)((GSList*)roster_usr->list)->data;
+}
+
+void buddy_settype(gpointer rosterdata, guint type)
+{
+  roster *roster_usr = rosterdata;
+  roster_usr->type = type;
+}
+
+guint buddy_gettype(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->type;
+}
+
+guint buddy_getsubscription(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->subscription;
+}
+
+enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status;
+  return offline;
+}
+
+const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status_msg;
+  return roster_usr->offline_status_message;
+}
+
+time_t buddy_getstatustime(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->status_timestamp;
+  return 0;
+}
+
+gchar buddy_getresourceprio(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->prio;
+  return 0;
+}
+
+guint buddy_resource_getevents(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->events;
+  return ROSTER_EVENT_NONE;
+}
+
+void buddy_resource_setevents(gpointer rosterdata, const char *resname,
+                              guint events)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    p_res->events = events;
+}
+
+char *buddy_resource_getcaps(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->caps;
+  return NULL;
+}
+
+void buddy_resource_setcaps(gpointer rosterdata, const char *resname,
+                            const char *caps)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res) {
+    g_free(p_res->caps);
+    p_res->caps = g_strdup(caps);
+  }
+}
+
+struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname)
+{
+#ifdef JEP0022
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return &p_res->jep22;
+#endif
+  return NULL;
+}
+
+struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname)
+{
+#ifdef JEP0085
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return &p_res->jep85;
+#endif
+  return NULL;
+}
+
+struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname)
+{
+#ifdef HAVE_GPGME
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return &p_res->pgpdata;
+#endif
+  return NULL;
+}
+
+enum imrole buddy_getrole(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->role;
+  return role_none;
+}
+
+enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->affil;
+  return affil_none;
+}
+
+const char *buddy_getrjid(gpointer rosterdata, const char *resname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res)
+    return p_res->realjid;
+  return NULL;
+}
+
+//  buddy_getresources(roster_data)
+// Return a singly-linked-list of resource names
+// Note: the caller should free the list (and data) after use
+// If roster_data is null, the current buddy is selected
+GSList *buddy_getresources(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  GSList *reslist = NULL, *lp;
+
+  if (!roster_usr) {
+    if (!current_buddy) return NULL;
+    roster_usr = BUDDATA(current_buddy);
+  }
+  for (lp = roster_usr->resource; lp; lp = g_slist_next(lp))
+    reslist = g_slist_append(reslist, g_strdup(((res*)lp->data)->name));
+
+  return reslist;
+}
+
+//  buddy_getresources_locale(roster_data)
+// Same as buddy_getresources() but names are converted to user's locale
+// Note: the caller should free the list (and data) after use
+GSList *buddy_getresources_locale(gpointer rosterdata)
+{
+  GSList *reslist, *lp;
+
+  reslist = buddy_getresources(rosterdata);
+  // Convert each item to UI's locale
+  for (lp = reslist; lp; lp = g_slist_next(lp)) {
+    gchar *oldname = lp->data;
+    lp->data = from_utf8(oldname);
+    if (lp->data)
+      g_free(oldname);
+    else
+      lp->data = oldname;
+  }
+  return reslist;
+}
+
+/*
+//  buddy_isresource(roster_data)
+// Return true if there is at least one resource
+// (which means, for a room, that it isn't empty)
+int buddy_isresource(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  if (!roster_usr)
+    return FALSE;
+  if (roster_usr->resource)
+    return TRUE;
+  return FALSE;
+}
+*/
+
+//  buddy_resource_setname(roster_data, oldname, newname)
+// Useful for nickname change in a MUC room
+void buddy_resource_setname(gpointer rosterdata, const char *resname,
+                            const char *newname)
+{
+  roster *roster_usr = rosterdata;
+  res *p_res = get_resource(roster_usr, resname);
+  if (p_res) {
+    if (p_res->name) {
+      g_free((gchar*)p_res->name);
+      p_res->name = NULL;
+    }
+    if (newname)
+      p_res->name = g_strdup(newname);
+  }
+}
+
+//  buddy_del_all_resources()
+// Remove all resources from the specified buddy
+void buddy_del_all_resources(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+
+  while (roster_usr->resource) {
+    res *r = roster_usr->resource->data;
+    del_resource(roster_usr, r->name);
+  }
+}
+
+//  buddy_setflags()
+// Set one or several flags to value (TRUE/FALSE)
+void buddy_setflags(gpointer rosterdata, guint flags, guint value)
+{
+  roster *roster_usr = rosterdata;
+  if (value)
+    roster_usr->flags |= flags;
+  else
+    roster_usr->flags &= ~flags;
+}
+
+guint buddy_getflags(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->flags;
+}
+
+//  buddy_setonserverflag()
+// Set the on_server flag
+void buddy_setonserverflag(gpointer rosterdata, guint onserver)
+{
+  roster *roster_usr = rosterdata;
+  roster_usr->on_server = onserver;
+}
+
+guint buddy_getonserverflag(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->on_server;
+}
+
+//  buddy_search_jid(jid)
+// Look for a buddy with specified jid.
+// Search begins at buddylist; if no match is found in the the buddylist,
+// return NULL;
+GList *buddy_search_jid(const char *jid)
+{
+  GList *buddy;
+  roster *roster_usr;
+
+  if (!buddylist) return NULL;
+
+  for (buddy = buddylist; buddy; buddy = g_list_next(buddy)) {
+    roster_usr = (roster*)buddy->data;
+    if (roster_usr->jid && !strcasecmp(roster_usr->jid, jid))
+      return buddy;
+  }
+  return NULL;
+}
+
+//  buddy_search(string)
+// Look for a buddy whose name or jid contains string.
+// Search begins at current_buddy; if no match is found in the the buddylist,
+// return NULL;
+GList *buddy_search(char *string)
+{
+  GList *buddy = current_buddy;
+  roster *roster_usr;
+  if (!buddylist || !current_buddy) return NULL;
+  for (;;) {
+    gchar *jid_locale, *name_locale;
+    char *found = NULL;
+
+    buddy = g_list_next(buddy);
+    if (!buddy)
+      buddy = buddylist;
+
+    roster_usr = (roster*)buddy->data;
+
+    jid_locale = from_utf8(roster_usr->jid);
+    if (jid_locale) {
+      found = strcasestr(jid_locale, string);
+      g_free(jid_locale);
+      if (found)
+        return buddy;
+    }
+    name_locale = from_utf8(roster_usr->name);
+    if (name_locale) {
+      found = strcasestr(name_locale, string);
+      g_free(name_locale);
+      if (found)
+        return buddy;
+    }
+
+    if (buddy == current_buddy)
+      return NULL; // Back to the beginning, and no match found
+  }
+}
+
+//  foreach_buddy(roster_type, pfunction, param)
+// Call pfunction(buddy, param) for each buddy from the roster with
+// type matching roster_type.
+void foreach_buddy(guint roster_type,
+                   void (*pfunc)(gpointer rosterdata, void *param),
+                   void *param)
+{
+  GSList *sl_roster_elt = groups;
+  roster *roster_elt;
+  GSList *sl_roster_usrelt;
+  roster *roster_usrelt;
+
+  while (sl_roster_elt) {       // group list loop
+    roster_elt = (roster*) sl_roster_elt->data;
+    if (roster_elt->type & ROSTER_TYPE_SPECIAL)
+      continue; // Skip special items
+    sl_roster_usrelt = roster_elt->list;
+    while (sl_roster_usrelt) {  // user list loop
+      roster_usrelt = (roster*) sl_roster_usrelt->data;
+
+      if (roster_usrelt->type & roster_type)
+        pfunc(roster_usrelt, param);
+
+      sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
+    }
+    sl_roster_elt = g_slist_next(sl_roster_elt);
+  }
+}
+
+//  foreach_group_member(group, pfunction, param)
+// Call pfunction(buddy, param) for each buddy in the specified group.
+void foreach_group_member(gpointer groupdata,
+                   void (*pfunc)(gpointer rosterdata, void *param),
+                   void *param)
+{
+  roster *roster_elt;
+  GSList *sl_roster_usrelt;
+  roster *roster_usrelt;
+
+  roster_elt = groupdata;
+
+  if (!(roster_elt->type & ROSTER_TYPE_GROUP))
+    return;
+
+  sl_roster_usrelt = roster_elt->list;
+  while (sl_roster_usrelt) {  // user list loop
+    roster_usrelt = (roster*) sl_roster_usrelt->data;
+
+    pfunc(roster_usrelt, param);
+    sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
+  }
+}
+
+//  compl_list(type)
+// Returns a list of jid's or groups.  (For commands completion)
+// type: ROSTER_TYPE_USER (jid's) or ROSTER_TYPE_GROUP (group names)
+// The list should be freed by the caller after use.
+GSList *compl_list(guint type)
+{
+  GSList *list = NULL;
+  GSList *sl_roster_elt = groups;
+  roster *roster_elt;
+  GSList *sl_roster_usrelt;
+  roster *roster_usrelt;
+
+  while (sl_roster_elt) {       // group list loop
+    roster_elt = (roster*) sl_roster_elt->data;
+
+    if (roster_elt->type & ROSTER_TYPE_SPECIAL)
+      continue; // Skip special items
+
+    if (type == ROSTER_TYPE_GROUP) { // (group names)
+      if (roster_elt->name && *(roster_elt->name))
+        list = g_slist_append(list, from_utf8(roster_elt->name));
+    } else { // ROSTER_TYPE_USER (jid) (or agent, or chatroom...)
+      sl_roster_usrelt = roster_elt->list;
+      while (sl_roster_usrelt) {  // user list loop
+        roster_usrelt = (roster*) sl_roster_usrelt->data;
+
+        if (roster_usrelt->jid)
+          list = g_slist_append(list, from_utf8(roster_usrelt->jid));
+
+        sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
+      }
+    }
+    sl_roster_elt = g_slist_next(sl_roster_elt);
+  }
+
+  return list;
+}
+
+//  unread_msg(rosterdata)
+// Return the next buddy with an unread message.  If the parameter is NULL,
+// return the first buddy with an unread message.
+gpointer unread_msg(gpointer rosterdata)
+{
+  GSList *unread, *next_unread;
+
+  if (!unread_list)
+    return NULL;
+
+  // First unread message
+  if (!rosterdata)
+    return unread_list->data;
+
+  unread = g_slist_find(unread_list, rosterdata);
+  if (!unread)
+    return unread_list->data;
+
+  next_unread = g_slist_next(unread);
+  if (next_unread)
+    return next_unread->data;
+  return unread_list->data;
+}
+
+
+/* ### "unread_jids" functions ###
+ *
+ * The unread_jids hash table is used to keep track of the buddies with
+ * unread messages when a disconnection occurs.
+ * When removing a buddy with an unread message from the roster, the
+ * jid should be added to the unread_jids table.  When adding a buddy to
+ * the roster, we check if (s)he had a pending unread message.
+ */
+
+//  unread_jid_add(jid)
+// Add jid to the unread_jids hash table
+void unread_jid_add(const char *jid)
+{
+  if (!unread_jids) {
+    // Initialize unread_jids hash table
+    unread_jids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+  }
+  // The 2nd unread_jids is an arbitrary non-null pointer:
+  g_hash_table_insert(unread_jids, g_strdup(jid), unread_jids);
+}
+
+//  unread_jid_del(jid)
+// Return TRUE if jid is found in the table (and remove it), FALSE if not
+static int unread_jid_del(const char *jid)
+{
+  if (!unread_jids)
+    return FALSE;
+  return g_hash_table_remove(unread_jids, jid);
+}
+
+// Helper function for unread_jid_get_list()
+static void add_to_unreadjids(gpointer key, gpointer value, gpointer udata)
+{
+  GList **listp = udata;
+  *listp = g_list_append(*listp, key);
+}
+
+//  unread_jid_get_list()
+// Return the JID list.
+// The content of the list should not be modified or freed.
+// The caller should call g_list_free() after use.
+GList *unread_jid_get_list(void)
+{
+  GList *list = NULL;
+
+  if (!unread_jids)
+    return NULL;
+
+  // g_hash_table_get_keys() is only in glib >= 2.14
+  //return g_hash_table_get_keys(unread_jids);
+
+  g_hash_table_foreach(unread_jids, add_to_unreadjids, &list);
+  return list;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/roster.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,247 @@
+#ifndef __MCABBER_ROSTER_H__
+#define __MCABBER_ROSTER_H__ 1
+
+#include <glib.h>
+#include <time.h>
+
+#include <mcabber/pgp.h>
+#include <mcabber/config.h>
+
+#define SPECIAL_BUFFER_STATUS_ID  "[status]"
+
+enum imstatus {
+    offline,
+    available,
+    freeforchat,
+    dontdisturb,
+    notavail,
+    away,
+    invisible,
+    imstatus_size
+};
+
+extern char imstatus2char[]; // Should match enum above
+
+enum imrole {
+  role_none,
+  role_moderator,
+  role_participant,
+  role_visitor,
+  imrole_size
+};
+
+extern char *strrole[]; // Should match enum above
+
+enum imaffiliation {
+  affil_none,
+  affil_owner,
+  affil_admin,
+  affil_member,
+  affil_outcast,
+  imaffiliation_size
+};
+
+extern char *straffil[]; // Should match enum above
+
+enum subscr {
+  sub_none    = 0,
+  sub_pending = 1,
+  sub_to      = 1 << 1,
+  sub_from    = 1 << 2,
+  sub_both    = sub_to|sub_from,
+  sub_remove  = 1 << 3
+};
+
+enum findwhat {
+  jidsearch,
+  namesearch
+};
+
+extern char *strprintstatus[];
+
+// Note: do not change the ordering as these values are visible
+// to the user (option 'muc_print_status')!
+enum room_printstatus {
+  status_default,
+  status_none,
+  status_in_and_out,
+  status_all
+};
+
+extern char *strautowhois[];
+
+enum room_autowhois {
+  autowhois_default,
+  autowhois_off,
+  autowhois_on
+};
+
+struct role_affil {
+  enum { type_role, type_affil } type;
+  union {
+    enum imrole role;
+    enum imaffiliation affil;
+  } val;
+};
+
+// Roster_type is a set of flags, so values should be 2^n
+#define ROSTER_TYPE_USER    1U
+#define ROSTER_TYPE_GROUP   (1U<<1)
+#define ROSTER_TYPE_AGENT   (1U<<2)
+#define ROSTER_TYPE_ROOM    (1U<<3)
+#define ROSTER_TYPE_SPECIAL (1U<<4)
+
+// Flags:
+#define ROSTER_FLAG_MSG     1U      // Message not read
+#define ROSTER_FLAG_HIDE    (1U<<1) // Group hidden (or buddy window closed)
+#define ROSTER_FLAG_LOCK    (1U<<2) // Node should not be removed from buddylist
+#define ROSTER_FLAG_USRLOCK (1U<<3) // Node should not be removed from buddylist
+// ROSTER_FLAG_LOCAL   (1U<<4) // Buddy not on server's roster  (??)
+
+#define JEP0022
+#define JEP0085
+
+struct jep0022 {
+  guint support;
+  guint last_state_sent;
+  gchar *last_msgid_sent;
+  guint last_state_rcvd;
+  gchar *last_msgid_rcvd;
+};
+struct jep0085 {
+  guint support;
+  guint last_state_sent;
+  guint last_state_rcvd;
+};
+
+enum chatstate_support {
+  CHATSTATES_SUPPORT_UNKNOWN = 0,
+  CHATSTATES_SUPPORT_PROBED,
+  CHATSTATES_SUPPORT_NONE,
+  CHATSTATES_SUPPORT_OK
+};
+
+struct pgp_data {
+  gchar *sign_keyid;  // KeyId used by the contact to sign their presence/msg
+#ifdef HAVE_GPGME
+  gpgme_sigsum_t last_sigsum; // Last signature summary
+#endif
+};
+
+/* Message event and chat state flags */
+#define ROSTER_EVENT_NONE      0U
+/* JEP-22 Message Events */
+#define ROSTER_EVENT_OFFLINE   (1U<<0)
+#define ROSTER_EVENT_DELIVERED (1U<<1)
+#define ROSTER_EVENT_DISPLAYED (1U<<2)
+/* JEP-22 & JEP-85 */
+#define ROSTER_EVENT_COMPOSING (1U<<3)
+/* JEP-85 Chat State Notifications */
+#define ROSTER_EVENT_ACTIVE    (1U<<4)
+#define ROSTER_EVENT_PAUSED    (1U<<5)
+#define ROSTER_EVENT_INACTIVE  (1U<<6)
+#define ROSTER_EVENT_GONE      (1U<<7)
+
+extern GList *buddylist;
+extern GList *current_buddy;
+extern GList *alternate_buddy;
+
+// Macros...
+
+#define BUDDATA(glist_node) ((glist_node)->data)
+#define CURRENT_JID         buddy_getjid(BUDDATA(current_buddy))
+
+// Prototypes...
+void    roster_init(void);
+GSList *roster_add_group(const char *name);
+GSList *roster_add_user(const char *jid, const char *name, const char *group,
+                        guint type, enum subscr esub, gint on_server);
+GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type);
+void    roster_del_user(const char *jid);
+void    roster_free(void);
+void    roster_setstatus(const char *jid, const char *resname, gchar prio,
+                         enum imstatus bstat, const char *status_msg,
+                         time_t timestamp,
+                         enum imrole role, enum imaffiliation affil,
+                         const char *realjid);
+void    roster_setflags(const char *jid, guint flags, guint value);
+void    roster_msg_setflag(const char *jid, guint special, guint value);
+const char *roster_getname(const char *jid);
+const char *roster_getnickname(const char *jid);
+void    roster_settype(const char *jid, guint type);
+enum imstatus roster_getstatus(const char *jid, const char *resname);
+const char   *roster_getstatusmsg(const char *jid, const char *resname);
+guint   roster_gettype(const char *jid);
+guint   roster_getsubscription(const char *jid);
+void    roster_unsubscribed(const char *jid);
+
+void    buddylist_build(void);
+void    buddy_hide_group(gpointer rosterdata, int hide);
+void    buddylist_set_hide_offline_buddies(int hide);
+int     buddylist_isset_filter(void);
+int     buddylist_is_status_filtered(enum imstatus status);
+void    buddylist_set_filter(guchar);
+guchar  buddylist_get_filter(void);
+const char *buddy_getjid(gpointer rosterdata);
+void        buddy_setname(gpointer rosterdata, char *newname);
+const char *buddy_getname(gpointer rosterdata);
+void        buddy_setnickname(gpointer rosterdata, const char *newname);
+const char *buddy_getnickname(gpointer rosterdata);
+void        buddy_setinsideroom(gpointer rosterdata, guint inside);
+guint       buddy_getinsideroom(gpointer rosterdata);
+void        buddy_settopic(gpointer rosterdata, const char *newtopic);
+const char *buddy_gettopic(gpointer rosterdata);
+void    buddy_setprintstatus(gpointer rosterdata, enum room_printstatus);
+enum room_printstatus buddy_getprintstatus(gpointer rosterdata);
+void    buddy_setautowhois(gpointer rosterdata, enum room_autowhois);
+enum room_autowhois buddy_getautowhois(gpointer rosterdata);
+void    buddy_settype(gpointer rosterdata, guint type);
+guint   buddy_gettype(gpointer rosterdata);
+guint   buddy_getsubscription(gpointer rosterdata);
+void    buddy_setgroup(gpointer rosterdata, char *newgroupname);
+const char *buddy_getgroupname(gpointer rosterdata);
+gpointer buddy_getgroup(gpointer rosterdata);
+enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname);
+const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname);
+time_t  buddy_getstatustime(gpointer rosterdata, const char *resname);
+gchar   buddy_getresourceprio(gpointer rosterdata, const char *resname);
+//int   buddy_isresource(gpointer rosterdata);
+GSList *buddy_getresources(gpointer rosterdata);
+GSList *buddy_getresources_locale(gpointer rosterdata);
+void    buddy_resource_setname(gpointer rosterdata, const char *resname,
+                               const char *newname);
+void    buddy_resource_setevents(gpointer rosterdata, const char *resname,
+                                 guint event);
+guint   buddy_resource_getevents(gpointer rosterdata, const char *resname);
+void    buddy_resource_setcaps(gpointer rosterdata, const char *resname,
+                               const char *caps);
+char   *buddy_resource_getcaps(gpointer rosterdata, const char *resname);
+struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname);
+struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname);
+struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname);
+enum imrole buddy_getrole(gpointer rosterdata, const char *resname);
+enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname);
+const char *buddy_getrjid(gpointer rosterdata, const char *resname);
+void    buddy_del_all_resources(gpointer rosterdata);
+void    buddy_setflags(gpointer rosterdata, guint flags, guint value);
+guint   buddy_getflags(gpointer rosterdata);
+void    buddy_setonserverflag(gpointer rosterdata, guint onserver);
+guint   buddy_getonserverflag(gpointer rosterdata);
+GList  *buddy_search_jid(const char *jid);
+GList  *buddy_search(char *string);
+void    foreach_buddy(guint roster_type,
+                      void (*pfunc)(gpointer rosterdata, void *param),
+                      void *param);
+void    foreach_group_member(gpointer groupdata,
+                             void (*pfunc)(gpointer rosterdata, void *param),
+                             void *param);
+gpointer unread_msg(gpointer rosterdata);
+
+void   unread_jid_add(const char *jid);
+GList *unread_jid_get_list(void);
+
+GSList *compl_list(guint type);
+
+#endif /* __MCABBER_ROSTER_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/screen.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,4124 @@
+/*
+ * screen.c     -- UI stuff
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ * Parts of this file come from the Cabber project <cabber@ajmacias.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <ctype.h>
+
+#include <config.h>
+#include <locale.h>
+#include <assert.h>
+#ifdef USE_SIGWINCH
+# include <sys/ioctl.h>
+# include <termios.h>
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_LOCALCHARSET_H
+# include <localcharset.h>
+#else
+# include <langinfo.h>
+#endif
+
+#ifdef WITH_ENCHANT
+# include <enchant.h>
+#endif
+
+#ifdef WITH_ASPELL
+# include <aspell.h>
+#endif
+
+#include "screen.h"
+#include "utf8.h"
+#include "hbuf.h"
+#include "commands.h"
+#include "compl.h"
+#include "roster.h"
+#include "histolog.h"
+#include "settings.h"
+#include "utils.h"
+#include "xmpp.h"
+#include "main.h"
+
+#define get_color(col)  (COLOR_PAIR(col)|COLOR_ATTRIB[col])
+#define compose_color(col)  (COLOR_PAIR(col->color_pair)|col->color_attrib)
+
+#define DEFAULT_LOG_WIN_HEIGHT (5+2)
+#define DEFAULT_ROSTER_WIDTH    24
+#define CHAT_WIN_HEIGHT (maxY-1-Log_Win_Height)
+
+const char *LocaleCharSet = "C";
+
+static unsigned short int Log_Win_Height;
+static unsigned short int Roster_Width;
+
+static inline void check_offset(int);
+static void scr_cancel_current_completion(void);
+static void scr_end_current_completion(void);
+static void scr_insert_text(const char*);
+static void scr_handle_tab(void);
+
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+static void spellcheck(char *, char *);
+#endif
+
+static GHashTable *winbufhash;
+
+typedef struct {
+  GList  *hbuf;
+  GList  *top;     // If top is NULL, we'll display the last lines
+  char    cleared; // For ex, user has issued a /clear command...
+  char    lock;
+} buffdata;
+
+typedef struct {
+  WINDOW *win;
+  PANEL  *panel;
+  buffdata *bd;
+} winbuf;
+
+struct dimensions {
+  int l;
+  int c;
+};
+
+static WINDOW *rosterWnd, *chatWnd, *activechatWnd, *inputWnd, *logWnd;
+static WINDOW *mainstatusWnd, *chatstatusWnd;
+static PANEL *rosterPanel, *chatPanel, *activechatPanel, *inputPanel;
+static PANEL *mainstatusPanel, *chatstatusPanel;
+static PANEL *logPanel;
+static int maxY, maxX;
+static int prev_chatwidth;
+static winbuf *statusWindow;
+static winbuf *currentWindow;
+static GList  *statushbuf;
+
+static int roster_hidden;
+static int chatmode;
+static int multimode;
+static char *multiline, *multimode_subj;
+int update_roster;
+int utf8_mode = 0;
+static bool Curses;
+static bool log_win_on_top;
+static bool roster_win_on_right;
+static time_t LastActivity;
+
+static char       inputLine[INPUTLINE_LENGTH+1];
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+static char       maskLine[INPUTLINE_LENGTH+1];
+#endif
+static char      *ptr_inputline;
+static short int  inputline_offset;
+static int    completion_started;
+static GList *cmdhisto;
+static GList *cmdhisto_cur;
+static guint  cmdhisto_nblines;
+static char   cmdhisto_backup[INPUTLINE_LENGTH+1];
+
+static int    chatstate; /* (0=active, 1=composing, 2=paused) */
+static bool   lock_chatstate;
+static time_t chatstate_timestamp;
+static guint  chatstate_timeout_id = 0;
+int chatstates_disabled;
+
+#define MAX_KEYSEQ_LENGTH 8
+
+typedef struct {
+  char *seqstr;
+  guint mkeycode;
+  gint  value;
+} keyseq;
+
+#ifdef HAVE_GLIB_REGEX
+static GRegex *url_regex;
+#endif
+
+GSList *keyseqlist;
+static void add_keyseq(char *seqstr, guint mkeycode, gint value);
+
+void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp,
+                       unsigned int prefix_flags, int force_show,
+                       unsigned mucnicklen, gpointer xep184);
+
+void scr_WriteMessage(const char *bjid, const char *text,
+                      time_t timestamp, guint prefix_flags,
+                      unsigned mucnicklen, gpointer xep184);
+
+inline void scr_UpdateBuddyWindow(void);
+inline void scr_set_chatmode(int enable);
+
+#define SPELLBADCHAR 5
+
+#ifdef WITH_ENCHANT
+EnchantBroker *spell_broker;
+EnchantDict *spell_checker;
+#endif
+
+#ifdef WITH_ASPELL
+AspellConfig *spell_config;
+AspellSpeller *spell_checker;
+#endif
+
+typedef struct {
+	int color_pair;
+	int color_attrib;
+} ccolor;
+
+typedef struct {
+  char *status, *wildcard;
+  ccolor *color;
+  GPatternSpec *compiled;
+} rostercolor;
+
+static GSList *rostercolrules = NULL;
+
+static GHashTable *muccolors = NULL, *nickcolors = NULL;
+
+typedef struct {
+  bool manual; // Manually set?
+  ccolor *color;
+} nickcolor;
+
+static int nickcolcount = 0;
+static ccolor ** nickcols = NULL;
+static muccoltype glob_muccol = MC_OFF;
+
+/* Functions */
+
+static int FindColor(const char *name)
+{
+  int result;
+
+  if (!strcmp(name, "default"))
+    return -1;
+  if (!strcmp(name, "black"))
+    return COLOR_BLACK;
+  if (!strcmp(name, "red"))
+    return COLOR_RED;
+  if (!strcmp(name, "green"))
+    return COLOR_GREEN;
+  if (!strcmp(name, "yellow"))
+    return COLOR_YELLOW;
+  if (!strcmp(name, "blue"))
+    return COLOR_BLUE;
+  if (!strcmp(name, "magenta"))
+    return COLOR_MAGENTA;
+  if (!strcmp(name, "cyan"))
+    return COLOR_CYAN;
+  if (!strcmp(name, "white"))
+    return COLOR_WHITE;
+
+  // Directly support 256-color values
+  result = atoi(name);
+  if (result > 0 && result < COLORS)
+    return result;
+
+  scr_LogPrint(LPRINT_LOGNORM, "ERROR: Wrong color: %s", name);
+  return -1;
+}
+
+static ccolor *get_user_color(const char *color)
+{
+  bool isbright = FALSE;
+  int cl;
+  ccolor *ccol;
+  if (!strncmp(color, "bright", 6)) {
+    isbright = TRUE;
+    color += 6;
+  }
+  cl = FindColor(color);
+  if (cl < 0)
+    return NULL;
+  ccol = g_new0(ccolor, 1);
+  ccol->color_attrib = isbright ? A_BOLD : A_NORMAL;
+  ccol->color_pair = cl + COLOR_max; //user colors come after the internal ones
+  return ccol;
+}
+
+static void ensure_string_htable(GHashTable **table,
+    GDestroyNotify value_destroy_func)
+{
+  if (*table)//Have it already
+    return;
+  *table = g_hash_table_new_full(g_str_hash, g_str_equal,
+      g_free, value_destroy_func);
+}
+
+// Sets the coloring mode for given MUC
+// The MUC room does not need to be in the roster at that time
+// muc - the JID of room
+// type - the new type
+void scr_MucColor(const char *muc, muccoltype type)
+{
+  gchar *muclow = g_utf8_strdown(muc, -1);
+  if (type == MC_REMOVE) {//Remove it
+    if (strcmp(muc, "*")) {
+      if (muccolors && g_hash_table_lookup(muccolors, muclow))
+        g_hash_table_remove(muccolors, muclow);
+    } else {
+      scr_LogPrint(LPRINT_NORMAL, "Can not remove global coloring mode");
+    }
+    g_free(muclow);
+  } else {//Add or overwrite
+    if (strcmp(muc, "*")) {
+      muccoltype *value = g_new(muccoltype, 1);
+      *value = type;
+      ensure_string_htable(&muccolors, g_free);
+      g_hash_table_replace(muccolors, muclow, value);
+    } else {
+      glob_muccol = type;
+      g_free(muclow);
+    }
+  }
+  //Need to redraw?
+  if (chatmode &&
+      ((buddy_search_jid(muc) == current_buddy) || !strcmp(muc, "*")))
+    scr_UpdateBuddyWindow();
+}
+
+// Sets the color for nick in MUC
+// If color is "-", the color is marked as automaticly assigned and is
+// not used if the room is in the "preset" mode
+void scr_MucNickColor(const char *nick, const char *color)
+{
+  char *snick, *mnick;
+  bool need_update = FALSE;
+  snick = g_strdup_printf("<%s>", nick);
+  mnick = g_strdup_printf("*%s ", nick);
+  if (!strcmp(color, "-")) {//Remove the color
+    if (nickcolors) {
+      nickcolor *nc = g_hash_table_lookup(nickcolors, snick);
+      if (nc) {//Have this nick already
+        nc->manual = FALSE;
+        nc = g_hash_table_lookup(nickcolors, mnick);
+        assert(nc);//Must have both at the same time
+        nc->manual = FALSE;
+      }// Else -> no color saved, nothing to delete
+    }
+    g_free(snick);//They are not saved in the hash
+    g_free(mnick);
+    need_update = TRUE;
+  } else {
+    ccolor *cl = get_user_color(color);
+    if (!cl) {
+      scr_LogPrint(LPRINT_NORMAL, "No such color name");
+      g_free(snick);
+      g_free(mnick);
+    } else {
+      nickcolor *nc = g_new(nickcolor, 1);
+      ensure_string_htable(&nickcolors, NULL);
+      nc->manual = TRUE;
+      nc->color = cl;
+      //Free the struct, if any there already
+      g_free(g_hash_table_lookup(nickcolors, mnick));
+      //Save the new ones
+      g_hash_table_replace(nickcolors, mnick, nc);
+      g_hash_table_replace(nickcolors, snick, nc);
+      need_update = TRUE;
+    }
+  }
+  if (need_update && chatmode &&
+      (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM))
+    scr_UpdateBuddyWindow();
+}
+
+static void free_rostercolrule(rostercolor *col)
+{
+  g_free(col->status);
+  g_free(col->wildcard);
+  g_free(col->color);
+  g_pattern_spec_free(col->compiled);
+  g_free(col);
+}
+
+// Removes all roster coloring rules
+void scr_RosterClearColor(void)
+{
+  GSList *head;
+  for (head = rostercolrules; head; head = g_slist_next(head)) {
+    free_rostercolrule(head->data);
+  }
+  g_slist_free(rostercolrules);
+  rostercolrules = NULL;
+}
+
+// Adds, modifies or removes roster coloring rule
+// color set to "-" removes the rule,
+// otherwise it is modified (if exists) or added
+//
+// Returns weather it was successfull (therefore the roster should be
+// redrawed) or not. If it failed, for example because of invalid color
+// name, it also prints the error.
+bool scr_RosterColor(const char *status, const char *wildcard,
+                     const char *color)
+{
+  GSList *head;
+  GSList *found = NULL;
+  for (head = rostercolrules; head; head = g_slist_next(head)) {
+    rostercolor *rc = head->data;
+    if ((!strcmp(status, rc->status)) && (!strcmp(wildcard, rc->wildcard))) {
+      found = head;
+      break;
+    }
+  }
+  if (!strcmp(color,"-")) {//Delete the rule
+    if (found) {
+      free_rostercolrule(found->data);
+      rostercolrules = g_slist_delete_link(rostercolrules, found);
+      return TRUE;
+    } else {
+      scr_LogPrint(LPRINT_NORMAL, "No such color rule, nothing removed");
+      return FALSE;
+    }
+  } else {
+    ccolor *cl = get_user_color(color);
+    if (!cl) {
+      scr_LogPrint(LPRINT_NORMAL, "No such color name");
+      return FALSE;
+    }
+    if (found) {
+      rostercolor *rc = found->data;
+			g_free(rc->color);
+      rc->color = cl;
+    } else {
+      rostercolor *rc = g_new(rostercolor, 1);
+      rc->status = g_strdup(status);
+      rc->wildcard = g_strdup(wildcard);
+      rc->compiled = g_pattern_spec_new(wildcard);
+      rc->color = cl;
+      rostercolrules = g_slist_prepend(rostercolrules, rc);
+    }
+    return TRUE;
+  }
+}
+
+static void ParseColors(void)
+{
+  const char *colors[] = {
+    "", "",
+    "general",
+    "msgout",
+    "msghl",
+    "status",
+    "roster",
+    "rostersel",
+    "rosterselmsg",
+    "rosternewmsg",
+    "info",
+    "msgin",
+    NULL
+  };
+
+  const char *color;
+  const char *background   = settings_opt_get("color_background");
+  const char *backselected = settings_opt_get("color_bgrostersel");
+  const char *backstatus   = settings_opt_get("color_bgstatus");
+  char *tmp;
+  int i;
+
+  // Initialize color attributes
+  memset(COLOR_ATTRIB, 0, sizeof(COLOR_ATTRIB));
+
+  // Default values
+  if (!background)   background   = "black";
+  if (!backselected) backselected = "cyan";
+  if (!backstatus)   backstatus   = "blue";
+
+  for (i=0; colors[i]; i++) {
+    tmp = g_strdup_printf("color_%s", colors[i]);
+    color = settings_opt_get(tmp);
+    g_free(tmp);
+
+    if (color) {
+      if (!strncmp(color, "bright", 6)) {
+        COLOR_ATTRIB[i+1] = A_BOLD;
+        color += 6;
+      }
+    }
+
+    switch (i + 1) {
+      case 1:
+          init_pair(1, COLOR_BLACK, COLOR_WHITE);
+          break;
+      case 2:
+          init_pair(2, COLOR_WHITE, COLOR_BLACK);
+          break;
+      case COLOR_GENERAL:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
+                    FindColor(background));
+          break;
+      case COLOR_MSGOUT:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_CYAN),
+                    FindColor(background));
+          break;
+      case COLOR_MSGHL:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_YELLOW),
+                    FindColor(background));
+          break;
+      case COLOR_STATUS:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
+                    FindColor(backstatus));
+          break;
+      case COLOR_ROSTER:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_GREEN),
+                    FindColor(background));
+          break;
+      case COLOR_ROSTERSEL:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_BLUE),
+                    FindColor(backselected));
+          break;
+      case COLOR_ROSTERSELNMSG:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED),
+                    FindColor(backselected));
+          break;
+      case COLOR_ROSTERNMSG:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED),
+                    FindColor(background));
+          break;
+      case COLOR_INFO:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
+                    FindColor(background));
+          break;
+      case COLOR_MSGIN:
+          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
+                    FindColor(background));
+          break;
+    }
+  }
+  for (i = COLOR_max; i < (COLOR_max + COLORS); i++)
+    init_pair(i, i-COLOR_max, FindColor(background));
+
+  if (!nickcols) {
+    char *ncolors = g_strdup(settings_opt_get("nick_colors"));
+    if (ncolors) {
+      char *ncolor_start, *ncolor_end;
+      ncolor_start = ncolor_end = ncolors;
+
+      while (*ncolor_end)
+        ncolor_end++;
+
+      while (ncolors < ncolor_end && *ncolors) {
+        if ((*ncolors == ' ') || (*ncolors == '\t')) {
+          ncolors++;
+        } else {
+          char *end = ncolors;
+          ccolor *cl;
+          while (*end && (*end != ' ') && (*end != '\t'))
+            end++;
+          *end = '\0';
+          cl = get_user_color(ncolors);
+          if (!cl) {
+            scr_LogPrint(LPRINT_NORMAL, "Unknown color %s", ncolors);
+          } else {
+            nickcols = g_realloc(nickcols, (++nickcolcount) * sizeof *nickcols);
+            nickcols[nickcolcount-1] = cl;
+          }
+          ncolors = end+1;
+        }
+      }
+      g_free(ncolor_start);
+    }
+    if (!nickcols) {//Fallback to have something
+      nickcolcount = 1;
+			nickcols = g_new(ccolor*, 1);
+			*nickcols = g_new(ccolor, 1);
+      (*nickcols)->color_pair = COLOR_GENERAL;
+      (*nickcols)->color_attrib = A_NORMAL;
+    }
+  }
+}
+
+static void init_keycodes(void)
+{
+  add_keyseq("O5A", MKEY_EQUIV, 521); // Ctrl-Up
+  add_keyseq("O5B", MKEY_EQUIV, 514); // Ctrl-Down
+  add_keyseq("O5C", MKEY_EQUIV, 518); // Ctrl-Right
+  add_keyseq("O5D", MKEY_EQUIV, 516); // Ctrl-Left
+  add_keyseq("O6A", MKEY_EQUIV, 520); // Shift-Up
+  add_keyseq("O6B", MKEY_EQUIV, 513); // Shift-Down
+  add_keyseq("O6C", MKEY_EQUIV, 402); // Shift-Right
+  add_keyseq("O6D", MKEY_EQUIV, 393); // Shift-Left
+  add_keyseq("O2A", MKEY_EQUIV, 520); // Shift-Up
+  add_keyseq("O2B", MKEY_EQUIV, 513); // Shift-Down
+  add_keyseq("O2C", MKEY_EQUIV, 402); // Shift-Right
+  add_keyseq("O2D", MKEY_EQUIV, 393); // Shift-Left
+  add_keyseq("[5^", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
+  add_keyseq("[6^", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
+  add_keyseq("[5@", MKEY_CTRL_SHIFT_PGUP, 0);   // Ctrl-Shift-PageUp
+  add_keyseq("[6@", MKEY_CTRL_SHIFT_PGDOWN, 0); // Ctrl-Shift-PageDown
+  add_keyseq("[7@", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
+  add_keyseq("[8@", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
+  add_keyseq("[8^", MKEY_CTRL_END, 0);  // Ctrl-End
+  add_keyseq("[7^", MKEY_CTRL_HOME, 0); // Ctrl-Home
+  add_keyseq("[2^", MKEY_CTRL_INS, 0);  // Ctrl-Insert
+  add_keyseq("[3^", MKEY_CTRL_DEL, 0);  // Ctrl-Delete
+
+  // Xterm
+  add_keyseq("[1;5A", MKEY_EQUIV, 521); // Ctrl-Up
+  add_keyseq("[1;5B", MKEY_EQUIV, 514); // Ctrl-Down
+  add_keyseq("[1;5C", MKEY_EQUIV, 518); // Ctrl-Right
+  add_keyseq("[1;5D", MKEY_EQUIV, 516); // Ctrl-Left
+  add_keyseq("[1;6A", MKEY_EQUIV, 520); // Ctrl-Shift-Up
+  add_keyseq("[1;6B", MKEY_EQUIV, 513); // Ctrl-Shift-Down
+  add_keyseq("[1;6C", MKEY_EQUIV, 402); // Ctrl-Shift-Right
+  add_keyseq("[1;6D", MKEY_EQUIV, 393); // Ctrl-Shift-Left
+  add_keyseq("[1;6H", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
+  add_keyseq("[1;6F", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
+  add_keyseq("[1;2A", MKEY_EQUIV, 521); // Shift-Up
+  add_keyseq("[1;2B", MKEY_EQUIV, 514); // Shift-Down
+  add_keyseq("[5;5~", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
+  add_keyseq("[6;5~", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
+  add_keyseq("[1;5F", MKEY_CTRL_END, 0);  // Ctrl-End
+  add_keyseq("[1;5H", MKEY_CTRL_HOME, 0); // Ctrl-Home
+  add_keyseq("[2;5~", MKEY_CTRL_INS, 0);  // Ctrl-Insert
+  add_keyseq("[3;5~", MKEY_CTRL_DEL, 0);  // Ctrl-Delete
+
+  // PuTTY
+  add_keyseq("[A", MKEY_EQUIV, 521); // Ctrl-Up
+  add_keyseq("[B", MKEY_EQUIV, 514); // Ctrl-Down
+  add_keyseq("[C", MKEY_EQUIV, 518); // Ctrl-Right
+  add_keyseq("[D", MKEY_EQUIV, 516); // Ctrl-Left
+
+  // screen
+  add_keyseq("Oa", MKEY_EQUIV, 521); // Ctrl-Up
+  add_keyseq("Ob", MKEY_EQUIV, 514); // Ctrl-Down
+  add_keyseq("Oc", MKEY_EQUIV, 518); // Ctrl-Right
+  add_keyseq("Od", MKEY_EQUIV, 516); // Ctrl-Left
+  add_keyseq("[a", MKEY_EQUIV, 520); // Shift-Up
+  add_keyseq("[b", MKEY_EQUIV, 513); // Shift-Down
+  add_keyseq("[c", MKEY_EQUIV, 402); // Shift-Right
+  add_keyseq("[d", MKEY_EQUIV, 393); // Shift-Left
+  add_keyseq("[5$", MKEY_SHIFT_PGUP, 0);   // Shift-PageUp
+  add_keyseq("[6$", MKEY_SHIFT_PGDOWN, 0); // Shift-PageDown
+
+  // VT100
+  add_keyseq("[H", MKEY_EQUIV, KEY_HOME); // Home
+  add_keyseq("[F", MKEY_EQUIV, KEY_END);  // End
+
+  // Konsole Linux
+  add_keyseq("[1~", MKEY_EQUIV, KEY_HOME); // Home
+  add_keyseq("[4~", MKEY_EQUIV, KEY_END);  // End
+}
+
+//  scr_init_bindings()
+// Create default key bindings
+// Return 0 if error and 1 if none
+void scr_init_bindings(void)
+{
+  GString *sbuf = g_string_new("");
+
+  // Common backspace key codes: 8, 127
+  settings_set(SETTINGS_TYPE_BINDING, "8", "iline char_bdel");    // Ctrl-h
+  settings_set(SETTINGS_TYPE_BINDING, "127", "iline char_bdel");
+  g_string_printf(sbuf, "%d", KEY_BACKSPACE);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_bdel");
+  g_string_printf(sbuf, "%d", KEY_DC);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_fdel");
+  g_string_printf(sbuf, "%d", KEY_LEFT);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline bchar");
+  g_string_printf(sbuf, "%d", KEY_RIGHT);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline fchar");
+  settings_set(SETTINGS_TYPE_BINDING, "7", "iline compl_cancel"); // Ctrl-g
+  g_string_printf(sbuf, "%d", KEY_UP);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
+               "iline hist_beginning_search_bwd");
+  g_string_printf(sbuf, "%d", KEY_DOWN);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
+               "iline hist_beginning_search_fwd");
+  g_string_printf(sbuf, "%d", KEY_PPAGE);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster up");
+  g_string_printf(sbuf, "%d", KEY_NPAGE);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster down");
+  g_string_printf(sbuf, "%d", KEY_HOME);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_start");
+  settings_set(SETTINGS_TYPE_BINDING, "1", "iline iline_start");  // Ctrl-a
+  g_string_printf(sbuf, "%d", KEY_END);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_end");
+  settings_set(SETTINGS_TYPE_BINDING, "5", "iline iline_end");    // Ctrl-e
+  // Ctrl-o (accept-line-and-down-history):
+  settings_set(SETTINGS_TYPE_BINDING, "15", "iline iline_accept_down_hist");
+  settings_set(SETTINGS_TYPE_BINDING, "21", "iline iline_bdel");  // Ctrl-u
+  g_string_printf(sbuf, "%d", KEY_EOL);
+  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_fdel");
+  settings_set(SETTINGS_TYPE_BINDING, "11", "iline iline_fdel");  // Ctrl-k
+  settings_set(SETTINGS_TYPE_BINDING, "16", "buffer up");         // Ctrl-p
+  settings_set(SETTINGS_TYPE_BINDING, "14", "buffer down");       // Ctrl-n
+  settings_set(SETTINGS_TYPE_BINDING, "20", "iline char_swap");   // Ctrl-t
+  settings_set(SETTINGS_TYPE_BINDING, "23", "iline word_bdel");   // Ctrl-w
+  settings_set(SETTINGS_TYPE_BINDING, "M98", "iline bword");      // Meta-b
+  settings_set(SETTINGS_TYPE_BINDING, "M102", "iline fword");     // Meta-f
+  settings_set(SETTINGS_TYPE_BINDING, "M100", "iline word_fdel"); // Meta-d
+  // Ctrl-Left  (2 codes):
+  settings_set(SETTINGS_TYPE_BINDING, "515", "iline bword");
+  settings_set(SETTINGS_TYPE_BINDING, "516", "iline bword");
+  // Ctrl-Right (2 codes):
+  settings_set(SETTINGS_TYPE_BINDING, "517", "iline fword");
+  settings_set(SETTINGS_TYPE_BINDING, "518", "iline fword");
+  settings_set(SETTINGS_TYPE_BINDING, "12", "screen_refresh");    // Ctrl-l
+  settings_set(SETTINGS_TYPE_BINDING, "27", "chat_disable --show-roster");// Esc
+  settings_set(SETTINGS_TYPE_BINDING, "M27", "chat_disable");     // Esc-Esc
+  settings_set(SETTINGS_TYPE_BINDING, "4", "iline send_multiline"); // Ctrl-d
+  settings_set(SETTINGS_TYPE_BINDING, "M117", "iline word_upcase"); // Meta-u
+  settings_set(SETTINGS_TYPE_BINDING, "M108", "iline word_downcase"); // Meta-l
+  settings_set(SETTINGS_TYPE_BINDING, "M99", "iline word_capit"); // Meta-c
+
+  settings_set(SETTINGS_TYPE_BINDING, "265", "help"); // Bind F1 to help...
+
+  g_string_free(sbuf, TRUE);
+}
+
+//  is_speckey(key)
+// Return TRUE if key is a special code, i.e. no char should be displayed on
+// the screen.  It's not very nice, it's a workaround for the systems where
+// isprint(KEY_PPAGE) returns TRUE...
+static int is_speckey(int key)
+{
+  switch (key) {
+    case 127:
+    case 393:
+    case 402:
+    case KEY_BACKSPACE:
+    case KEY_DC:
+    case KEY_LEFT:
+    case KEY_RIGHT:
+    case KEY_UP:
+    case KEY_DOWN:
+    case KEY_PPAGE:
+    case KEY_NPAGE:
+    case KEY_HOME:
+    case KEY_END:
+    case KEY_EOL:
+        return TRUE;
+  }
+
+  // Fn keys
+  if (key >= 265 && key < 265+12)
+    return TRUE;
+
+  // Special key combinations
+  if (key >= 513 && key <= 521)
+    return TRUE;
+
+  return FALSE;
+}
+
+void scr_InitLocaleCharSet(void)
+{
+  setlocale(LC_ALL, "");
+#ifdef HAVE_LOCALCHARSET_H
+  LocaleCharSet = locale_charset();
+#else
+  LocaleCharSet = nl_langinfo(CODESET);
+#endif
+  utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0);
+}
+
+void scr_InitCurses(void)
+{
+  /* Key sequences initialization */
+  init_keycodes();
+
+  initscr();
+  raw();
+  noecho();
+  nonl();
+  intrflush(stdscr, FALSE);
+  start_color();
+  use_default_colors();
+#ifdef NCURSES_MOUSE_VERSION
+  if (settings_opt_get_int("use_mouse"))
+    mousemask(ALL_MOUSE_EVENTS, NULL);
+#endif
+
+  if (settings_opt_get("escdelay")) {
+#ifdef HAVE_ESCDELAY
+    ESCDELAY = (unsigned) settings_opt_get_int("escdelay");
+#else
+    scr_LogPrint(LPRINT_LOGNORM, "ERROR: no ESCDELAY support.");
+#endif
+  }
+
+  ParseColors();
+
+  getmaxyx(stdscr, maxY, maxX);
+  Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
+  // Note scr_DrawMainWindow() should be called early after scr_InitCurses()
+  // to update Log_Win_Height and set max{X,Y}
+
+  inputLine[0] = 0;
+  ptr_inputline = inputLine;
+
+  if (settings_opt_get("url_regex")) {
+#ifdef HAVE_GLIB_REGEX
+    url_regex = g_regex_new(settings_opt_get("url_regex"),
+                            G_REGEX_OPTIMIZE, 0, NULL);
+#else
+    scr_LogPrint(LPRINT_LOGNORM, "ERROR: Your glib version is too old, "
+                 "cannot use url_regex.");
+#endif // HAVE_GLIB_REGEX
+  }
+
+  Curses = TRUE;
+  return;
+}
+
+void scr_TerminateCurses(void)
+{
+  if (!Curses) return;
+  clear();
+  refresh();
+  endwin();
+#ifdef HAVE_GLIB_REGEX
+  if (url_regex)
+    g_regex_unref(url_regex);
+#endif
+  Curses = FALSE;
+  return;
+}
+
+void scr_Beep(void)
+{
+  beep();
+}
+
+// This and following belongs to dynamic setting of time prefix
+static const char *timeprefixes[] = {
+  "%m-%d %H:%M ",
+  "%H:%M ",
+  " "
+};
+
+static const char *spectimeprefixes[] = {
+  "%m-%d %H:%M:%S   ",
+  "%H:%M:%S   ",
+  "   "
+};
+
+static int timepreflengths[] = {
+  // (length of the corresponding timeprefix + 5)
+  17,
+  11,
+  6
+};
+
+static const char *gettprefix(void)
+{
+  guint n = settings_opt_get_int("time_prefix");
+  return timeprefixes[(n < 3 ? n : 0)];
+}
+
+static const char *getspectprefix(void)
+{
+  guint n = settings_opt_get_int("time_prefix");
+  return spectimeprefixes[(n < 3 ? n : 0)];
+}
+
+guint scr_getprefixwidth(void)
+{
+  guint n = settings_opt_get_int("time_prefix");
+  return timepreflengths[(n < 3 ? n : 0)];
+}
+
+//  scr_print_logwindow(string)
+// Display the string in the log window.
+// Note: The string must be in the user's locale!
+void scr_print_logwindow(const char *string)
+{
+  time_t timestamp;
+  char strtimestamp[64];
+
+  timestamp = time(NULL);
+  strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
+  if (Curses) {
+    wprintw(logWnd, "\n%s %s", strtimestamp, string);
+    update_panels();
+  } else {
+    printf("%s %s\n", strtimestamp, string);
+  }
+}
+
+//  scr_LogPrint(...)
+// Display a message in the log window and in the status buffer.
+// Add the message to the tracelog file if the log flag is set.
+// This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set.
+void scr_LogPrint(unsigned int flag, const char *fmt, ...)
+{
+  time_t timestamp;
+  char strtimestamp[64];
+  char *buffer, *btext;
+  char *convbuf1 = NULL, *convbuf2 = NULL;
+  va_list ap;
+
+  if (!(flag & ~LPRINT_NOTUTF8)) return; // Shouldn't happen
+
+  timestamp = time(NULL);
+  strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
+  va_start(ap, fmt);
+  btext = g_strdup_vprintf(fmt, ap);
+  va_end(ap);
+
+  if (flag & LPRINT_NORMAL) {
+    char *buffer_locale;
+    char *buf_specialwindow;
+
+    buffer = g_strdup_printf("%s %s", strtimestamp, btext);
+
+    // Convert buffer to current locale for wprintw()
+    if (!(flag & LPRINT_NOTUTF8))
+      buffer_locale = convbuf1 = from_utf8(buffer);
+    else
+      buffer_locale = buffer;
+
+    if (!buffer_locale) {
+      wprintw(logWnd,
+              "\n%s*Error: cannot convert string to locale.", strtimestamp);
+      update_panels();
+      g_free(buffer);
+      g_free(btext);
+      return;
+    }
+
+    // For the special status buffer, we need utf-8, but without the timestamp
+    if (flag & LPRINT_NOTUTF8)
+      buf_specialwindow = convbuf2 = to_utf8(btext);
+    else
+      buf_specialwindow = btext;
+
+    if (Curses) {
+      wprintw(logWnd, "\n%s", buffer_locale);
+      update_panels();
+      scr_WriteInWindow(NULL, buf_specialwindow, timestamp,
+                        HBB_PREFIX_SPECIAL, FALSE, 0, NULL);
+    } else {
+      printf("%s\n", buffer_locale);
+      // ncurses are not initialized yet, so we call directly hbuf routine
+      hbuf_add_line(&statushbuf, buf_specialwindow, timestamp,
+        HBB_PREFIX_SPECIAL, 0, 0, 0, NULL);
+    }
+
+    g_free(convbuf1);
+    g_free(convbuf2);
+    g_free(buffer);
+  }
+
+  if (flag & (LPRINT_LOG|LPRINT_DEBUG)) {
+    strftime(strtimestamp, 23, "[%Y-%m-%d %H:%M:%S]", localtime(&timestamp));
+    buffer = g_strdup_printf("%s %s\n", strtimestamp, btext);
+    ut_WriteLog(flag, buffer);
+    g_free(buffer);
+  }
+  g_free(btext);
+}
+
+static winbuf *scr_SearchWindow(const char *winId, int special)
+{
+  char *id;
+  winbuf *wbp;
+
+  if (special)
+    return statusWindow; // Only one special window atm.
+
+  if (!winId)
+    return NULL;
+
+  id = g_strdup(winId);
+  mc_strtolower(id);
+  wbp = g_hash_table_lookup(winbufhash, id);
+  g_free(id);
+  return wbp;
+}
+
+int scr_BuddyBufferExists(const char *bjid)
+{
+  return (scr_SearchWindow(bjid, FALSE) != NULL);
+}
+
+//  scr_new_buddy(title, dontshow)
+// Note: title (aka winId/jid) can be NULL for special buffers
+static winbuf *scr_new_buddy(const char *title, int dont_show)
+{
+  winbuf *tmp;
+
+  tmp = g_new0(winbuf, 1);
+
+  tmp->win = activechatWnd;
+  tmp->panel = activechatPanel;
+
+  if (!dont_show) {
+    currentWindow = tmp;
+  } else {
+    if (currentWindow)
+      top_panel(currentWindow->panel);
+    else
+      top_panel(chatPanel);
+  }
+  update_panels();
+
+  // If title is NULL, this is a special buffer
+  if (title) {
+    char *id;
+    id = hlog_get_log_jid(title);
+    if (id) {
+      winbuf *wb = scr_SearchWindow(id, FALSE);
+      if (!wb)
+        wb = scr_new_buddy(id, TRUE);
+      tmp->bd=wb->bd;
+      g_free(id);
+    } else {  // Load buddy history from file (if enabled)
+      tmp->bd = g_new0(buffdata, 1);
+      hlog_read_history(title, &tmp->bd->hbuf,
+                        maxX - Roster_Width - scr_getprefixwidth());
+    }
+
+    id = g_strdup(title);
+    mc_strtolower(id);
+    g_hash_table_insert(winbufhash, id, tmp);
+  } else {
+    tmp->bd = g_new0(buffdata, 1);
+  }
+  return tmp;
+}
+
+//  scr_line_prefix(line, pref, preflen)
+// Use data from the hbb_line structure and write the prefix
+// to pref (not exceeding preflen, trailing null byte included).
+void scr_line_prefix(hbb_line *line, char *pref, guint preflen)
+{
+  char date[64];
+
+  if (line->timestamp &&
+      !(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) {
+    strftime(date, 30, gettprefix(), localtime(&line->timestamp));
+  } else
+    strcpy(date, "           ");
+
+  if (!(line->flags & HBB_PREFIX_CONT)) {
+    if (line->flags & HBB_PREFIX_INFO) {
+      char dir = '*';
+      if (line->flags & HBB_PREFIX_IN)
+        dir = '<';
+      else if (line->flags & HBB_PREFIX_OUT)
+        dir = '>';
+      g_snprintf(pref, preflen, "%s*%c* ", date, dir);
+    } else if (line->flags & HBB_PREFIX_ERR) {
+      char dir = '#';
+      if (line->flags & HBB_PREFIX_IN)
+        dir = '<';
+      else if (line->flags & HBB_PREFIX_OUT)
+        dir = '>';
+      g_snprintf(pref, preflen, "%s#%c# ", date, dir);
+    } else if (line->flags & HBB_PREFIX_IN) {
+      char cryptflag;
+      if (line->flags & HBB_PREFIX_PGPCRYPT)
+        cryptflag = '~';
+      else if (line->flags & HBB_PREFIX_OTRCRYPT)
+        cryptflag = 'O';
+      else
+        cryptflag = '=';
+      g_snprintf(pref, preflen, "%s<%c= ", date, cryptflag);
+    } else if (line->flags & HBB_PREFIX_OUT) {
+      char cryptflag, receiptflag;
+      if (line->flags & HBB_PREFIX_PGPCRYPT)
+        cryptflag = '~';
+      else if (line->flags & HBB_PREFIX_OTRCRYPT)
+        cryptflag = 'O';
+      else
+        cryptflag = '-';
+      if (line->flags & HBB_PREFIX_RECEIPT)
+        receiptflag = 'r';
+      else
+        receiptflag = '-';
+      g_snprintf(pref, preflen, "%s%c%c> ", date, receiptflag, cryptflag);
+    } else if (line->flags & HBB_PREFIX_SPECIAL) {
+      strftime(date, 30, getspectprefix(), localtime(&line->timestamp));
+      g_snprintf(pref, preflen, "%s   ", date);
+    } else {
+      g_snprintf(pref, preflen, "%s    ", date);
+    }
+  } else {
+    g_snprintf(pref, preflen, "                ");
+  }
+}
+
+//  scr_UpdateWindow()
+// (Re-)Display the given chat window.
+static void scr_UpdateWindow(winbuf *win_entry)
+{
+  int n;
+  guint prefixwidth;
+  char pref[96];
+  hbb_line **lines, *line;
+  GList *hbuf_head;
+  int color;
+
+  prefixwidth = scr_getprefixwidth();
+  prefixwidth = MIN(prefixwidth, sizeof pref);
+
+  // Should the window be empty?
+  if (win_entry->bd->cleared) {
+    werase(win_entry->win);
+    return;
+  }
+
+  // win_entry->bd->top is the top message of the screen.  If it set to NULL,
+  // we are displaying the last messages.
+
+  // We will show the last CHAT_WIN_HEIGHT lines.
+  // Let's find out where it begins.
+  if (!win_entry->bd->top || (g_list_position(g_list_first(win_entry->bd->hbuf),
+                                              win_entry->bd->top) == -1)) {
+    // Move up CHAT_WIN_HEIGHT lines
+    win_entry->bd->hbuf = g_list_last(win_entry->bd->hbuf);
+    hbuf_head = win_entry->bd->hbuf;
+    win_entry->bd->top = NULL; // (Just to make sure)
+    n = 0;
+    while (hbuf_head && (n < CHAT_WIN_HEIGHT-1) && g_list_previous(hbuf_head)) {
+      hbuf_head = g_list_previous(hbuf_head);
+      n++;
+    }
+    // If the buffer is locked, remember current "top" line for the next time.
+    if (win_entry->bd->lock)
+      win_entry->bd->top = hbuf_head;
+  } else
+    hbuf_head = win_entry->bd->top;
+
+  // Get the last CHAT_WIN_HEIGHT lines.
+  lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT);
+
+  // Display these lines
+  for (n = 0; n < CHAT_WIN_HEIGHT; n++) {
+    wmove(win_entry->win, n, 0);
+    line = *(lines+n);
+    if (line) {
+      if (line->flags & HBB_PREFIX_HLIGHT_OUT)
+        color = COLOR_MSGOUT;
+      else if (line->flags & HBB_PREFIX_HLIGHT)
+        color = COLOR_MSGHL;
+      else if (line->flags & HBB_PREFIX_INFO)
+        color = COLOR_INFO;
+      else if (line->flags & HBB_PREFIX_IN)
+        color = COLOR_MSGIN;
+      else
+        color = COLOR_GENERAL;
+
+      if (color != COLOR_GENERAL)
+        wattrset(win_entry->win, get_color(color));
+
+      // Generate the prefix area and display it
+      scr_line_prefix(line, pref, prefixwidth);
+      wprintw(win_entry->win, pref);
+
+      // Make sure we are at the right position
+      wmove(win_entry->win, n, prefixwidth-1);
+
+      // The MUC nick - overwrite with proper color
+      if (line->mucnicklen) {
+        char *mucjid;
+        char tmp;
+        nickcolor *actual = NULL;
+        muccoltype type, *typetmp;
+
+        // Store the char after the nick
+        tmp = line->text[line->mucnicklen];
+        type = glob_muccol;
+        // Terminate the string after the nick
+        line->text[line->mucnicklen] = '\0';
+        mucjid = g_utf8_strdown(CURRENT_JID, -1);
+        if (muccolors) {
+          typetmp = g_hash_table_lookup(muccolors, mucjid);
+          if (typetmp)
+            type = *typetmp;
+        }
+        g_free(mucjid);
+        // Need to generate a color for the specified nick?
+        if ((type == MC_ALL) && (!nickcolors ||
+            !g_hash_table_lookup(nickcolors, line->text))) {
+          char *snick, *mnick;
+          nickcolor *nc;
+          const char *p = line->text;
+          unsigned int nicksum = 0;
+          snick = g_strdup(line->text);
+          mnick = g_strdup(line->text);
+          nc = g_new(nickcolor, 1);
+          ensure_string_htable(&nickcolors, NULL);
+          while (*p)
+            nicksum += *p++;
+          nc->color = nickcols[nicksum % nickcolcount];
+          nc->manual = FALSE;
+          *snick = '<';
+          snick[strlen(snick)-1] = '>';
+          *mnick = '*';
+          mnick[strlen(mnick)-1] = ' ';
+          // Insert them
+          g_hash_table_insert(nickcolors, snick, nc);
+          g_hash_table_insert(nickcolors, mnick, nc);
+        }
+        if (nickcolors)
+          actual = g_hash_table_lookup(nickcolors, line->text);
+        if (actual && ((type == MC_ALL) || (actual->manual))
+            && (line->flags & HBB_PREFIX_IN) &&
+           (!(line->flags & HBB_PREFIX_HLIGHT_OUT)))
+          wattrset(win_entry->win, compose_color(actual->color));
+        wprintw(win_entry->win, "%s", line->text);
+        // Return the char
+        line->text[line->mucnicklen] = tmp;
+        // Return the color back
+        wattrset(win_entry->win, get_color(color));
+      }
+
+      // Display text line
+      wprintw(win_entry->win, "%s", line->text+line->mucnicklen);
+      wclrtoeol(win_entry->win);
+
+      // Return the color back
+      if (color != COLOR_GENERAL)
+        wattrset(win_entry->win, get_color(COLOR_GENERAL));
+
+      g_free(line->text);
+      g_free(line);
+    } else {
+      wclrtobot(win_entry->win);
+      break;
+    }
+  }
+  g_free(lines);
+}
+
+static winbuf *scr_CreateWindow(const char *winId, int special, int dont_show)
+{
+  if (special) {
+    if (!statusWindow) {
+      statusWindow = scr_new_buddy(NULL, dont_show);
+      statusWindow->bd->hbuf = statushbuf;
+    }
+    return statusWindow;
+  } else {
+    return scr_new_buddy(winId, dont_show);
+  }
+}
+
+//  scr_ShowWindow()
+// Display the chat window with the given identifier.
+// "special" must be true if this is a special buffer window.
+static void scr_ShowWindow(const char *winId, int special)
+{
+  winbuf *win_entry;
+
+  win_entry = scr_SearchWindow(winId, special);
+
+  if (!win_entry) {
+    win_entry = scr_CreateWindow(winId, special, FALSE);
+  }
+
+  top_panel(win_entry->panel);
+  currentWindow = win_entry;
+  chatmode = TRUE;
+  if (!win_entry->bd->lock)
+    roster_msg_setflag(winId, special, FALSE);
+  if (!special)
+    roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE);
+  update_roster = TRUE;
+
+  // Refresh the window
+  scr_UpdateWindow(win_entry);
+
+  // Finished :)
+  update_panels();
+
+  top_panel(inputPanel);
+}
+
+//  scr_ShowBuddyWindow()
+// Display the chat window buffer for the current buddy.
+void scr_ShowBuddyWindow(void)
+{
+  const gchar *bjid;
+
+  if (!current_buddy) {
+    bjid = NULL;
+  } else {
+    bjid = CURRENT_JID;
+    if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) {
+      scr_ShowWindow(buddy_getname(BUDDATA(current_buddy)), TRUE);
+      return;
+    }
+  }
+
+  if (!bjid) {
+    top_panel(chatPanel);
+    top_panel(inputPanel);
+    currentWindow = NULL;
+    return;
+  }
+
+  scr_ShowWindow(bjid, FALSE);
+}
+
+//  scr_UpdateBuddyWindow()
+// (Re)Display the current window.
+// If chatmode is enabled, call scr_ShowBuddyWindow(),
+// else display the chat window.
+inline void scr_UpdateBuddyWindow(void)
+{
+  if (chatmode) {
+    scr_ShowBuddyWindow();
+    return;
+  }
+
+  top_panel(chatPanel);
+  top_panel(inputPanel);
+}
+
+//  scr_WriteInWindow()
+// Write some text in the winId window (this usually is a jid).
+// Use winId == NULL for the special status buffer.
+// Lines are splitted when they are too long to fit in the chat window.
+// If this window doesn't exist, it is created.
+void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp,
+                       unsigned int prefix_flags, int force_show,
+                       unsigned mucnicklen, gpointer xep184)
+{
+  winbuf *win_entry;
+  char *text_locale;
+  int dont_show = FALSE;
+  int special;
+  guint num_history_blocks;
+  bool setmsgflg = FALSE;
+  char *nicktmp, *nicklocaltmp;
+
+  // Look for the window entry.
+  special = (winId == NULL);
+  win_entry = scr_SearchWindow(winId, special);
+
+  // Do we have to really show the window?
+  if (!chatmode)
+    dont_show = TRUE;
+  else if ((!force_show) && ((!currentWindow || (currentWindow != win_entry))))
+    dont_show = TRUE;
+
+  // If the window entry doesn't exist yet, let's create it.
+  if (!win_entry) {
+    win_entry = scr_CreateWindow(winId, special, dont_show);
+  }
+
+  // The message must be displayed -> update top pointer
+  if (win_entry->bd->cleared)
+    win_entry->bd->top = g_list_last(win_entry->bd->hbuf);
+
+  // Make sure we do not free the buffer while it's locked or when
+  // top is set.
+  if (win_entry->bd->lock || win_entry->bd->top)
+    num_history_blocks = 0U;
+  else
+    num_history_blocks = get_max_history_blocks();
+
+  text_locale = from_utf8(text);
+  //Convert the nick alone and compute its length
+  if (mucnicklen) {
+    nicktmp = g_strndup(text, mucnicklen);
+    nicklocaltmp = from_utf8(nicktmp);
+    mucnicklen = strlen(nicklocaltmp);
+    g_free(nicklocaltmp);
+    g_free(nicktmp);
+  }
+  hbuf_add_line(&win_entry->bd->hbuf, text_locale, timestamp, prefix_flags,
+                maxX - Roster_Width - scr_getprefixwidth(), num_history_blocks,
+                mucnicklen, xep184);
+  g_free(text_locale);
+
+  if (win_entry->bd->cleared) {
+    win_entry->bd->cleared = FALSE;
+    if (g_list_next(win_entry->bd->top))
+      win_entry->bd->top = g_list_next(win_entry->bd->top);
+  }
+
+  // Make sure the last line appears in the window; update top if necessary
+  if (!win_entry->bd->lock && win_entry->bd->top) {
+    int dist;
+    GList *first = g_list_first(win_entry->bd->hbuf);
+    dist = g_list_position(first, g_list_last(win_entry->bd->hbuf)) -
+           g_list_position(first, win_entry->bd->top);
+    if (dist >= CHAT_WIN_HEIGHT)
+      win_entry->bd->top = NULL;
+  }
+
+  if (!dont_show) {
+    if (win_entry->bd->lock)
+      setmsgflg = TRUE;
+    // Show and refresh the window
+    top_panel(win_entry->panel);
+    scr_UpdateWindow(win_entry);
+    top_panel(inputPanel);
+    update_panels();
+  } else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) {
+    setmsgflg = TRUE;
+  }
+  if (setmsgflg && !special) {
+    if (special && !winId)
+      winId = SPECIAL_BUFFER_STATUS_ID;
+    roster_msg_setflag(winId, special, TRUE);
+    update_roster = TRUE;
+  }
+}
+
+//  scr_UpdateMainStatus()
+// Redraw the main (bottom) status line.
+void scr_UpdateMainStatus(int forceupdate)
+{
+  char *sm = from_utf8(xmpp_getstatusmsg());
+  const char *info = settings_opt_get("info");
+
+  werase(mainstatusWnd);
+  if (info) {
+    char *info_locale = from_utf8(info);
+    mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s: %s",
+              (unread_msg(NULL) ? '#' : ' '),
+              imstatus2char[xmpp_getstatus()],
+              info_locale, (sm ? sm : ""));
+    g_free(info_locale);
+  } else
+    mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s",
+              (unread_msg(NULL) ? '#' : ' '),
+              imstatus2char[xmpp_getstatus()], (sm ? sm : ""));
+  if (forceupdate) {
+    top_panel(inputPanel);
+    update_panels();
+  }
+  g_free(sm);
+}
+
+//  scr_DrawMainWindow()
+// Set fullinit to TRUE to also create panels.  Set it to FALSE for a resize.
+//
+// I think it could be improved a _lot_ but I'm really not an ncurses
+// expert... :-\   Mikael.
+//
+void scr_DrawMainWindow(unsigned int fullinit)
+{
+  int requested_size;
+  gchar *ver, *message;
+  int chat_y_pos, chatstatus_y_pos, log_y_pos;
+  int roster_x_pos, chat_x_pos;
+
+  Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
+  requested_size = settings_opt_get_int("log_win_height");
+  if (requested_size > 0) {
+    if (maxY > requested_size + 3)
+      Log_Win_Height = requested_size + 2;
+    else
+      Log_Win_Height = ((maxY > 5) ? (maxY - 2) : 3);
+  } else if (requested_size < 0) {
+    Log_Win_Height = 3;
+  }
+
+  if (maxY < Log_Win_Height+2) {
+    if (maxY < 5) {
+      Log_Win_Height = 3;
+      maxY = Log_Win_Height+2;
+    } else {
+      Log_Win_Height = maxY - 2;
+    }
+  }
+
+  if (roster_hidden) {
+    Roster_Width = 0;
+  } else {
+    requested_size = settings_opt_get_int("roster_width");
+    if (requested_size > 1)
+      Roster_Width = requested_size;
+    else if (requested_size == 1)
+      Roster_Width = 2;
+    else
+      Roster_Width = DEFAULT_ROSTER_WIDTH;
+  }
+
+  log_win_on_top = (settings_opt_get_int("log_win_on_top") == 1);
+  roster_win_on_right = (settings_opt_get_int("roster_win_on_right") == 1);
+
+  if (log_win_on_top) {
+    chat_y_pos = Log_Win_Height-1;
+    log_y_pos = 0;
+    chatstatus_y_pos = Log_Win_Height-2;
+  } else {
+    chat_y_pos = 0;
+    log_y_pos = CHAT_WIN_HEIGHT+1;
+    chatstatus_y_pos = CHAT_WIN_HEIGHT;
+  }
+
+  if (roster_win_on_right) {
+    roster_x_pos = maxX - Roster_Width;
+    chat_x_pos = 0;
+  } else {
+    roster_x_pos = 0;
+    chat_x_pos = Roster_Width;
+  }
+
+  if (fullinit) {
+    if (!winbufhash)
+      winbufhash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+    /* Create windows */
+    rosterWnd = newwin(CHAT_WIN_HEIGHT, Roster_Width, chat_y_pos, roster_x_pos);
+    chatWnd   = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
+                       chat_x_pos);
+    activechatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
+                           chat_x_pos);
+    logWnd    = newwin(Log_Win_Height-2, maxX, log_y_pos, 0);
+    chatstatusWnd = newwin(1, maxX, chatstatus_y_pos, 0);
+    mainstatusWnd = newwin(1, maxX, maxY-2, 0);
+    inputWnd  = newwin(1, maxX, maxY-1, 0);
+    if (!rosterWnd || !chatWnd || !logWnd || !inputWnd) {
+      scr_TerminateCurses();
+      fprintf(stderr, "Cannot create windows!\n");
+      exit(EXIT_FAILURE);
+    }
+    wbkgd(rosterWnd,      get_color(COLOR_GENERAL));
+    wbkgd(chatWnd,        get_color(COLOR_GENERAL));
+    wbkgd(activechatWnd,  get_color(COLOR_GENERAL));
+    wbkgd(logWnd,         get_color(COLOR_GENERAL));
+    wbkgd(chatstatusWnd,  get_color(COLOR_STATUS));
+    wbkgd(mainstatusWnd,  get_color(COLOR_STATUS));
+  } else {
+    /* Resize/move windows */
+    wresize(rosterWnd, CHAT_WIN_HEIGHT, Roster_Width);
+    wresize(chatWnd, CHAT_WIN_HEIGHT, maxX - Roster_Width);
+    wresize(logWnd, Log_Win_Height-2, maxX);
+
+    mvwin(chatWnd, chat_y_pos, chat_x_pos);
+    mvwin(rosterWnd, chat_y_pos, roster_x_pos);
+    mvwin(logWnd, log_y_pos, 0);
+
+    // Resize & move chat status window
+    wresize(chatstatusWnd, 1, maxX);
+    mvwin(chatstatusWnd, chatstatus_y_pos, 0);
+    // Resize & move main status window
+    wresize(mainstatusWnd, 1, maxX);
+    mvwin(mainstatusWnd, maxY-2, 0);
+    // Resize & move input line window
+    wresize(inputWnd, 1, maxX);
+    mvwin(inputWnd, maxY-1, 0);
+
+    werase(chatWnd);
+  }
+
+  /* Draw/init windows */
+
+  ver = mcabber_version();
+  message = g_strdup_printf("MCabber version %s.\n", ver);
+  mvwprintw(chatWnd, 0, 0, message);
+  mvwprintw(chatWnd, 1, 0, "http://mcabber.com/");
+  g_free(ver);
+  g_free(message);
+
+  // Auto-scrolling in log window
+  scrollok(logWnd, TRUE);
+
+
+  if (fullinit) {
+    // Enable keypad (+ special keys)
+    keypad(inputWnd, TRUE);
+#ifdef __MirBSD__
+    wtimeout(inputWnd, 50 /* ms */);
+#else
+    nodelay(inputWnd, TRUE);
+#endif
+
+    // Create panels
+    rosterPanel = new_panel(rosterWnd);
+    chatPanel   = new_panel(chatWnd);
+    activechatPanel = new_panel(activechatWnd);
+    logPanel    = new_panel(logWnd);
+    chatstatusPanel = new_panel(chatstatusWnd);
+    mainstatusPanel = new_panel(mainstatusWnd);
+    inputPanel  = new_panel(inputWnd);
+
+    // Build the buddylist at least once, to make sure the special buffer
+    // is added
+    buddylist_build();
+
+    // Init prev_chatwidth; this variable will be used to prevent us
+    // from rewrapping buffers when the width doesn't change.
+    prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
+    // Wrap existing status buffer lines
+    hbuf_rebuild(&statushbuf, prev_chatwidth);
+
+#ifndef UNICODE
+    if (utf8_mode)
+      scr_LogPrint(LPRINT_NORMAL,
+                   "WARNING: Compiled without full UTF-8 support!");
+#endif
+  } else {
+    // Update panels
+    replace_panel(rosterPanel, rosterWnd);
+    replace_panel(chatPanel, chatWnd);
+    replace_panel(logPanel, logWnd);
+    replace_panel(chatstatusPanel, chatstatusWnd);
+    replace_panel(mainstatusPanel, mainstatusWnd);
+    replace_panel(inputPanel, inputWnd);
+  }
+
+  // We'll need to redraw the roster
+  update_roster = TRUE;
+  return;
+}
+
+static void resize_win_buffer(gpointer key, gpointer value, gpointer data)
+{
+  winbuf *wbp = value;
+  struct dimensions *dim = data;
+  int chat_x_pos, chat_y_pos;
+  int new_chatwidth;
+
+  if (!(wbp && wbp->win))
+    return;
+
+  if (log_win_on_top)
+    chat_y_pos = Log_Win_Height-1;
+  else
+    chat_y_pos = 0;
+
+  if (roster_win_on_right)
+    chat_x_pos = 0;
+  else
+    chat_x_pos = Roster_Width;
+
+  // Resize/move buddy window
+  wresize(wbp->win, dim->l, dim->c);
+  mvwin(wbp->win, chat_y_pos, chat_x_pos);
+  werase(wbp->win);
+  // If a panel exists, replace the old window with the new
+  if (wbp->panel)
+    replace_panel(wbp->panel, wbp->win);
+  // Redo line wrapping
+  wbp->bd->top = hbuf_previous_persistent(wbp->bd->top);
+
+  new_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
+  if (new_chatwidth != prev_chatwidth)
+    hbuf_rebuild(&wbp->bd->hbuf, new_chatwidth);
+}
+
+//  scr_Resize()
+// Function called when the window is resized.
+// - Resize windows
+// - Rewrap lines in each buddy buffer
+void scr_Resize(void)
+{
+  struct dimensions dim;
+
+  // First, update the global variables
+  getmaxyx(stdscr, maxY, maxX);
+  // scr_DrawMainWindow() will take care of maxY and Log_Win_Height
+
+  // Make sure the cursor stays inside the window
+  check_offset(0);
+
+  // Resize windows and update panels
+  scr_DrawMainWindow(FALSE);
+
+  // Resize all buddy windows
+  dim.l = CHAT_WIN_HEIGHT;
+  dim.c = maxX - Roster_Width;
+  if (dim.c < 1)
+    dim.c = 1;
+
+  // Resize all buffers
+  g_hash_table_foreach(winbufhash, resize_win_buffer, &dim);
+
+  // Resize/move special status buffer
+  if (statusWindow)
+    resize_win_buffer(NULL, statusWindow, &dim);
+
+  // Update prev_chatwidth, now that all buffers have been resized
+  prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
+
+  // Refresh current buddy window
+  if (chatmode)
+    scr_ShowBuddyWindow();
+}
+
+//  scr_UpdateChatStatus(forceupdate)
+// Redraw the buddy status bar.
+// Set forceupdate to TRUE if update_panels() must be called.
+void scr_UpdateChatStatus(int forceupdate)
+{
+  unsigned short btype, isgrp, ismuc, isspe;
+  const char *btypetext = "Unknown";
+  const char *fullname;
+  const char *msg = NULL;
+  char status;
+  char *buf, *buf_locale;
+
+  // Usually we need to update the bottom status line too,
+  // at least to refresh the pending message flag.
+  scr_UpdateMainStatus(FALSE);
+
+  // Clear the line
+  werase(chatstatusWnd);
+
+  if (!current_buddy) {
+    if (forceupdate) {
+      update_panels();
+    }
+    return;
+  }
+
+  fullname = buddy_getname(BUDDATA(current_buddy));
+  btype = buddy_gettype(BUDDATA(current_buddy));
+
+  isgrp = ismuc = isspe = 0;
+  if (btype & ROSTER_TYPE_USER) {
+    btypetext = "Buddy";
+  } else if (btype & ROSTER_TYPE_GROUP) {
+    btypetext = "Group";
+    isgrp = 1;
+  } else if (btype & ROSTER_TYPE_AGENT) {
+    btypetext = "Agent";
+  } else if (btype & ROSTER_TYPE_ROOM) {
+    btypetext = "Room";
+    ismuc = 1;
+  } else if (btype & ROSTER_TYPE_SPECIAL) {
+    btypetext = "Special buffer";
+    isspe = 1;
+  }
+
+  if (chatmode) {
+    wprintw(chatstatusWnd, "~");
+  } else {
+    unsigned short bflags = buddy_getflags(BUDDATA(current_buddy));
+    if (bflags & ROSTER_FLAG_MSG) {
+      // There is an unread message from the current buddy
+      wprintw(chatstatusWnd, "#");
+    }
+  }
+
+  if (chatmode && !isgrp) {
+    winbuf *win_entry;
+    win_entry = scr_SearchWindow(buddy_getjid(BUDDATA(current_buddy)), isspe);
+    if (win_entry && win_entry->bd->lock)
+      mvwprintw(chatstatusWnd, 0, 0, "*");
+  }
+
+  if (isgrp || isspe) {
+    buf_locale = from_utf8(fullname);
+    mvwprintw(chatstatusWnd, 0, 5, "%s: %s", btypetext, buf_locale);
+    g_free(buf_locale);
+    if (forceupdate) {
+      update_panels();
+    }
+    return;
+  }
+
+  status = '?';
+
+  if (ismuc) {
+    if (buddy_getinsideroom(BUDDATA(current_buddy)))
+      status = 'C';
+    else
+      status = 'x';
+  } else if (xmpp_getstatus() != offline) {
+    enum imstatus budstate;
+    budstate = buddy_getstatus(BUDDATA(current_buddy), NULL);
+    if (budstate < imstatus_size)
+      status = imstatus2char[budstate];
+  }
+
+  // No status message for MUC rooms
+  if (!ismuc) {
+    GSList *resources, *p_res, *p_next_res;
+    resources = buddy_getresources(BUDDATA(current_buddy));
+
+    for (p_res = resources ; p_res ; p_res = p_next_res) {
+      p_next_res = g_slist_next(p_res);
+      // Store the status message of the latest resource (highest priority)
+      if (!p_next_res)
+        msg = buddy_getstatusmsg(BUDDATA(current_buddy), p_res->data);
+      g_free(p_res->data);
+    }
+    g_slist_free(resources);
+  } else {
+    msg = buddy_gettopic(BUDDATA(current_buddy));
+  }
+
+  if (msg)
+    buf = g_strdup_printf("[%c] %s: %s -- %s", status, btypetext, fullname, msg);
+  else
+    buf = g_strdup_printf("[%c] %s: %s", status, btypetext, fullname);
+  replace_nl_with_dots(buf);
+  buf_locale = from_utf8(buf);
+  mvwprintw(chatstatusWnd, 0, 1, "%s", buf_locale);
+  g_free(buf_locale);
+  g_free(buf);
+
+  // Display chatstates of the contact, if available.
+  if (btype & ROSTER_TYPE_USER) {
+    char eventchar = 0;
+    guint event;
+
+    // We do not specify the resource here, so one of the resources with the
+    // highest priority will be used.
+    event = buddy_resource_getevents(BUDDATA(current_buddy), NULL);
+
+    if (event == ROSTER_EVENT_ACTIVE)
+      eventchar = 'A';
+    else if (event == ROSTER_EVENT_COMPOSING)
+      eventchar = 'C';
+    else if (event == ROSTER_EVENT_PAUSED)
+      eventchar = 'P';
+    else if (event == ROSTER_EVENT_INACTIVE)
+      eventchar = 'I';
+    else if (event == ROSTER_EVENT_GONE)
+      eventchar = 'G';
+
+    if (eventchar)
+      mvwprintw(chatstatusWnd, 0, maxX-3, "[%c]", eventchar);
+  }
+
+
+  if (forceupdate) {
+    update_panels();
+  }
+}
+
+void increment_if_buddy_not_filtered(gpointer rosterdata, void *param)
+{
+  int *p = param;
+  if (buddylist_is_status_filtered(buddy_getstatus(rosterdata, NULL)))
+    *p=*p+1;
+}
+
+//  scr_DrawRoster()
+// Display the buddylist (not really the roster) on the screen
+void scr_DrawRoster(void)
+{
+  static int offset = 0;
+  char *name, *rline;
+  int maxx, maxy;
+  GList *buddy;
+  int i, n;
+  int rOffset;
+  int cursor_backup;
+  char status, pending;
+  enum imstatus currentstatus = xmpp_getstatus();
+  int x_pos;
+
+  // We can reset update_roster
+  update_roster = FALSE;
+
+  getmaxyx(rosterWnd, maxy, maxx);
+  maxx--;  // Last char is for vertical border
+
+  cursor_backup = curs_set(0);
+
+  if (!buddylist)
+    offset = 0;
+  else
+    scr_UpdateChatStatus(FALSE);
+
+  // Cleanup of roster window
+  werase(rosterWnd);
+
+  if (Roster_Width) {
+    int line_x_pos = roster_win_on_right ? 0 : Roster_Width-1;
+    // Redraw the vertical line (not very good...)
+    wattrset(rosterWnd, get_color(COLOR_GENERAL));
+    for (i=0 ; i < CHAT_WIN_HEIGHT ; i++)
+      mvwaddch(rosterWnd, i, line_x_pos, ACS_VLINE);
+  }
+
+  // Leave now if buddylist is empty or the roster is hidden
+  if (!buddylist || !Roster_Width) {
+    update_panels();
+    curs_set(cursor_backup);
+    return;
+  }
+
+  // Update offset if necessary
+  // a) Try to show as many buddylist items as possible
+  i = g_list_length(buddylist) - maxy;
+  if (i < 0)
+    i = 0;
+  if (i < offset)
+    offset = i;
+  // b) Make sure the current_buddy is visible
+  i = g_list_position(buddylist, current_buddy);
+  if (i == -1) { // This is bad
+    scr_LogPrint(LPRINT_NORMAL, "Doh! Can't find current selected buddy!!");
+    curs_set(cursor_backup);
+    return;
+  } else if (i < offset) {
+    offset = i;
+  } else if (i+1 > offset + maxy) {
+    offset = i + 1 - maxy;
+  }
+
+  if (roster_win_on_right)
+    x_pos = 1; // 1 char offset (vertical line)
+  else
+    x_pos = 0;
+
+  name = g_new0(char, 4*Roster_Width);
+  rline = g_new0(char, 4*Roster_Width+1);
+
+  buddy = buddylist;
+  rOffset = offset;
+
+  for (i=0; i<maxy && buddy; buddy = g_list_next(buddy)) {
+    unsigned short bflags, btype, ismsg, isgrp, ismuc, ishid, isspe;
+    gchar *rline_locale;
+    GSList *resources, *p_res;
+
+    bflags = buddy_getflags(BUDDATA(buddy));
+    btype = buddy_gettype(BUDDATA(buddy));
+
+    ismsg = bflags & ROSTER_FLAG_MSG;
+    ishid = bflags & ROSTER_FLAG_HIDE;
+    isgrp = btype  & ROSTER_TYPE_GROUP;
+    ismuc = btype  & ROSTER_TYPE_ROOM;
+    isspe = btype  & ROSTER_TYPE_SPECIAL;
+
+    if (rOffset > 0) {
+      rOffset--;
+      continue;
+    }
+
+    status = '?';
+    pending = ' ';
+
+    resources = buddy_getresources(BUDDATA(buddy));
+    for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
+      guint events = buddy_resource_getevents(BUDDATA(buddy),
+                                              p_res ? p_res->data : "");
+      if ((events & ROSTER_EVENT_PAUSED) && pending != '+')
+        pending = '.';
+      if (events & ROSTER_EVENT_COMPOSING)
+        pending = '+';
+      g_free(p_res->data);
+    }
+    g_slist_free(resources);
+
+    // Display message notice if there is a message flag, but not
+    // for unfolded groups.
+    if (ismsg && (!isgrp || ishid)) {
+      pending = '#';
+    }
+
+    if (ismuc) {
+      if (buddy_getinsideroom(BUDDATA(buddy)))
+        status = 'C';
+      else
+        status = 'x';
+    } else if (currentstatus != offline) {
+      enum imstatus budstate;
+      budstate = buddy_getstatus(BUDDATA(buddy), NULL);
+      if (budstate < imstatus_size)
+        status = imstatus2char[budstate];
+    }
+    if (buddy == current_buddy) {
+      if (pending == '#')
+        wattrset(rosterWnd, get_color(COLOR_ROSTERSELNMSG));
+      else
+        wattrset(rosterWnd, get_color(COLOR_ROSTERSEL));
+      // The 3 following lines aim at coloring the whole line
+      wmove(rosterWnd, i, x_pos);
+      for (n = 0; n < maxx; n++)
+        waddch(rosterWnd, ' ');
+    } else {
+      if (pending == '#')
+        wattrset(rosterWnd, get_color(COLOR_ROSTERNMSG));
+      else {
+        int color = get_color(COLOR_ROSTER);
+        if ((!isspe) && (!isgrp)) {//Look for color rules
+          GSList *head;
+          const char *jid = buddy_getjid(BUDDATA(buddy));
+          for (head = rostercolrules; head; head = g_slist_next(head)) {
+            rostercolor *rc = head->data;
+            if (g_pattern_match_string(rc->compiled, jid) &&
+                (!strcmp("*", rc->status) || strchr(rc->status, status))) {
+              color = compose_color(rc->color);
+              break;
+            }
+          }
+        }
+        wattrset(rosterWnd, color);
+      }
+    }
+
+    if (Roster_Width > 7)
+      g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-7);
+    else
+      name[0] = 0;
+
+    if (isgrp) {
+      if (ishid) {
+        int group_count = 0;
+        foreach_group_member(BUDDATA(buddy), increment_if_buddy_not_filtered,
+                             &group_count);
+        snprintf(rline, 4*Roster_Width, " %c+++ %s (%i)", pending, name,
+                 group_count);
+        /* Do not display the item count if there isn't enough space */
+        if (g_utf8_strlen(rline, 4*Roster_Width) >= Roster_Width)
+          snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name);
+      }
+      else
+        snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name);
+    } else if (isspe) {
+      snprintf(rline, 4*Roster_Width, " %c%s", pending, name);
+    } else {
+      char sepleft  = '[';
+      char sepright = ']';
+      if (btype & ROSTER_TYPE_USER) {
+        guint subtype = buddy_getsubscription(BUDDATA(buddy));
+        if (status == '_' && !(subtype & sub_to))
+          status = '?';
+        if (!(subtype & sub_from)) {
+          sepleft  = '{';
+          sepright = '}';
+        }
+      }
+
+      snprintf(rline, 4*Roster_Width,
+               " %c%c%c%c %s", pending, sepleft, status, sepright, name);
+    }
+
+    rline_locale = from_utf8(rline);
+    mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale);
+    g_free(rline_locale);
+    i++;
+  }
+
+  g_free(rline);
+  g_free(name);
+  top_panel(inputPanel);
+  update_panels();
+  curs_set(cursor_backup);
+}
+
+//  scr_RosterVisibility(status)
+// Set the roster visibility:
+// status=1   Show roster
+// status=0   Hide roster
+// status=-1  Toggle roster status
+void scr_RosterVisibility(int status)
+{
+  int old_roster_status = roster_hidden;
+
+  if (status > 0)
+    roster_hidden = FALSE;
+  else if (status == 0)
+    roster_hidden = TRUE;
+  else
+    roster_hidden = !roster_hidden;
+
+  if (roster_hidden != old_roster_status) {
+    // Recalculate windows size and redraw
+    scr_Resize();
+    redrawwin(stdscr);
+  }
+}
+
+#ifdef HAVE_GLIB_REGEX
+static inline void scr_LogUrls(const gchar *string)
+{
+  GMatchInfo *match_info;
+
+  g_regex_match_full(url_regex, string, -1, 0, 0, &match_info, NULL);
+  while (g_match_info_matches(match_info)) {
+    gchar *url = g_match_info_fetch(match_info, 0);
+    scr_print_logwindow(url);
+    g_free(url);
+    g_match_info_next(match_info, NULL);
+  }
+  g_match_info_free(match_info);
+}
+#endif
+
+void scr_WriteMessage(const char *bjid, const char *text,
+                      time_t timestamp, guint prefix_flags,
+                      unsigned mucnicklen, gpointer xep184)
+{
+  char *xtext;
+
+  if (!timestamp) timestamp = time(NULL);
+
+  xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars
+
+  scr_WriteInWindow(bjid, xtext, timestamp, prefix_flags, FALSE, mucnicklen,
+                    xep184);
+
+  if (xtext != (char*)text)
+    g_free(xtext);
+}
+
+// If prefix is NULL, HBB_PREFIX_IN is supposed.
+void scr_WriteIncomingMessage(const char *jidfrom, const char *text,
+        time_t timestamp, guint prefix, unsigned mucnicklen)
+{
+  if (!(prefix &
+        ~HBB_PREFIX_NOFLAG & ~HBB_PREFIX_HLIGHT & ~HBB_PREFIX_HLIGHT_OUT &
+        ~HBB_PREFIX_PGPCRYPT & ~HBB_PREFIX_OTRCRYPT))
+    prefix |= HBB_PREFIX_IN;
+
+#ifdef HAVE_GLIB_REGEX
+  if (url_regex)
+    scr_LogUrls(text);
+#endif
+  scr_WriteMessage(jidfrom, text, timestamp, prefix, mucnicklen, NULL);
+}
+
+void scr_WriteOutgoingMessage(const char *jidto, const char *text, guint prefix,
+                              gpointer xep184)
+{
+  GSList *roster_elt;
+  roster_elt = roster_find(jidto, jidsearch,
+                           ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
+
+  scr_WriteMessage(jidto, text,
+                   0, prefix|HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT, 0, xep184);
+
+  // Show jidto's buffer unless the buddy is not in the buddylist
+  if (roster_elt && g_list_position(buddylist, roster_elt->data) != -1)
+    scr_ShowWindow(jidto, FALSE);
+}
+
+void scr_RemoveReceiptFlag(const char *bjid, gpointer xep184)
+{
+  winbuf *win_entry = scr_SearchWindow(bjid, FALSE);
+  if (win_entry) {
+    hbuf_remove_receipt(win_entry->bd->hbuf, xep184);
+    if (chatmode && (buddy_search_jid(bjid) == current_buddy))
+      scr_UpdateBuddyWindow();
+  }
+}
+
+static inline void set_autoaway(bool setaway)
+{
+  static enum imstatus oldstatus;
+  static char *oldmsg;
+  Autoaway = setaway;
+
+  if (setaway) {
+    const char *msg, *prevmsg;
+    oldstatus = xmpp_getstatus();
+    if (oldmsg) {
+      g_free(oldmsg);
+      oldmsg = NULL;
+    }
+    prevmsg = xmpp_getstatusmsg();
+    msg = settings_opt_get("message_autoaway");
+    if (!msg)
+      msg = prevmsg;
+    if (prevmsg)
+      oldmsg = g_strdup(prevmsg);
+    xmpp_setstatus(away, NULL, msg, FALSE);
+  } else {
+    // Back
+    xmpp_setstatus(oldstatus, NULL, (oldmsg ? oldmsg : ""), FALSE);
+    if (oldmsg) {
+      g_free(oldmsg);
+      oldmsg = NULL;
+    }
+  }
+}
+
+//  set_chatstate(state)
+// Set the current chat state (0=active, 1=composing, 2=paused)
+// If the chat state has changed, call xmpp_send_chatstate()
+static inline void set_chatstate(int state)
+{
+#if defined JEP0022 || defined JEP0085
+  if (chatstates_disabled)
+    return;
+  if (!chatmode)
+    state = 0;
+  if (state != chatstate) {
+    chatstate = state;
+    if (current_buddy &&
+        buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_USER) {
+      guint jep_state;
+      if (chatstate == 1) {
+        if (chatstate_timeout_id == 0)
+          chatstate_timeout_id = g_timeout_add_seconds(1,
+                                                       scr_ChatStatesTimeout,
+                                                       NULL);
+        jep_state = ROSTER_EVENT_COMPOSING;
+      }
+      else if (chatstate == 2)
+        jep_state = ROSTER_EVENT_PAUSED;
+      else
+        jep_state = ROSTER_EVENT_ACTIVE;
+      xmpp_send_chatstate(BUDDATA(current_buddy), jep_state);
+    }
+    if (!chatstate)
+      chatstate_timestamp = 0;
+  }
+#endif
+}
+
+#if defined JEP0022 || defined JEP0085
+gboolean scr_ChatStatesTimeout(void)
+{
+  time_t now;
+  time(&now);
+  // Check if we're currently composing...
+  if (chatstate != 1 || !chatstate_timestamp) {
+    chatstate_timeout_id = 0;
+    return FALSE;
+  }
+
+  // If the timeout is reached, let's change the state right now.
+  if (now >= chatstate_timestamp + COMPOSING_TIMEOUT) {
+    chatstate_timestamp = now;
+    set_chatstate(2);
+    chatstate_timeout_id = 0;
+    return FALSE;
+  }
+  return TRUE;
+}
+#endif
+
+// Check if we should enter/leave automatic away status
+void scr_CheckAutoAway(int activity)
+{
+  enum imstatus cur_st;
+  unsigned int autoaway_timeout = settings_opt_get_int("autoaway");
+
+  if (Autoaway && activity) set_autoaway(FALSE);
+  if (!autoaway_timeout) return;
+  if (!LastActivity || activity) time(&LastActivity);
+
+  cur_st = xmpp_getstatus();
+  // Auto-away is disabled for the following states
+  if ((cur_st != available) && (cur_st != freeforchat))
+    return;
+
+  if (!activity) {
+    time_t now;
+    time(&now);
+    if (!Autoaway && (now > LastActivity + (time_t)autoaway_timeout))
+      set_autoaway(TRUE);
+  }
+}
+
+//  set_current_buddy(newbuddy)
+// Set the current_buddy to newbuddy (if not NULL)
+// Lock the newbuddy, and unlock the previous current_buddy
+static void set_current_buddy(GList *newbuddy)
+{
+  enum imstatus prev_st = imstatus_size;
+  /* prev_st initialized to imstatus_size, which is used as "undef" value.
+   * We are sure prev_st will get a different status value after the
+   * buddy_getstatus() call.
+   */
+
+  if (!current_buddy || !newbuddy)  return;
+  if (newbuddy == current_buddy)    return;
+
+  // We're moving to another buddy.  We're thus inactive wrt current_buddy.
+  set_chatstate(0);
+  // We don't want the chatstate to be changed again right now.
+  lock_chatstate = TRUE;
+
+  prev_st = buddy_getstatus(BUDDATA(current_buddy), NULL);
+  buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
+  if (chatmode)
+    alternate_buddy = current_buddy;
+  current_buddy = newbuddy;
+  // Lock the buddy in the buddylist if we're in chat mode
+  if (chatmode)
+    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);
+  // We should rebuild the buddylist but not everytime
+  // Here we check if we were locking a buddy who is actually offline,
+  // and hide_offline_buddies is TRUE.  In which case we need to rebuild.
+  if (!(buddylist_get_filter() & 1<<prev_st))
+    buddylist_build();
+  update_roster = TRUE;
+}
+
+//  scr_RosterTop()
+// Go to the first buddy in the buddylist
+void scr_RosterTop(void)
+{
+  set_current_buddy(buddylist);
+  if (chatmode)
+    scr_ShowBuddyWindow();
+}
+
+//  scr_RosterBottom()
+// Go to the last buddy in the buddylist
+void scr_RosterBottom(void)
+{
+  set_current_buddy(g_list_last(buddylist));
+  if (chatmode)
+    scr_ShowBuddyWindow();
+}
+
+//  scr_RosterUpDown(updown, n)
+// Go to the nth next buddy in the buddylist
+// (up if updown == -1, down if updown == 1)
+void scr_RosterUpDown(int updown, unsigned int n)
+{
+  unsigned int i;
+
+  if (updown < 0) {
+    for (i = 0; i < n; i++)
+      set_current_buddy(g_list_previous(current_buddy));
+  } else {
+    for (i = 0; i < n; i++)
+      set_current_buddy(g_list_next(current_buddy));
+  }
+  if (chatmode)
+    scr_ShowBuddyWindow();
+}
+
+//  scr_RosterPrevGroup()
+// Go to the previous group in the buddylist
+void scr_RosterPrevGroup(void)
+{
+  GList *bud;
+
+  for (bud = current_buddy ; bud ; ) {
+    bud = g_list_previous(bud);
+    if (!bud)
+      break;
+    if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
+      set_current_buddy(bud);
+      if (chatmode)
+        scr_ShowBuddyWindow();
+      break;
+    }
+  }
+}
+
+//  scr_RosterNextGroup()
+// Go to the next group in the buddylist
+void scr_RosterNextGroup(void)
+{
+  GList *bud;
+
+  for (bud = current_buddy ; bud ; ) {
+    bud = g_list_next(bud);
+    if (!bud)
+      break;
+    if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
+      set_current_buddy(bud);
+      if (chatmode)
+        scr_ShowBuddyWindow();
+      break;
+    }
+  }
+}
+
+//  scr_RosterSearch(str)
+// Look forward for a buddy with jid/name containing str.
+void scr_RosterSearch(char *str)
+{
+  set_current_buddy(buddy_search(str));
+  if (chatmode)
+    scr_ShowBuddyWindow();
+}
+
+//  scr_RosterJumpJid(bjid)
+// Jump to buddy bjid.
+// NOTE: With this function, the buddy is added to the roster if doesn't exist.
+void scr_RosterJumpJid(char *barejid)
+{
+  GSList *roster_elt;
+  // Look for an existing buddy
+  roster_elt = roster_find(barejid, jidsearch,
+                 ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
+  // Create it if necessary
+  if (!roster_elt)
+    roster_elt = roster_add_user(barejid, NULL, NULL, ROSTER_TYPE_USER,
+                                 sub_none, -1);
+  // Set a lock to see it in the buddylist
+  buddy_setflags(BUDDATA(roster_elt), ROSTER_FLAG_LOCK, TRUE);
+  buddylist_build();
+  // Jump to the buddy
+  set_current_buddy(buddy_search_jid(barejid));
+  if (chatmode)
+    scr_ShowBuddyWindow();
+}
+
+//  scr_RosterUnreadMessage(next)
+// Go to a new message.  If next is not null, try to go to the next new
+// message.  If it is not possible or if next is NULL, go to the first new
+// message from unread_list.
+void scr_RosterUnreadMessage(int next)
+{
+  gpointer unread_ptr;
+  gpointer refbuddata;
+  GList *nbuddy;
+
+  if (!current_buddy) return;
+
+  if (next) refbuddata = BUDDATA(current_buddy);
+  else      refbuddata = NULL;
+
+  unread_ptr = unread_msg(refbuddata);
+  if (!unread_ptr) return;
+
+  if (!(buddy_gettype(unread_ptr) & ROSTER_TYPE_SPECIAL)) {
+    gpointer ngroup;
+    // If buddy is in a folded group, we need to expand it
+    ngroup = buddy_getgroup(unread_ptr);
+    if (buddy_getflags(ngroup) & ROSTER_FLAG_HIDE) {
+      buddy_setflags(ngroup, ROSTER_FLAG_HIDE, FALSE);
+      buddylist_build();
+    }
+  }
+
+  nbuddy = g_list_find(buddylist, unread_ptr);
+  if (nbuddy) {
+    set_current_buddy(nbuddy);
+    if (chatmode) scr_ShowBuddyWindow();
+  } else
+    scr_LogPrint(LPRINT_LOGNORM, "Error: nbuddy == NULL"); // should not happen
+}
+
+//  scr_RosterJumpAlternate()
+// Try to jump to alternate (== previous) buddy
+void scr_RosterJumpAlternate(void)
+{
+  if (!alternate_buddy || g_list_position(buddylist, alternate_buddy) == -1)
+    return;
+  set_current_buddy(alternate_buddy);
+  if (chatmode)
+    scr_ShowBuddyWindow();
+}
+
+//  scr_RosterDisplay(filter)
+// Set the roster filter mask.  If filter is null/empty, the current
+// mask is displayed.
+void scr_RosterDisplay(const char *filter)
+{
+  guchar status;
+  enum imstatus budstate;
+  char strfilter[imstatus_size+1];
+  char *psfilter;
+
+  if (filter && *filter) {
+    int show_all = (*filter == '*');
+    status = 0;
+    for (budstate = 0; budstate < imstatus_size-1; budstate++)
+      if (strchr(filter, imstatus2char[budstate]) || show_all)
+        status |= 1<<budstate;
+    buddylist_set_filter(status);
+    buddylist_build();
+    update_roster = TRUE;
+    return;
+  }
+
+  // Display current filter
+  psfilter = strfilter;
+  status = buddylist_get_filter();
+  for (budstate = 0; budstate < imstatus_size-1; budstate++)
+    if (status & 1<<budstate)
+      *psfilter++ = imstatus2char[budstate];
+  *psfilter = '\0';
+  scr_LogPrint(LPRINT_NORMAL, "Roster status filter: %s", strfilter);
+}
+
+//  scr_BufferScrollUpDown()
+// Scroll up/down the current buddy window,
+// - half a screen if nblines is 0,
+// - up if updown == -1, down if updown == 1
+void scr_BufferScrollUpDown(int updown, unsigned int nblines)
+{
+  winbuf *win_entry;
+  int n, nbl;
+  GList *hbuf_top;
+  guint isspe;
+
+  // Get win_entry
+  if (!current_buddy) return;
+
+  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  win_entry  = scr_SearchWindow(CURRENT_JID, isspe);
+  if (!win_entry) return;
+
+  if (!nblines) {
+    // Scroll half a screen (or less)
+    nbl = CHAT_WIN_HEIGHT/2;
+  } else {
+    nbl = nblines;
+  }
+  hbuf_top = win_entry->bd->top;
+
+  if (updown == -1) {   // UP
+    if (!hbuf_top) {
+      hbuf_top = g_list_last(win_entry->bd->hbuf);
+      if (!win_entry->bd->cleared) {
+        if (!nblines) nbl = nbl*3 - 1;
+        else nbl += CHAT_WIN_HEIGHT - 1;
+      } else {
+        win_entry->bd->cleared = FALSE;
+      }
+    }
+    for (n=0 ; hbuf_top && n < nbl && g_list_previous(hbuf_top) ; n++)
+      hbuf_top = g_list_previous(hbuf_top);
+    win_entry->bd->top = hbuf_top;
+  } else {              // DOWN
+    for (n=0 ; hbuf_top && n < nbl ; n++)
+      hbuf_top = g_list_next(hbuf_top);
+    win_entry->bd->top = hbuf_top;
+    // Check if we are at the bottom
+    for (n=0 ; hbuf_top && n < CHAT_WIN_HEIGHT-1 ; n++)
+      hbuf_top = g_list_next(hbuf_top);
+    if (!hbuf_top)
+      win_entry->bd->top = NULL; // End reached
+  }
+
+  // Refresh the window
+  scr_UpdateWindow(win_entry);
+
+  // Finished :)
+  update_panels();
+}
+
+//  scr_BufferClear()
+// Clear the current buddy window (used for the /clear command)
+void scr_BufferClear(void)
+{
+  winbuf *win_entry;
+  guint isspe;
+
+  // Get win_entry
+  if (!current_buddy) return;
+  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
+  if (!win_entry) return;
+
+  win_entry->bd->cleared = TRUE;
+  win_entry->bd->top = NULL;
+
+  // Refresh the window
+  scr_UpdateWindow(win_entry);
+
+  // Finished :)
+  update_panels();
+}
+
+//  buffer_purge()
+// key: winId/jid
+// value: winbuf structure
+// data: int, set to 1 if the buffer should be closed.
+// NOTE: does not work for special buffers.
+static void buffer_purge(gpointer key, gpointer value, gpointer data)
+{
+  int *p_closebuf = data;
+  winbuf *win_entry = value;
+
+  // Delete the current hbuf
+  hbuf_free(&win_entry->bd->hbuf);
+
+  if (*p_closebuf) {
+    g_hash_table_remove(winbufhash, key);
+  } else {
+    win_entry->bd->cleared = FALSE;
+    win_entry->bd->top = NULL;
+  }
+}
+
+//  scr_BufferPurge(closebuf, jid)
+// Purge/Drop the current buddy buffer or jid's buffer if jid != NULL.
+// If closebuf is 1, close the buffer.
+void scr_BufferPurge(int closebuf, const char *jid)
+{
+  winbuf *win_entry;
+  guint isspe;
+  guint *p_closebuf;
+  const char *cjid;
+  guint hold_chatmode = FALSE;
+
+  if (jid) {
+    cjid = jid;
+    isspe = FALSE;
+    // If closebuf is TRUE, it's probably better not to leave chat mode
+    // if the change isn't related to the current buffer.
+    if (closebuf && current_buddy) {
+      if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL ||
+          strcasecmp(jid, CURRENT_JID))
+        hold_chatmode = TRUE;
+    }
+  } else {
+    // Get win_entry
+    if (!current_buddy) return;
+    cjid = CURRENT_JID;
+    isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  }
+  win_entry = scr_SearchWindow(cjid, isspe);
+  if (!win_entry) return;
+
+  if (!isspe) {
+    p_closebuf = g_new(guint, 1);
+    *p_closebuf = closebuf;
+    buffer_purge((gpointer)cjid, win_entry, p_closebuf);
+    g_free(p_closebuf);
+    if (closebuf && !hold_chatmode) {
+      scr_set_chatmode(FALSE);
+      currentWindow = NULL;
+    }
+  } else {
+    // (Special buffer)
+    // Reset the current hbuf
+    hbuf_free(&win_entry->bd->hbuf);
+    // Currently it can only be the status buffer
+    statushbuf = NULL;
+
+    win_entry->bd->cleared = FALSE;
+    win_entry->bd->top = NULL;
+  }
+
+  // Refresh the window
+  scr_UpdateBuddyWindow();
+
+  // Finished :)
+  update_panels();
+}
+
+void scr_BufferPurgeAll(int closebuf)
+{
+  guint *p_closebuf;
+  p_closebuf = g_new(guint, 1);
+
+  *p_closebuf = closebuf;
+  g_hash_table_foreach(winbufhash, buffer_purge, p_closebuf);
+  g_free(p_closebuf);
+
+  if (closebuf) {
+    scr_set_chatmode(FALSE);
+    currentWindow = NULL;
+  }
+
+  // Refresh the window
+  scr_UpdateBuddyWindow();
+
+  // Finished :)
+  update_panels();
+}
+
+//  scr_BufferScrollLock(lock)
+// Lock/unlock the current buddy buffer
+// lock = 1 : lock
+// lock = 0 : unlock
+// lock = -1: toggle lock status
+void scr_BufferScrollLock(int lock)
+{
+  winbuf *win_entry;
+  guint isspe;
+
+  // Get win_entry
+  if (!current_buddy) return;
+  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
+  if (!win_entry) return;
+
+  if (lock == -1)
+    lock = !win_entry->bd->lock;
+
+  if (lock) {
+    win_entry->bd->lock = TRUE;
+  } else {
+    win_entry->bd->lock = FALSE;
+    //win_entry->bd->cleared = FALSE;
+    if (isspe || (buddy_getflags(BUDDATA(current_buddy)) & ROSTER_FLAG_MSG))
+      win_entry->bd->top = NULL;
+  }
+
+  // If chatmode is disabled and we're at the bottom of the buffer,
+  // we need to set the "top" line, so we need to call scr_ShowBuddyWindow()
+  // at least once.  (Maybe it will cause a double refresh...)
+  if (!chatmode && !win_entry->bd->top) {
+    chatmode = TRUE;
+    scr_ShowBuddyWindow();
+    chatmode = FALSE;
+  }
+
+  // Refresh the window
+  scr_UpdateBuddyWindow();
+
+  // Finished :)
+  update_panels();
+}
+
+//  scr_BufferTopBottom()
+// Jump to the head/tail of the current buddy window
+// (top if topbottom == -1, bottom topbottom == 1)
+void scr_BufferTopBottom(int topbottom)
+{
+  winbuf *win_entry;
+  guint isspe;
+
+  // Get win_entry
+  if (!current_buddy) return;
+  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
+  if (!win_entry) return;
+
+  win_entry->bd->cleared = FALSE;
+  if (topbottom == 1)
+    win_entry->bd->top = NULL;
+  else
+    win_entry->bd->top = g_list_first(win_entry->bd->hbuf);
+
+  // Refresh the window
+  scr_UpdateWindow(win_entry);
+
+  // Finished :)
+  update_panels();
+}
+
+//  scr_BufferSearch(direction, text)
+// Jump to the next line containing text
+// (backward search if direction == -1, forward if topbottom == 1)
+void scr_BufferSearch(int direction, const char *text)
+{
+  winbuf *win_entry;
+  GList *current_line, *search_res;
+  guint isspe;
+
+  // Get win_entry
+  if (!current_buddy) return;
+  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
+  if (!win_entry) return;
+
+  if (win_entry->bd->top)
+    current_line = win_entry->bd->top;
+  else
+    current_line = g_list_last(win_entry->bd->hbuf);
+
+  search_res = hbuf_search(current_line, direction, text);
+
+  if (search_res) {
+    win_entry->bd->cleared = FALSE;
+    win_entry->bd->top = search_res;
+
+    // Refresh the window
+    scr_UpdateWindow(win_entry);
+
+    // Finished :)
+    update_panels();
+  } else
+    scr_LogPrint(LPRINT_NORMAL, "Search string not found");
+}
+
+//  scr_BufferPercent(n)
+// Jump to the specified position in the buffer, in %
+void scr_BufferPercent(int pc)
+{
+  winbuf *win_entry;
+  GList *search_res;
+  guint isspe;
+
+  // Get win_entry
+  if (!current_buddy) return;
+  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
+  if (!win_entry) return;
+
+  if (pc < 0 || pc > 100) {
+    scr_LogPrint(LPRINT_NORMAL, "Bad % value");
+    return;
+  }
+
+  search_res = hbuf_jump_percent(win_entry->bd->hbuf, pc);
+
+  win_entry->bd->cleared = FALSE;
+  win_entry->bd->top = search_res;
+
+  // Refresh the window
+  scr_UpdateWindow(win_entry);
+
+  // Finished :)
+  update_panels();
+}
+
+//  scr_BufferDate(t)
+// Jump to the first line after date t in the buffer
+// t is a date in seconds since `00:00:00 1970-01-01 UTC'
+void scr_BufferDate(time_t t)
+{
+  winbuf *win_entry;
+  GList *search_res;
+  guint isspe;
+
+  // Get win_entry
+  if (!current_buddy) return;
+  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
+  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
+  if (!win_entry) return;
+
+  search_res = hbuf_jump_date(win_entry->bd->hbuf, t);
+
+  win_entry->bd->cleared = FALSE;
+  win_entry->bd->top = search_res;
+
+  // Refresh the window
+  scr_UpdateWindow(win_entry);
+
+  // Finished :)
+  update_panels();
+}
+
+void scr_BufferDump(const char *file)
+{
+  char *extfname;
+
+  if (!currentWindow) {
+    scr_LogPrint(LPRINT_NORMAL, "No current buffer!");
+    return;
+  }
+
+  if (!file || !*file) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter (file name)!");
+    return;
+  }
+
+  extfname = expand_filename(file);
+  hbuf_dump_to_file(currentWindow->bd->hbuf, extfname);
+  g_free(extfname);
+}
+
+//  buffer_list()
+// key: winId/jid
+// value: winbuf structure
+// data: none.
+static void buffer_list(gpointer key, gpointer value, gpointer data)
+{
+  GList *head;
+  winbuf *win_entry = value;
+
+  head = g_list_first(win_entry->bd->hbuf);
+
+  scr_LogPrint(LPRINT_NORMAL, " %s  (%u/%u)", key,
+               g_list_length(head), hbuf_get_blocks_number(head));
+}
+
+void scr_BufferList(void)
+{
+  scr_LogPrint(LPRINT_NORMAL, "Buffer list:");
+  buffer_list("[status]", statusWindow, NULL);
+  g_hash_table_foreach(winbufhash, buffer_list, NULL);
+  scr_LogPrint(LPRINT_NORMAL, "End of buffer list.");
+  scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
+  update_roster = TRUE;
+}
+
+//  scr_set_chatmode()
+// Public function to (un)set chatmode...
+inline void scr_set_chatmode(int enable)
+{
+  chatmode = enable;
+  scr_UpdateChatStatus(TRUE);
+}
+
+//  scr_get_chatmode()
+// Public function to get chatmode state.
+inline int scr_get_chatmode(void)
+{
+  return chatmode;
+}
+
+//  scr_get_multimode()
+// Public function to get multimode status...
+inline int scr_get_multimode(void)
+{
+  return multimode;
+}
+
+//  scr_setmsgflag_if_needed(jid)
+// Set the message flag unless we're already in the jid buffer window
+void scr_setmsgflag_if_needed(const char *bjid, int special)
+{
+  const char *current_id;
+  bool iscurrentlocked = FALSE;
+
+  if (!bjid)
+    return;
+
+  if (current_buddy) {
+    if (special)
+      current_id = buddy_getname(BUDDATA(current_buddy));
+    else
+      current_id = buddy_getjid(BUDDATA(current_buddy));
+    if (current_id) {
+      winbuf *win_entry = scr_SearchWindow(current_id, special);
+      if (!win_entry) return;
+      iscurrentlocked = win_entry->bd->lock;
+    }
+  } else {
+    current_id = NULL;
+  }
+  if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked)
+    roster_msg_setflag(bjid, special, TRUE);
+}
+
+//  scr_set_multimode()
+// Public function to (un)set multimode...
+// Convention:
+//  0 = disabled / 1 = multimode / 2 = multimode verbatim (commands disabled)
+void scr_set_multimode(int enable, char *subject)
+{
+  g_free(multiline);
+  multiline = NULL;
+
+  g_free(multimode_subj);
+  if (enable && subject)
+    multimode_subj = g_strdup(subject);
+  else
+    multimode_subj = NULL;
+
+  multimode = enable;
+}
+
+//  scr_get_multiline()
+// Public function to get the current multi-line.
+const char *scr_get_multiline(void)
+{
+  if (multimode && multiline)
+    return multiline;
+  return NULL;
+}
+
+//  scr_get_multimode_subj()
+// Public function to get the multi-line subject, if any.
+const char *scr_get_multimode_subj(void)
+{
+  if (multimode)
+    return multimode_subj;
+  return NULL;
+}
+
+//  scr_append_multiline(line)
+// Public function to append a line to the current multi-line message.
+// Skip empty leading lines.
+void scr_append_multiline(const char *line)
+{
+  static int num;
+
+  if (!multimode) {
+    scr_LogPrint(LPRINT_NORMAL, "Error: Not in multi-line message mode!");
+    return;
+  }
+  if (multiline) {
+    int len = strlen(multiline)+strlen(line)+2;
+    if (len >= HBB_BLOCKSIZE - 1) {
+      // We don't handle single messages with size > HBB_BLOCKSIZE
+      // (see hbuf)
+      scr_LogPrint(LPRINT_NORMAL, "Your multi-line message is too big, "
+                   "this line has not been added.");
+      scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
+      return;
+    }
+    if (num >= MULTILINE_MAX_LINE_NUMBER) {
+      // We don't allow too many lines; however the maximum is arbitrary
+      // (It should be < 1000 yet)
+      scr_LogPrint(LPRINT_NORMAL, "Your message has too many lines, "
+                   "this one has not been added.");
+      scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
+      return;
+    }
+    multiline = g_renew(char, multiline, len);
+    strcat(multiline, "\n");
+    strcat(multiline, line);
+    num++;
+  } else {
+    // First message line (we skip leading empty lines)
+    num = 0;
+    if (line[0]) {
+      multiline = g_strdup(line);
+      num++;
+    } else
+      return;
+  }
+  scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
+               "Multi-line mode: line #%d added  [%.25s...", num, line);
+}
+
+//  scr_cmdhisto_addline()
+// Add a line to the inputLine history
+static inline void scr_cmdhisto_addline(char *line)
+{
+  int max_histo_lines;
+
+  if (!line || !*line)
+    return;
+
+  max_histo_lines = settings_opt_get_int("cmdhistory_lines");
+
+  if (max_histo_lines < 0)
+    max_histo_lines = 1;
+
+  if (max_histo_lines)
+    while (cmdhisto_nblines >= (guint)max_histo_lines) {
+      if (cmdhisto_cur && cmdhisto_cur == cmdhisto)
+        break;
+      g_free(cmdhisto->data);
+      cmdhisto = g_list_delete_link(cmdhisto, cmdhisto);
+      cmdhisto_nblines--;
+    }
+
+  cmdhisto = g_list_append(cmdhisto, g_strdup(line));
+  cmdhisto_nblines++;
+}
+
+//  scr_cmdhisto_prev()
+// Look for previous line beginning w/ the given mask in the inputLine history
+// Returns NULL if none found
+static const char *scr_cmdhisto_prev(char *mask, guint len)
+{
+  GList *hl;
+  if (!cmdhisto_cur) {
+    hl = g_list_last(cmdhisto);
+    if (hl) { // backup current line
+      strncpy(cmdhisto_backup, mask, INPUTLINE_LENGTH);
+    }
+  } else {
+    hl = g_list_previous(cmdhisto_cur);
+  }
+  while (hl) {
+    if (!strncmp((char*)hl->data, mask, len)) {
+      // Found a match
+      cmdhisto_cur = hl;
+      return (const char*)hl->data;
+    }
+    hl = g_list_previous(hl);
+  }
+  return NULL;
+}
+
+//  scr_cmdhisto_next()
+// Look for next line beginning w/ the given mask in the inputLine history
+// Returns NULL if none found
+static const char *scr_cmdhisto_next(char *mask, guint len)
+{
+  GList *hl;
+  if (!cmdhisto_cur) return NULL;
+  hl = cmdhisto_cur;
+  while ((hl = g_list_next(hl)) != NULL)
+    if (!strncmp((char*)hl->data, mask, len)) {
+      // Found a match
+      cmdhisto_cur = hl;
+      return (const char*)hl->data;
+    }
+  // If the "backuped" line matches, we'll use it
+  if (strncmp(cmdhisto_backup, mask, len)) return NULL; // No match
+  cmdhisto_cur = NULL;
+  return cmdhisto_backup;
+}
+
+//  readline_transpose_chars()
+// Drag  the  character  before point forward over the character at
+// point, moving point forward as well.  If point is at the end  of
+// the  line, then this transposes the two characters before point.
+void readline_transpose_chars(void)
+{
+  char *c1, *c2;
+  unsigned a, b;
+
+  if (ptr_inputline == inputLine) return;
+
+  if (!*ptr_inputline) { // We're at EOL
+    // If line is only 1 char long, nothing to do...
+    if (ptr_inputline == prev_char(ptr_inputline, inputLine)) return;
+    // Transpose the two previous characters
+    c2 = prev_char(ptr_inputline, inputLine);
+    c1 = prev_char(c2, inputLine);
+    a = get_char(c1);
+    b = get_char(c2);
+    put_char(put_char(c1, b), a);
+  } else {
+    // Swap the two characters before the cursor and move right.
+    c2 = ptr_inputline;
+    c1 = prev_char(c2, inputLine);
+    a = get_char(c1);
+    b = get_char(c2);
+    put_char(put_char(c1, b), a);
+    check_offset(1);
+  }
+}
+
+void readline_forward_kill_word(void)
+{
+  char *c, *old = ptr_inputline;
+  int spaceallowed = 1;
+
+  if (! *ptr_inputline) return;
+
+  for (c = ptr_inputline ; *c ; c = next_char(c)) {
+    if (!iswalnum(get_char(c))) {
+      if (iswblank(get_char(c))) {
+        if (!spaceallowed) break;
+      } else spaceallowed = 0;
+    } else spaceallowed = 0;
+  }
+
+  // Modify the line
+  for (;;) {
+    *old = *c++;
+    if (!*old++) break;
+  }
+}
+
+//  readline_backward_kill_word()
+// Kill the word before the cursor, in input line
+void readline_backward_kill_word(void)
+{
+  char *c, *old = ptr_inputline;
+  int spaceallowed = 1;
+
+  if (ptr_inputline == inputLine) return;
+
+  c = prev_char(ptr_inputline, inputLine);
+  for ( ; c > inputLine ; c = prev_char(c, inputLine)) {
+    if (!iswalnum(get_char(c))) {
+      if (iswblank(get_char(c))) {
+        if (!spaceallowed) break;
+      } else spaceallowed = 0;
+    } else spaceallowed = 0;
+  }
+
+  if (c == inputLine && *c == COMMAND_CHAR && old != c+1) {
+      c = next_char(c);
+  } else if (c != inputLine || iswblank(get_char(c))) {
+    if ((c < prev_char(ptr_inputline, inputLine)) && (!iswalnum(get_char(c))))
+      c = next_char(c);
+  }
+
+  // Modify the line
+  ptr_inputline = c;
+  for (;;) {
+    *c = *old++;
+    if (!*c++) break;
+  }
+  check_offset(-1);
+}
+
+//  readline_backward_word()
+// Move  back  to the start of the current or previous word
+void readline_backward_word(void)
+{
+  int i = 0;
+
+  if (ptr_inputline == inputLine) return;
+
+  if (iswalnum(get_char(ptr_inputline)) &&
+      !iswalnum(get_char(prev_char(ptr_inputline, inputLine))))
+    i--;
+
+  for ( ;
+       ptr_inputline > inputLine;
+       ptr_inputline = prev_char(ptr_inputline, inputLine)) {
+    if (!iswalnum(get_char(ptr_inputline))) {
+      if (i) {
+        ptr_inputline = next_char(ptr_inputline);
+        break;
+      }
+    } else i++;
+  }
+
+  check_offset(-1);
+}
+
+//  readline_forward_word()
+// Move forward to the end of the next word
+void readline_forward_word(void)
+{
+  int stopsymbol_allowed = 1;
+
+  while (*ptr_inputline) {
+    if (!iswalnum(get_char(ptr_inputline))) {
+      if (!stopsymbol_allowed) break;
+    } else stopsymbol_allowed = 0;
+    ptr_inputline = next_char(ptr_inputline);
+  }
+
+  check_offset(1);
+}
+
+void readline_updowncase_word(int upcase)
+{
+  int stopsymbol_allowed = 1;
+
+  while (*ptr_inputline) {
+    if (!iswalnum(get_char(ptr_inputline))) {
+      if (!stopsymbol_allowed) break;
+    } else {
+      stopsymbol_allowed = 0;
+      if (upcase)
+        *ptr_inputline = towupper(get_char(ptr_inputline));
+      else
+        *ptr_inputline = towlower(get_char(ptr_inputline));
+    }
+    ptr_inputline = next_char(ptr_inputline);
+  }
+
+  check_offset(1);
+}
+
+void readline_capitalize_word(void)
+{
+  int stopsymbol_allowed = 1;
+  int upcased = 0;
+
+  while (*ptr_inputline) {
+    if (!iswalnum(get_char(ptr_inputline))) {
+      if (!stopsymbol_allowed) break;
+    } else {
+      stopsymbol_allowed = 0;
+      if (!upcased) {
+        *ptr_inputline = towupper(get_char(ptr_inputline));
+        upcased = 1;
+      } else *ptr_inputline = towlower(get_char(ptr_inputline));
+    }
+    ptr_inputline = next_char(ptr_inputline);
+  }
+
+  check_offset(1);
+}
+
+void readline_backward_char(void)
+{
+  if (ptr_inputline == (char*)&inputLine) return;
+
+  ptr_inputline = prev_char(ptr_inputline, inputLine);
+  check_offset(-1);
+}
+
+void readline_forward_char(void)
+{
+  if (!*ptr_inputline) return;
+
+  ptr_inputline = next_char(ptr_inputline);
+  check_offset(1);
+}
+
+//  readline_accept_line(down_history)
+// Validate current command line.
+// If down_history is true, load the next history line.
+int readline_accept_line(int down_history)
+{
+  scr_CheckAutoAway(TRUE);
+  if (process_line(inputLine))
+    return 255;
+  // Add line to history
+  scr_cmdhisto_addline(inputLine);
+  // Reset the line
+  ptr_inputline = inputLine;
+  *ptr_inputline = 0;
+  inputline_offset = 0;
+
+  if (down_history) {
+    // Use next history line instead of a blank line
+    const char *l = scr_cmdhisto_next("", 0);
+    if (l) strcpy(inputLine, l);
+    // Reset backup history line
+    cmdhisto_backup[0] = 0;
+  } else {
+    // Reset history line pointer
+    cmdhisto_cur = NULL;
+  }
+  return 0;
+}
+
+void readline_cancel_completion(void)
+{
+  scr_cancel_current_completion();
+  scr_end_current_completion();
+  check_offset(-1);
+}
+
+void readline_do_completion(void)
+{
+  int i, n;
+
+  if (multimode != 2) {
+    // Not in verbatim multi-line mode
+    scr_handle_tab();
+  } else {
+    // Verbatim multi-line mode: expand tab
+    char tabstr[9];
+    n = 8 - (ptr_inputline - inputLine) % 8;
+    for (i = 0; i < n; i++)
+      tabstr[i] = ' ';
+    tabstr[i] = '\0';
+    scr_insert_text(tabstr);
+  }
+  check_offset(0);
+}
+
+void readline_refresh_screen(void)
+{
+  scr_CheckAutoAway(TRUE);
+  ParseColors();
+  scr_Resize();
+  redrawwin(stdscr);
+}
+
+void readline_disable_chat_mode(guint show_roster)
+{
+  scr_CheckAutoAway(TRUE);
+  currentWindow = NULL;
+  chatmode = FALSE;
+  if (current_buddy)
+    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
+  if (show_roster)
+    scr_RosterVisibility(1);
+  scr_UpdateChatStatus(FALSE);
+  top_panel(chatPanel);
+  top_panel(inputPanel);
+  update_panels();
+}
+
+void readline_hist_beginning_search_bwd(void)
+{
+  const char *l = scr_cmdhisto_prev(inputLine, ptr_inputline-inputLine);
+  if (l) strcpy(inputLine, l);
+}
+
+void readline_hist_beginning_search_fwd(void)
+{
+  const char *l = scr_cmdhisto_next(inputLine, ptr_inputline-inputLine);
+  if (l) strcpy(inputLine, l);
+}
+
+void readline_hist_prev(void)
+{
+  const char *l = scr_cmdhisto_prev(inputLine, 0);
+  if (l) {
+    strcpy(inputLine, l);
+    // Set the pointer at the EOL.
+    // We have to move it to BOL first, because we could be too far already.
+    readline_iline_start();
+    readline_iline_end();
+  }
+}
+
+void readline_hist_next(void)
+{
+  const char *l = scr_cmdhisto_next(inputLine, 0);
+  if (l) {
+    strcpy(inputLine, l);
+    // Set the pointer at the EOL.
+    // We have to move it to BOL first, because we could be too far already.
+    readline_iline_start();
+    readline_iline_end();
+  }
+}
+
+void readline_backward_kill_char(void)
+{
+  char *src, *c;
+
+  if (ptr_inputline == (char*)&inputLine)
+    return;
+
+  src = ptr_inputline;
+  c = prev_char(ptr_inputline, inputLine);
+  ptr_inputline = c;
+  for ( ; *src ; )
+    *c++ = *src++;
+  *c = 0;
+  check_offset(-1);
+}
+
+void readline_forward_kill_char(void)
+{
+  if (!*ptr_inputline)
+    return;
+
+  strcpy(ptr_inputline, next_char(ptr_inputline));
+}
+
+void readline_iline_start(void)
+{
+  ptr_inputline = inputLine;
+  inputline_offset = 0;
+}
+
+void readline_iline_end(void)
+{
+  for (; *ptr_inputline; ptr_inputline++) ;
+  check_offset(1);
+}
+
+void readline_backward_kill_iline(void)
+{
+  strcpy(inputLine, ptr_inputline);
+  ptr_inputline = inputLine;
+  inputline_offset = 0;
+}
+
+void readline_forward_kill_iline(void)
+{
+  *ptr_inputline = 0;
+}
+
+void readline_send_multiline(void)
+{
+  // Validate current multi-line
+  if (multimode)
+    process_command(mkcmdstr("msay send"), TRUE);
+}
+
+//  which_row()
+// Tells which row our cursor is in, in the command line.
+// -2 -> normal text
+// -1 -> room: nickname completion
+//  0 -> command
+//  1 -> parameter 1 (etc.)
+//  If > 0, then *p_row is set to the beginning of the row
+static int which_row(const char **p_row)
+{
+  int row = -1;
+  char *p;
+  int quote = FALSE;
+
+  // Not a command?
+  if ((ptr_inputline == inputLine) || (inputLine[0] != COMMAND_CHAR)) {
+    if (!current_buddy) return -2;
+    if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_ROOM) {
+      *p_row = inputLine;
+      return -1;
+    }
+    return -2;
+  }
+
+  // This is a command
+  row = 0;
+  for (p = inputLine ; p < ptr_inputline ; p = next_char(p)) {
+    if (quote) {
+      if (*p == '"' && *(p-1) != '\\')
+        quote = FALSE;
+      continue;
+    }
+    if (*p == '"' && *(p-1) != '\\') {
+      quote = TRUE;
+    } else if (*p == ' ') {
+      if (*(p-1) != ' ')
+        row++;
+      *p_row = p+1;
+    }
+  }
+  return row;
+}
+
+//  scr_insert_text()
+// Insert the given text at the current cursor position.
+// The cursor is moved.  We don't check if the cursor still is in the screen
+// after, the caller should do that.
+static void scr_insert_text(const char *text)
+{
+  char tmpLine[INPUTLINE_LENGTH+1];
+  int len = strlen(text);
+  // Check the line isn't too long
+  if (strlen(inputLine) + len >= INPUTLINE_LENGTH) {
+    scr_LogPrint(LPRINT_LOGNORM, "Cannot insert text, line too long.");
+    return;
+  }
+
+  strcpy(tmpLine, ptr_inputline);
+  strcpy(ptr_inputline, text);
+  ptr_inputline += len;
+  strcpy(ptr_inputline, tmpLine);
+}
+
+static void scr_cancel_current_completion(void);
+
+//  scr_handle_tab()
+// Function called when tab is pressed.
+// Initiate or continue a completion...
+static void scr_handle_tab(void)
+{
+  int nrow;
+  const char *row;
+  const char *cchar;
+  guint compl_categ;
+
+  row = inputLine; // (Kills a GCC warning)
+  nrow = which_row(&row);
+
+  // a) No completion if no leading slash ('cause not a command),
+  //    unless this is a room (then, it is a nickname completion)
+  // b) We can't have more than 2 parameters (we use 2 flags)
+  if ((nrow == -2) || (nrow == 3 && !completion_started) || nrow > 3)
+    return;
+
+  if (nrow == 0) {          // Command completion
+    row = next_char(inputLine);
+    compl_categ = COMPL_CMD;
+  } else if (nrow == -1) {  // Nickname completion
+    compl_categ = COMPL_RESOURCE;
+  } else {                  // Other completion, depending on the command
+    int alias = FALSE;
+    cmd *com;
+    char *xpline = expandalias(inputLine);
+    com = cmd_get(xpline);
+    if (xpline != inputLine) {
+      // This is an alias, so we can't complete rows > 0
+      alias = TRUE;
+      g_free(xpline);
+    }
+    if ((!com && (!alias || !completion_started)) || !row) {
+      scr_LogPrint(LPRINT_NORMAL, "I cannot complete that...");
+      return;
+    }
+    if (!alias)
+      compl_categ = com->completion_flags[nrow-1];
+    else
+      compl_categ = 0;
+  }
+
+  if (!completion_started) {
+    guint dynlist;
+    GSList *list = compl_get_category_list(compl_categ, &dynlist);
+    if (list) {
+      guint n;
+      char *prefix = g_strndup(row, ptr_inputline-row);
+      // Init completion
+      n = new_completion(prefix, list);
+      g_free(prefix);
+      if (n == 0 && nrow == -1) {
+        // This is a MUC room and we can't complete from the beginning of the
+        // line.  Let's try a bit harder and complete the current word.
+        row = prev_char(ptr_inputline, inputLine);
+        while (row >= inputLine) {
+          if (iswspace(get_char(row)) || get_char(row) == '(') {
+              row = next_char((char*)row);
+              break;
+          }
+          if (row == inputLine)
+            break;
+          row = prev_char((char*)row, inputLine);
+        }
+        // There's no need to try again if row == inputLine
+        if (row > inputLine) {
+          prefix = g_strndup(row, ptr_inputline-row);
+          new_completion(prefix, list);
+          g_free(prefix);
+        }
+      }
+      // Free the list if it's a dynamic one
+      if (dynlist) {
+        GSList *slp;
+        for (slp = list; slp; slp = g_slist_next(slp))
+          g_free(slp->data);
+        g_slist_free(list);
+      }
+      // Now complete
+      cchar = complete();
+      if (cchar)
+        scr_insert_text(cchar);
+      completion_started = TRUE;
+    }
+  } else {      // Completion already initialized
+    scr_cancel_current_completion();
+    // Now complete again
+    cchar = complete();
+    if (cchar)
+      scr_insert_text(cchar);
+  }
+}
+
+static void scr_cancel_current_completion(void)
+{
+  char *c;
+  char *src = ptr_inputline;
+  guint back = cancel_completion();
+  guint i;
+  // Remove $back chars
+  for (i = 0; i < back; i++)
+    ptr_inputline = prev_char(ptr_inputline, inputLine);
+  c = ptr_inputline;
+  for ( ; *src ; )
+    *c++ = *src++;
+  *c = 0;
+}
+
+static void scr_end_current_completion(void)
+{
+  done_completion();
+  completion_started = FALSE;
+}
+
+//  check_offset(int direction)
+// Check inputline_offset value, and make sure the cursor is inside the
+// screen.
+static inline void check_offset(int direction)
+{
+  int i;
+  char *c = &inputLine[inputline_offset];
+  // Left side
+  if (inputline_offset && direction <= 0) {
+    while (ptr_inputline <= c) {
+      for (i = 0; i < 5; i++)
+        c = prev_char(c, inputLine);
+      if (c == inputLine)
+        break;
+    }
+  }
+  // Right side
+  if (direction >= 0) {
+    int delta = get_char_width(c);
+    while (ptr_inputline > c) {
+      c = next_char(c);
+      delta += get_char_width(c);
+    }
+    c = &inputLine[inputline_offset];
+    while (delta >= maxX) {
+      for (i = 0; i < 5; i++) {
+        delta -= get_char_width(c);
+        c = next_char(c);
+      }
+    }
+  }
+  inputline_offset = c - inputLine;
+}
+
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+// prints inputLine with underlined words when misspelled
+static inline void print_checked_line(void)
+{
+  char *wprint_char_fmt = "%c";
+  int point;
+  int nrchar = maxX;
+  char *ptrCur = inputLine + inputline_offset;
+
+#ifdef UNICODE
+  // We need this to display a single UTF-8 char... Any better solution?
+  if (utf8_mode)
+    wprint_char_fmt = "%lc";
+#endif
+
+  wmove(inputWnd, 0, 0); // problem with backspace
+
+  while (*ptrCur && nrchar-- > 0) {
+    point = ptrCur - inputLine;
+    if (maskLine[point])
+      wattrset(inputWnd, A_UNDERLINE);
+    wprintw(inputWnd, wprint_char_fmt, get_char(ptrCur));
+    wattrset(inputWnd, A_NORMAL);
+    ptrCur = next_char(ptrCur);
+  }
+}
+#endif
+
+static inline void refresh_inputline(void)
+{
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+  if (settings_opt_get_int("spell_enable")) {
+    memset(maskLine, 0, INPUTLINE_LENGTH+1);
+    spellcheck(inputLine, maskLine);
+  }
+  print_checked_line();
+  wclrtoeol(inputWnd);
+  if (*ptr_inputline) {
+    // hack to set cursor pos. Characters can have different width,
+    // so I know of no better way.
+    char c = *ptr_inputline;
+    *ptr_inputline = 0;
+    print_checked_line();
+    *ptr_inputline = c;
+  }
+#else
+  mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
+  wclrtoeol(inputWnd);
+  if (*ptr_inputline) {
+    // hack to set cursor pos. Characters can have different width,
+    // so I know of no better way.
+    char c = *ptr_inputline;
+    *ptr_inputline = 0;
+    mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
+    *ptr_inputline = c;
+  }
+#endif
+}
+
+void scr_handle_CtrlC(void)
+{
+  if (!Curses) return;
+  // Leave multi-line mode
+  process_command(mkcmdstr("msay abort"), TRUE);
+  // Same as Ctrl-g, now
+  scr_cancel_current_completion();
+  scr_end_current_completion();
+  check_offset(-1);
+  refresh_inputline();
+}
+
+static void add_keyseq(char *seqstr, guint mkeycode, gint value)
+{
+  keyseq *ks;
+
+  // Let's make sure the length is correct
+  if (strlen(seqstr) > MAX_KEYSEQ_LENGTH) {
+    scr_LogPrint(LPRINT_LOGNORM, "add_keyseq(): key sequence is too long!");
+    return;
+  }
+
+  ks = g_new0(keyseq, 1);
+  ks->seqstr = g_strdup(seqstr);
+  ks->mkeycode = mkeycode;
+  ks->value = value;
+  keyseqlist = g_slist_append(keyseqlist, ks);
+}
+
+//  match_keyseq(iseq, &ret)
+// Check if "iseq" is a known key escape sequence.
+// Return value:
+// -1  if "seq" matches no known sequence
+//  0  if "seq" could match 1 or more known sequences
+// >0  if "seq" matches a key sequence; the mkey code is returned
+//     and *ret is set to the matching keyseq structure.
+static inline gint match_keyseq(int *iseq, keyseq **ret)
+{
+  GSList *ksl;
+  keyseq *ksp;
+  char *p, c;
+  int *i;
+  int needmore = FALSE;
+
+  for (ksl = keyseqlist; ksl; ksl = g_slist_next(ksl)) {
+    ksp = ksl->data;
+    p = ksp->seqstr;
+    i = iseq;
+    while (1) {
+      c = (unsigned char)*i;
+      if (!*p && !c) { // Match
+        (*ret) = ksp;
+        return ksp->mkeycode;
+      }
+      if (!c) {
+        // iseq is too short
+        needmore = TRUE;
+        break;
+      } else if (!*p || c != *p) {
+        // This isn't a match
+        break;
+      }
+      p++; i++;
+    }
+  }
+
+  if (needmore)
+    return 0;
+  return -1;
+}
+
+static inline int match_utf8_keyseq(int *iseq)
+{
+  int *strp = iseq;
+  unsigned c = *strp++;
+  unsigned mask = 0x80;
+  int len = -1;
+  while (c & mask) {
+    mask >>= 1;
+    len++;
+  }
+  if (len <= 0 || len > 4)
+    return -1;
+  c &= mask - 1;
+  while ((*strp & 0xc0) == 0x80) {
+    if (len-- <= 0) // can't happen
+      return -1;
+    c = (c << 6) | (*strp++ & 0x3f);
+  }
+  if (len)
+    return 0;
+  return c;
+}
+
+void scr_Getch(keycode *kcode)
+{
+  keyseq *mks = NULL;
+  int  ks[MAX_KEYSEQ_LENGTH+1];
+  int i;
+
+  memset(kcode, 0, sizeof(keycode));
+  memset(ks,  0, sizeof(ks));
+
+  kcode->value = wgetch(inputWnd);
+  if (utf8_mode) {
+    bool ismeta = (kcode->value == 27);
+#ifdef NCURSES_MOUSE_VERSION
+    bool ismouse = (kcode->value == KEY_MOUSE);
+
+    if (ismouse) {
+      MEVENT mouse;
+      getmouse(&mouse);
+      kcode->value = mouse.bstate;
+      kcode->mcode = MKEY_MOUSE;
+      return;
+    } else if (ismeta)
+#else
+    if (ismeta)
+#endif
+      ks[0] = wgetch(inputWnd);
+    else
+      ks[0] = kcode->value;
+
+    for (i = 0; i < MAX_KEYSEQ_LENGTH - 1; i++) {
+      int match = match_utf8_keyseq(ks);
+      if (match == -1)
+        break;
+      if (match > 0) {
+        kcode->value = match;
+        kcode->utf8 = 1;
+        if (ismeta)
+          kcode->mcode = MKEY_META;
+        return;
+      }
+      ks[i + 1] = wgetch(inputWnd);
+      if (ks[i + 1] == ERR)
+        break;
+    }
+    while (i > 0)
+      ungetch(ks[i--]);
+    if (ismeta)
+      ungetch(ks[0]);
+    memset(ks,  0, sizeof(ks));
+  }
+  if (kcode->value != 27)
+    return;
+
+  // Check for escape key sequence
+  for (i=0; i < MAX_KEYSEQ_LENGTH; i++) {
+    int match;
+    ks[i] = wgetch(inputWnd);
+    if (ks[i] == ERR) break;
+    match = match_keyseq(ks, &mks);
+    if (match == -1) {
+      // No such key sequence.  Let's increment i as it is a valid key.
+      i++;
+      break;
+    }
+    if (match > 0) {
+      // We have a matching sequence
+      kcode->mcode = mks->mkeycode;
+      kcode->value = mks->value;
+      return;
+    }
+  }
+
+  // No match.  Let's return a meta-key.
+  if (i > 0) {
+    kcode->mcode = MKEY_META;
+    kcode->value = ks[0];
+  }
+  if (i > 1) {
+    // We need to push some keys back to the keyboard buffer
+    while (i-- > 1)
+      ungetch(ks[i]);
+  }
+  return;
+}
+
+void scr_DoUpdate(void)
+{
+  doupdate();
+}
+
+static int bindcommand(keycode kcode)
+{
+  gchar asciikey[16], asciicode[16];
+  const gchar *boundcmd;
+
+  if (kcode.utf8)
+    g_snprintf(asciicode, 15, "U%d", kcode.value);
+  else
+    g_snprintf(asciicode, 15, "%d", kcode.value);
+
+  if (!kcode.mcode || kcode.mcode == MKEY_EQUIV)
+    g_snprintf(asciikey, 15, "%s", asciicode);
+  else if (kcode.mcode == MKEY_META)
+    g_snprintf(asciikey, 15, "M%s", asciicode);
+  else if (kcode.mcode == MKEY_MOUSE)
+    g_snprintf(asciikey, 15, "p%s", asciicode);
+  else
+    g_snprintf(asciikey, 15, "MK%d", kcode.mcode);
+
+  boundcmd = settings_get(SETTINGS_TYPE_BINDING, asciikey);
+
+  if (boundcmd) {
+    gchar *cmdline = from_utf8(boundcmd);
+    scr_CheckAutoAway(TRUE);
+    if (process_command(cmdline, TRUE))
+      return 255; // Quit
+    g_free(cmdline);
+    return 0;
+  }
+
+  scr_LogPrint(LPRINT_NORMAL, "Unknown key=%s", asciikey);
+#ifndef UNICODE
+  if (utf8_mode)
+    scr_LogPrint(LPRINT_NORMAL,
+                 "WARNING: Compiled without full UTF-8 support!");
+#endif
+  return -1;
+}
+
+//  process_key(key)
+// Handle the pressed key, in the command line (bottom).
+void process_key(keycode kcode)
+{
+  int key = kcode.value;
+  int display_char = FALSE;
+
+  lock_chatstate = FALSE;
+
+  switch (kcode.mcode) {
+    case 0:
+        break;
+    case MKEY_EQUIV:
+        key = kcode.value;
+        break;
+    case MKEY_META:
+    default:
+        if (bindcommand(kcode) == 255) {
+          mcabber_set_terminate_ui();
+          return;
+        }
+        key = ERR; // Do not process any further
+  }
+
+  if (kcode.utf8) {
+    if (key != ERR && !kcode.mcode)
+      display_char = TRUE;
+    goto display;
+  }
+
+  switch (key) {
+    case 0:
+    case ERR:
+        break;
+    case 9:     // Tab
+        readline_do_completion();
+        break;
+    case 13:    // Enter
+        if (readline_accept_line(FALSE) == 255) {
+          mcabber_set_terminate_ui();
+          return;
+        }
+        break;
+    case 3:     // Ctrl-C
+        scr_handle_CtrlC();
+        break;
+    case KEY_RESIZE:
+#ifdef USE_SIGWINCH
+        {
+          struct winsize size;
+          if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1)
+            resizeterm(size.ws_row, size.ws_col);
+        }
+#endif
+        scr_Resize();
+        break;
+    default:
+        display_char = TRUE;
+  } // switch
+
+display:
+  if (display_char) {
+    guint printable;
+
+    if (kcode.utf8) {
+      printable = iswprint(key);
+    } else {
+#ifdef __CYGWIN__
+      printable = (isprint(key) || (key >= 161 && key <= 255))
+                  && !is_speckey(key);
+#else
+      printable = isprint(key) && !is_speckey(key);
+#endif
+    }
+    if (printable) {
+      char tmpLine[INPUTLINE_LENGTH+1];
+
+      // Check the line isn't too long
+      if (strlen(inputLine) + 4 > INPUTLINE_LENGTH)
+        return;
+
+      // Insert char
+      strcpy(tmpLine, ptr_inputline);
+      ptr_inputline = put_char(ptr_inputline, key);
+      strcpy(ptr_inputline, tmpLine);
+      check_offset(1);
+    } else {
+      // Look for a key binding.
+      if (!kcode.utf8 && (bindcommand(kcode) == 255)) {
+          mcabber_set_terminate_ui();
+          return;
+        }
+    }
+  }
+
+  if (completion_started && key != 9 && key != KEY_RESIZE)
+    scr_end_current_completion();
+  refresh_inputline();
+
+  if (!lock_chatstate) {
+    // Set chat state to composing (1) if the user is currently composing,
+    // i.e. not an empty line and not a command line.
+    if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
+      set_chatstate(0);
+    else
+      set_chatstate(1);
+    if (chatstate)
+      time(&chatstate_timestamp);
+  }
+  return;
+}
+
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+// initialization
+void spellcheck_init(void)
+{
+  int spell_enable            = settings_opt_get_int("spell_enable");
+  const char *spell_lang     = settings_opt_get("spell_lang");
+#ifdef WITH_ASPELL
+  const char *spell_encoding = settings_opt_get("spell_encoding");
+  AspellCanHaveError *possible_err;
+#endif
+
+  if (!spell_enable)
+    return;
+
+#ifdef WITH_ENCHANT
+  if (spell_checker) {
+     enchant_broker_free_dict(spell_broker, spell_checker);
+     enchant_broker_free(spell_broker);
+     spell_checker = NULL;
+     spell_broker = NULL;
+  }
+
+  spell_broker = enchant_broker_init();
+  spell_checker = enchant_broker_request_dict(spell_broker, spell_lang);
+#endif
+#ifdef WITH_ASPELL
+  if (spell_checker) {
+    delete_aspell_speller(spell_checker);
+    delete_aspell_config(spell_config);
+    spell_checker = NULL;
+    spell_config = NULL;
+  }
+
+  spell_config = new_aspell_config();
+  aspell_config_replace(spell_config, "encoding", spell_encoding);
+  aspell_config_replace(spell_config, "lang", spell_lang);
+  possible_err = new_aspell_speller(spell_config);
+
+  if (aspell_error_number(possible_err) != 0) {
+    spell_checker = NULL;
+    delete_aspell_config(spell_config);
+    spell_config = NULL;
+  } else {
+    spell_checker = to_aspell_speller(possible_err);
+  }
+#endif
+}
+
+// Deinitialization of spellchecker
+void spellcheck_deinit(void)
+{
+  if (spell_checker) {
+#ifdef WITH_ENCHANT
+    enchant_broker_free_dict(spell_broker, spell_checker);
+#endif
+#ifdef WITH_ASPELL
+    delete_aspell_speller(spell_checker);
+#endif
+    spell_checker = NULL;
+  }
+
+#ifdef WITH_ENCHANT
+  if (spell_broker) {
+    enchant_broker_free(spell_broker);
+    spell_broker = NULL;
+  }
+#endif
+#ifdef WITH_ASPELL
+  if (spell_config) {
+    delete_aspell_config(spell_config);
+    spell_config = NULL;
+  }
+#endif
+}
+
+#define spell_isalpha(c) (utf8_mode ? iswalpha(get_char(c)) : isalpha(*c))
+
+// Spell checking function
+static void spellcheck(char *line, char *checked)
+{
+  const char *start, *line_start;
+
+  if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
+    return;
+
+  line_start = line;
+
+  while (*line) {
+
+    if (!spell_isalpha(line)) {
+      line = next_char(line);
+      continue;
+    }
+
+    if (!strncmp(line, "http://", 7)) {
+      line += 7; // : and / characters are 1 byte long in utf8, right?
+
+      while (!strchr(" \t\r\n", *line))
+        line = next_char(line); // i think line++ would be fine here?
+
+      continue;
+    }
+
+    if (!strncmp(line, "ftp://", 6)) {
+      line += 6;
+
+      while (!strchr(" \t\r\n", *line))
+        line = next_char(line);
+
+      continue;
+    }
+
+    start = line;
+
+    while (spell_isalpha(line))
+      line = next_char(line);
+
+    if (spell_checker &&
+#ifdef WITH_ENCHANT
+        enchant_dict_check(spell_checker, start, line - start) != 0
+#endif
+#ifdef WITH_ASPELL
+        aspell_speller_check(spell_checker, start, line - start) == 0
+#endif
+    )
+      memset(&checked[start - line_start], SPELLBADCHAR, line - start);
+  }
+}
+#endif
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/screen.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,189 @@
+#ifndef __MCABBER_SCREEN_H__
+#define __MCABBER_SCREEN_H__ 1
+
+#include <glib.h>
+
+#include <mcabber/config.h>
+
+#if HAVE_NCURSESW_NCURSES_H
+# include <ncursesw/ncurses.h>
+# include <ncursesw/panel.h>
+#elif HAVE_NCURSES_NCURSES_H
+# include <ncurses/ncurses.h>
+# include <ncurses/panel.h>
+#else
+# include <ncurses.h>
+# include <panel.h>
+#endif
+
+#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
+void spellcheck_init(void);
+void spellcheck_deinit(void);
+//static void spellcheck(char*, char*);
+#endif
+
+#include <mcabber/hbuf.h>
+#include <mcabber/logprint.h>
+#include <mcabber/roster.h>
+
+#define INPUTLINE_LENGTH  1024
+
+// Only used in screen.c; this is the maximum line number
+// in a multi-line message.  Should be < 1000
+// Note: message length is limited by the HBB_BLOCKSIZE size too
+#define MULTILINE_MAX_LINE_NUMBER 299
+
+// When chatstates are enabled, timeout (in seconds) before "composing"
+// becomes "paused" because of user inactivity.
+// Warning: setting this very low will cause more network traffic.
+#define COMPOSING_TIMEOUT 6L
+
+enum colors {
+  COLOR_GENERAL = 3,
+  COLOR_MSGOUT,
+  COLOR_MSGHL,
+  COLOR_STATUS,
+  COLOR_ROSTER,
+  COLOR_ROSTERSEL,
+  COLOR_ROSTERSELNMSG,
+  COLOR_ROSTERNMSG,
+  COLOR_INFO,
+  COLOR_MSGIN,
+  COLOR_max
+};
+
+int COLOR_ATTRIB[COLOR_max];
+
+extern int update_roster;
+
+typedef struct {
+  int value;
+  int utf8;
+  enum {
+    MKEY_META = 1,
+    MKEY_EQUIV,
+    MKEY_CTRL_PGUP,
+    MKEY_CTRL_PGDOWN,
+    MKEY_SHIFT_PGUP,
+    MKEY_SHIFT_PGDOWN,
+    MKEY_CTRL_SHIFT_PGUP,
+    MKEY_CTRL_SHIFT_PGDOWN,
+    MKEY_CTRL_HOME,
+    MKEY_CTRL_END,
+    MKEY_CTRL_INS,
+    MKEY_CTRL_DEL,
+    MKEY_CTRL_SHIFT_HOME,
+    MKEY_CTRL_SHIFT_END,
+    MKEY_MOUSE
+  } mcode;
+} keycode;
+
+typedef enum {
+  MC_ALL,
+  MC_PRESET,
+  MC_OFF,
+  MC_REMOVE
+} muccoltype;
+
+void scr_init_bindings(void);
+
+void scr_Getch(keycode *kcode);
+void process_key(keycode kcode);
+
+void scr_InitLocaleCharSet(void);
+void scr_InitCurses(void);
+void scr_TerminateCurses(void);
+void scr_DrawMainWindow(unsigned int fullinit);
+void scr_DrawRoster(void);
+void scr_UpdateMainStatus(int forceupdate);
+void scr_UpdateChatStatus(int forceupdate);
+void scr_RosterVisibility(int status);
+void scr_WriteIncomingMessage(const char *jidfrom, const char *text,
+                              time_t timestamp, guint prefix,
+                              unsigned mucnicklen);
+void scr_WriteOutgoingMessage(const char *jidto,   const char *text,
+                              guint prefix, gpointer xep184);
+void scr_RemoveReceiptFlag(const char *jidto, gpointer xep184);
+void scr_ShowBuddyWindow(void);
+int  scr_BuddyBufferExists(const char *jid);
+void scr_UpdateBuddyWindow(void);
+void scr_set_chatmode(int enable);
+int  scr_get_chatmode(void);
+void scr_set_multimode(int enable, char *subject);
+int  scr_get_multimode(void);
+void scr_setmsgflag_if_needed(const char *jid, int special);
+void scr_append_multiline(const char *line);
+const char *scr_get_multiline(void);
+const char *scr_get_multimode_subj(void);
+
+guint scr_getprefixwidth(void);
+void  scr_line_prefix(hbb_line *line, char *prefix, guint preflen);
+
+void scr_Beep(void);
+
+bool Autoaway;
+
+void scr_CheckAutoAway(int activity);
+
+#if defined JEP0022 || defined JEP0085
+gboolean scr_ChatStatesTimeout();
+#endif
+int chatstates_disabled;
+
+// For commands...
+void scr_RosterTop(void);
+void scr_RosterBottom(void);
+void scr_RosterUpDown(int updown, unsigned int n);
+void scr_RosterPrevGroup(void);
+void scr_RosterNextGroup(void);
+void scr_RosterSearch(char *);
+void scr_RosterJumpJid(char *);
+void scr_RosterDisplay(const char *);
+void scr_BufferTopBottom(int topbottom);
+void scr_BufferClear(void);
+void scr_BufferScrollLock(int lock);
+void scr_BufferPurge(int, const char*);
+void scr_BufferPurgeAll(int);
+void scr_BufferSearch(int direction, const char *text);
+void scr_BufferPercent(int pc);
+void scr_BufferDate(time_t t);
+void scr_BufferDump(const char *file);
+void scr_RosterUnreadMessage(int);
+void scr_RosterJumpAlternate(void);
+void scr_BufferScrollUpDown(int updown, unsigned int nblines);
+bool scr_RosterColor(const char *status, const char *wildcard,
+                     const char *color);
+void scr_RosterClearColor(void);
+void scr_MucColor(const char *muc, muccoltype type);
+void scr_MucNickColor(const char *nick, const char *color);
+void scr_BufferList(void);
+
+void readline_transpose_chars(void);
+void readline_forward_kill_word(void);
+void readline_backward_kill_word(void);
+void readline_backward_word(void);
+void readline_forward_word(void);
+void readline_updowncase_word(int);
+void readline_capitalize_word(void);
+void readline_backward_char(void);
+void readline_forward_char(void);
+int  readline_accept_line(int down_history);
+void readline_cancel_completion(void);
+void readline_do_completion(void);
+void readline_refresh_screen(void);
+void readline_disable_chat_mode(guint show_roster);
+void readline_hist_beginning_search_bwd(void);
+void readline_hist_beginning_search_fwd(void);
+void readline_hist_prev(void);
+void readline_hist_next(void);
+void readline_backward_kill_char(void);
+void readline_forward_kill_char(void);
+void readline_iline_start(void);
+void readline_iline_end(void);
+void readline_backward_kill_iline(void);
+void readline_forward_kill_iline(void);
+void readline_send_multiline(void);
+
+#endif
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/settings.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,609 @@
+/*
+ * settings.c   -- Configuration stuff
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#include "settings.h"
+#include "commands.h"
+#include "logprint.h"
+#include "otr.h"
+#include "utils.h"
+#include "xmpp.h"
+#include "main.h"
+
+// Maximum line length
+// (probably best to use the same value as INPUTLINE_LENGTH)
+#define CONFLINE_LENGTH  1024
+
+static GHashTable *option;
+static GHashTable *alias;
+static GHashTable *binding;
+
+#ifdef HAVE_GPGME     /* PGP settings */
+static GHashTable *pgpopt;
+
+typedef struct {
+  gchar *pgp_keyid;   /* KeyId the contact is supposed to use */
+  guint pgp_disabled; /* If TRUE, PGP is disabled for outgoing messages */
+  guint pgp_force;    /* If TRUE, PGP is used w/o negotiation */
+} T_pgpopt;
+#endif
+
+#ifdef HAVE_LIBOTR
+static GHashTable *otrpolicy;
+static enum otr_policy default_policy;
+#endif
+
+static inline GHashTable *get_hash(guint type)
+{
+  if      (type == SETTINGS_TYPE_OPTION)  return option;
+  else if (type == SETTINGS_TYPE_ALIAS)   return alias;
+  else if (type == SETTINGS_TYPE_BINDING) return binding;
+#ifdef HAVE_LIBOTR
+  else if (type == SETTINGS_TYPE_OTR)     return otrpolicy;
+#endif
+  return NULL;
+}
+
+/* -- */
+
+void settings_init(void)
+{
+  option  = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free);
+  alias   = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free);
+  binding = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free);
+#ifdef HAVE_GPGME
+  pgpopt = g_hash_table_new(&g_str_hash, &g_str_equal);
+#endif
+#ifdef HAVE_LIBOTR
+  otrpolicy = g_hash_table_new(&g_str_hash, &g_str_equal);
+#endif
+}
+
+//  cfg_read_file(filename, mainfile)
+// Read and parse config file "filename".  If filename is NULL,
+// try to open the configuration file at the default locations.
+// mainfile must be set to TRUE for the startup config file.
+// If mainfile is TRUE, the permissions of the configuration file will
+// be fixed if they're insecure.
+//
+int cfg_read_file(char *filename, guint mainfile)
+{
+  static unsigned int runtime;
+  FILE *fp;
+  char *buf;
+  char *line, *eol;
+  unsigned int ln = 0;
+  int err = 0;
+
+  if (!filename) {
+    // Use default config file locations
+    char *home;
+    GString *sfilename;
+
+    if (!mainfile) {
+      scr_LogPrint(LPRINT_LOGNORM, "No file name provided");
+      return -1;
+    }
+
+    home = getenv("HOME");
+    if (!home) {
+      scr_LogPrint(LPRINT_LOG, "Can't find home dir!");
+      fprintf(stderr, "Can't find home dir!\n");
+      err = -1;
+      goto cfg_read_file_return;
+    }
+    sfilename = g_string_new("");
+    g_string_printf(sfilename, "%s/.mcabber/mcabberrc", home);
+    if ((fp = fopen(sfilename->str, "r")) == NULL) {
+      // 2nd try...
+      g_string_printf(sfilename, "%s/.mcabberrc", home);
+      if ((fp = fopen(sfilename->str, "r")) == NULL) {
+        fprintf(stderr, "Cannot open config file!\n");
+        g_string_free(sfilename, TRUE);
+        err = -1;
+        goto cfg_read_file_return;
+      }
+    }
+    // Check configuration file permissions
+    // As it could contain sensitive data, we make it user-readable only.
+    checkset_perm(sfilename->str, TRUE);
+    scr_LogPrint(LPRINT_LOGNORM, "Reading %s", sfilename->str);
+    // Check mcabber dir.  Here we just warn, we don't change the modes.
+    g_string_printf(sfilename, "%s/.mcabber/", home);
+    checkset_perm(sfilename->str, FALSE);
+    g_string_free(sfilename, TRUE);
+  } else {
+    // filename was specified
+    if ((fp = fopen(filename, "r")) == NULL) {
+      const char *msg = "Cannot open configuration file";
+      if (mainfile)
+        perror(msg);
+      else
+        scr_LogPrint(LPRINT_LOGNORM, "%s (%s).", msg, filename);
+      err = -2;
+      goto cfg_read_file_return;
+    }
+    // Check configuration file permissions (see above)
+    // We don't change the permissions if that's not the main file.
+    if (mainfile)
+      checkset_perm(filename, TRUE);
+    scr_LogPrint(LPRINT_LOGNORM, "Reading %s", filename);
+  }
+
+  buf = g_new(char, CONFLINE_LENGTH+1);
+
+  while (fgets(buf+1, CONFLINE_LENGTH, fp) != NULL) {
+    // The first char is reserved to add a '/', to make a command line
+    line = buf+1;
+    ln++;
+
+    // Strip leading spaces
+    while (isspace(*line))
+      line++;
+
+    // Make eol point to the last char of the line
+    for (eol = line ; *eol ; eol++)
+      ;
+    if (eol > line)
+      eol--;
+
+    // Strip trailing spaces
+    while (eol > line && isspace(*eol))
+      *eol-- = 0;
+
+    // Ignore empty lines and comments
+    if ((*line == '\n') || (*line == '\0') || (*line == '#'))
+      continue;
+
+    // We only allow assignments line, except for commands "pgp", "source",
+    // "color", "load" and "otrpolicy", unless we're in runtime (i.e. not startup).
+    if (runtime ||
+        (strchr(line, '=') != NULL)        ||
+        startswith(line, "pgp ", FALSE)    ||
+        startswith(line, "source ", FALSE) ||
+        startswith(line, "color ", FALSE)  ||
+#ifdef MODULES_ENABLE
+        startswith(line, "load ", FALSE)   ||
+#endif
+        startswith(line, "otrpolicy", FALSE)) {
+      // Only accept a few "safe" commands
+      if (!runtime &&
+          !startswith(line, "set ", FALSE)    &&
+          !startswith(line, "bind ", FALSE)   &&
+          !startswith(line, "alias ", FALSE)  &&
+          !startswith(line, "pgp ", FALSE)    &&
+          !startswith(line, "source ", FALSE) &&
+          !startswith(line, "color ", FALSE)  &&
+#ifdef MODULES_ENABLE
+          !startswith(line, "load ", FALSE)   &&
+#endif
+          !startswith(line, "otrpolicy ", FALSE)) {
+        scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): "
+                     "this command can't be used here", ln);
+        err++;
+        continue;
+      }
+      // Set the leading COMMAND_CHAR to build a command line
+      // and process the command
+      *(--line) = COMMAND_CHAR;
+      if (process_command(line, TRUE) == 255)
+        mcabber_set_terminate_ui();
+    } else {
+      scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): "
+                   "this is not an assignment", ln);
+      err++;
+    }
+  }
+  g_free(buf);
+  fclose(fp);
+
+  if (filename)
+    scr_LogPrint(LPRINT_LOGNORM, "Loaded %s.", filename);
+
+cfg_read_file_return:
+  // If we're done with the main file parsing, we can assume that
+  // the next time this function is called will be at run time.
+  if (mainfile)
+    runtime = TRUE;
+  return err;
+}
+
+//  parse_assigment(assignment, pkey, pval)
+// Read assignment and split it to key, value
+//
+// If this is an assignment, the function will return TRUE and
+// set *pkey and *pval (*pval is set to NULL if value field is empty).
+//
+// If this isn't a assignment (no = char), the function will set *pval
+// to NULL and return FALSE.
+//
+// The caller should g_free() *pkey and *pval (if not NULL) after use.
+guint parse_assigment(gchar *assignment, gchar **pkey, gchar **pval)
+{
+  char *key, *val, *t, *p;
+
+  *pkey = *pval = NULL;
+
+  key = assignment;
+  // Remove leading spaces in option name
+  while ((!isalnum(*key)) && (*key != '=') && *key) {
+    //if (!isblank(*key))
+    //  scr_LogPrint("Error in assignment parsing!");
+    key++;
+  }
+  if (!*key) return FALSE; // Empty assignment
+
+  if (*key == '=') {
+    //scr_LogPrint("Cannot parse assignment!");
+    return FALSE;
+  }
+  // Ok, key points to the option name
+
+  for (val = key+1 ; *val && (*val != '=') ; val++)
+    if (!isalnum(*val) && !isblank(*val) && (*val != '_') && (*val != '-')) {
+      // Key should only have alnum chars...
+      //scr_LogPrint("Error in assignment parsing!");
+      return FALSE;
+    }
+  // Remove trailing spaces in option name:
+  for (t = val-1 ; t > key && isblank(*t) ; t--)
+    ;
+  // Check for embedded whitespace characters
+  for (p = key; p < t; p++) {
+    if (isblank(*p)) {
+      //scr_LogPrint("Error in assignment parsing!"
+      //             " (Name should not contain space chars)");
+      return FALSE;
+    }
+  }
+
+  *pkey = g_strndup(key, t+1-key);
+
+  if (!*val) return FALSE; // Not an assignment
+
+  // Remove leading and trailing spaces in option value:
+  for (val++; *val && isblank(*val) ; val++) ;
+  for (t = val ; *t ; t++) ;
+  for (t-- ; t >= val && isblank(*t) ; t--) ;
+
+  if (t < val) return TRUE; // no value (variable reset for example)
+
+  // If the value begins and ends with quotes ("), these quotes are
+  // removed and whitespace is not stripped
+  if ((t>val) && (*val == '"' && *t == '"')) {
+    val++;
+    t--;
+  }
+  *pval = g_strndup(val, t+1-val);
+  return TRUE;
+}
+
+void settings_set(guint type, const gchar *key, const gchar *value)
+{
+  GHashTable *hash;
+
+  hash = get_hash(type);
+  if (!hash)
+    return;
+
+  if (!value) {
+    g_hash_table_remove(hash, key);
+  } else {
+    g_hash_table_insert(hash, g_strdup(key), g_strdup(value));
+  }
+}
+
+void settings_del(guint type, const gchar *key)
+{
+  settings_set(type, key, NULL);
+}
+
+const gchar *settings_get(guint type, const gchar *key)
+{
+  GHashTable *hash;
+
+  hash = get_hash(type);
+  if (!hash)
+    return NULL;
+
+  return g_hash_table_lookup(hash, key);
+}
+
+int settings_get_int(guint type, const gchar *key)
+{
+  const gchar *setval = settings_get(type, key);
+
+  if (setval) return atoi(setval);
+  return 0;
+}
+
+//  settings_get_status_msg(status)
+// Return a string with the current status message:
+// - if there is a user-defined message ("message" option),
+//   return this message
+// - if there is a user-defined message for the given status (and no
+//   generic user message), it is returned
+// - if no message is found, return NULL
+const gchar *settings_get_status_msg(enum imstatus status)
+{
+  const gchar *rstatus = settings_opt_get("message");
+
+  if (rstatus) return rstatus;
+
+  switch(status) {
+    case available:
+        rstatus = settings_opt_get("message_avail");
+        break;
+
+    case freeforchat:
+        rstatus = settings_opt_get("message_free");
+        break;
+
+    case dontdisturb:
+        rstatus = settings_opt_get("message_dnd");
+        break;
+
+    case notavail:
+        rstatus = settings_opt_get("message_notavail");
+        break;
+
+    case away:
+        rstatus = settings_opt_get("message_away");
+        break;
+
+    default: // offline, invisible
+        break;
+  }
+  return rstatus;
+}
+
+//  settings_foreach(type, pfunction, param)
+// Call pfunction(key, value, param) for each setting with requested type.
+void settings_foreach(guint type, void (*pfunc)(char *k, char *v, void *param),
+                      void *param)
+{
+  GHashTable *hash;
+
+  hash = get_hash(type);
+  if (!hash)
+    return;
+
+  g_hash_table_foreach(hash, (GHFunc)pfunc, param);
+}
+
+
+//  default_muc_nickname()
+// Return the user's default nickname
+// The caller should free the string after use
+char *default_muc_nickname(const char *roomid)
+{
+  char *nick;
+
+  nick = (char*)xmpp_get_bookmark_nick(roomid);
+  if (nick)
+    return g_strdup(nick);
+
+  // We try the "nickname" option, then the username part of the jid.
+  nick = (char*)settings_opt_get("nickname");
+  if (nick)
+    return g_strdup(nick);
+
+  nick = jid_get_username(settings_opt_get("jid"));
+  return nick;
+}
+
+
+/* PGP settings */
+
+//  settings_pgp_setdisabled(jid, value)
+// Enable/disable PGP encryption for jid.
+// (Set value to TRUE to disable encryption)
+void settings_pgp_setdisabled(const char *bjid, guint value)
+{
+#ifdef HAVE_GPGME
+  T_pgpopt *pgpdata;
+  pgpdata = g_hash_table_lookup(pgpopt, bjid);
+  if (!pgpdata) {
+    // If value is 0, we do not need to create a structure (that's
+    // the default value).
+    if (value) {
+      pgpdata = g_new0(T_pgpopt, 1);
+      pgpdata->pgp_disabled = value;
+      g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+    }
+  } else {
+    pgpdata->pgp_disabled = value;
+    // We could remove the key/value if pgp_disabled is 0 and
+    // pgp_keyid is NULL, actually.
+  }
+#endif
+}
+
+//  settings_pgp_getdisabled(jid)
+// Return TRUE if PGP encryption should be disabled for jid.
+guint settings_pgp_getdisabled(const char *bjid)
+{
+#ifdef HAVE_GPGME
+  T_pgpopt *pgpdata;
+  pgpdata = g_hash_table_lookup(pgpopt, bjid);
+  if (pgpdata)
+    return pgpdata->pgp_disabled;
+  else
+    return FALSE; // Default: not disabled
+#else
+  return TRUE;    // No PGP support, let's say it's disabled.
+#endif
+}
+
+//  settings_pgp_setforce(jid, value)
+// Force (or not) PGP encryption for jid.
+// When value is TRUE, PGP support will be assumed for the remote client.
+void settings_pgp_setforce(const char *bjid, guint value)
+{
+#ifdef HAVE_GPGME
+  T_pgpopt *pgpdata;
+  pgpdata = g_hash_table_lookup(pgpopt, bjid);
+  if (!pgpdata) {
+    // If value is 0, we do not need to create a structure (that's
+    // the default value).
+    if (value) {
+      pgpdata = g_new0(T_pgpopt, 1);
+      pgpdata->pgp_force = value;
+      g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+    }
+  } else {
+    pgpdata->pgp_force = value;
+  }
+  if (value && pgpdata && !pgpdata->pgp_keyid)
+    scr_LogPrint(LPRINT_NORMAL, "Warning: the Key Id is not set!");
+#endif
+}
+
+//  settings_pgp_getforce(jid)
+// Return TRUE if PGP enforcement is set for jid.
+guint settings_pgp_getforce(const char *bjid)
+{
+#ifdef HAVE_GPGME
+  T_pgpopt *pgpdata;
+  pgpdata = g_hash_table_lookup(pgpopt, bjid);
+  if (pgpdata)
+    return pgpdata->pgp_force;
+  else
+    return FALSE; // Default
+#else
+  return FALSE;   // No PGP support
+#endif
+}
+
+//  settings_pgp_setkeyid(jid, keyid)
+// Set the PGP KeyId for user jid.
+// Use keyid = NULL to erase the previous KeyId.
+void settings_pgp_setkeyid(const char *bjid, const char *keyid)
+{
+#ifdef HAVE_GPGME
+  T_pgpopt *pgpdata;
+  pgpdata = g_hash_table_lookup(pgpopt, bjid);
+  if (!pgpdata) {
+    // If keyid is NULL, we do not need to create a structure (that's
+    // the default value).
+    if (keyid) {
+      pgpdata = g_new0(T_pgpopt, 1);
+      pgpdata->pgp_keyid = g_strdup(keyid);
+      g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
+    }
+  } else {
+    g_free(pgpdata->pgp_keyid);
+    if (keyid)
+      pgpdata->pgp_keyid = g_strdup(keyid);
+    else
+      pgpdata->pgp_keyid = NULL;
+    // We could remove the key/value if pgp_disabled is 0 and
+    // pgp_keyid is NULL, actually.
+  }
+#endif
+}
+
+//  settings_pgp_getkeyid(jid)
+// Get the PGP KeyId for user jid.
+const char *settings_pgp_getkeyid(const char *bjid)
+{
+#ifdef HAVE_GPGME
+  T_pgpopt *pgpdata;
+  pgpdata = g_hash_table_lookup(pgpopt, bjid);
+  if (pgpdata)
+    return pgpdata->pgp_keyid;
+#endif
+  return NULL;
+}
+
+/* otr settings */
+
+#ifdef HAVE_LIBOTR
+static void remove_default_policies(char *k, char *policy, void *defaultp)
+{
+  if (*(enum otr_policy *)policy == *(enum otr_policy *)defaultp) {
+    g_free((enum otr_policy *) policy);
+    g_hash_table_remove(otrpolicy, k);
+  }
+}
+#endif
+
+void settings_otr_setpolicy(const char *bjid, guint value)
+{
+#ifdef HAVE_LIBOTR
+  enum otr_policy *otrdata;
+
+  if (!bjid) {
+    default_policy = value;
+    /* refresh hash */
+    settings_foreach(SETTINGS_TYPE_OTR, &remove_default_policies, &value);
+    return;
+  }
+
+  otrdata = g_hash_table_lookup(otrpolicy, bjid);
+
+  if (value == default_policy) {
+    if (otrdata) {
+      g_free(otrdata);
+      g_hash_table_remove(otrpolicy, bjid);
+    }
+  } else if (otrdata) {
+    *otrdata = value;
+  } else {
+    otrdata = g_new(enum otr_policy, 1);
+    *otrdata = value;
+    g_hash_table_insert(otrpolicy, g_strdup(bjid), otrdata);
+  }
+#endif
+}
+
+guint settings_otr_getpolicy(const char *bjid)
+{
+#ifdef HAVE_LIBOTR
+  enum otr_policy *otrdata;
+  if (!bjid)
+    return default_policy;
+
+  otrdata = g_hash_table_lookup(otrpolicy, bjid);
+  if (otrdata)
+    return *otrdata;
+  else
+    return default_policy;
+#else
+  return 0;
+#endif
+}
+
+guint get_max_history_blocks(void)
+{
+  int max_num_of_blocks = settings_opt_get_int("max_history_blocks");
+  if (max_num_of_blocks < 0)
+    max_num_of_blocks = 0;
+  else if (max_num_of_blocks == 1)
+    max_num_of_blocks = 2;
+  return (guint)max_num_of_blocks;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/settings.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,62 @@
+#ifndef __MCABBER_SETTINGS_H__
+#define __MCABBER_SETTINGS_H__ 1
+
+#include <ctype.h>
+#include <glib.h>
+
+#include <mcabber/roster.h>
+#include <mcabber/config.h>
+
+#ifndef isblank
+# define isblank(c)  ((c) == 0x20 || (c) == 0x09)
+#endif
+
+
+#define SETTINGS_TYPE_OPTION    1
+#define SETTINGS_TYPE_ALIAS     2
+#define SETTINGS_TYPE_BINDING   3
+#ifdef HAVE_LIBOTR
+#define SETTINGS_TYPE_OTR       4
+#endif
+
+#define COMMAND_CHAR    '/'
+#define COMMAND_CHARSTR "/"
+
+#define settings_opt_get(k)     settings_get(SETTINGS_TYPE_OPTION, k)
+#define settings_opt_get_int(k) settings_get_int(SETTINGS_TYPE_OPTION, k)
+
+#define mkcmdstr(cmd) COMMAND_CHARSTR cmd
+
+void    settings_init(void);
+int     cfg_read_file(char *filename, guint mainfile);
+guint   parse_assigment(gchar *assignment, gchar **pkey, gchar **pval);
+void    settings_set(guint type, const gchar *key, const gchar *value);
+void    settings_del(guint type, const gchar *key);
+const gchar *settings_get(guint type, const gchar *key);
+int     settings_get_int(guint type, const gchar *key);
+const gchar *settings_get_status_msg(enum imstatus status);
+void    settings_foreach(guint type,
+                         void (*pfunc)(char *k, char *v, void *param),
+                         void *param);
+
+void    settings_pgp_setdisabled(const char *bjid, guint value);
+guint   settings_pgp_getdisabled(const char *bjid);
+void    settings_pgp_setforce(const char *bjid, guint value);
+guint   settings_pgp_getforce(const char *bjid);
+void    settings_pgp_setkeyid(const char *bjid, const char *keyid);
+const char *settings_pgp_getkeyid(const char *bjid);
+
+#ifdef HAVE_LIBOTR
+guint   settings_otr_getpolicy(const char *bjid);
+void    settings_otr_setpolicy(const char *bjid, guint value);
+#endif
+
+guint get_max_history_blocks(void);
+
+char *default_muc_nickname(const char *roomid);
+
+const gchar *isbound(int key);
+
+#endif /* __MCABBER_SETTINGS_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/utf8.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,98 @@
+/*
+ * utf8.c       -- UTF-8 routines
+ *
+ * Copyright (C) 2006 Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include "utf8.h"
+
+char *prev_char(char *str, const char *limit)
+{
+  if (str <= limit)
+    return str;
+  str--;
+  if (utf8_mode)
+    while ((str > limit) && ((*str & 0xc0) == 0x80))
+      str--;
+  return str;
+}
+
+char *next_char(char *str)
+{
+  if (!*str)
+    return str;
+  str++;
+  if (utf8_mode)
+    while ((*str & 0xc0) == 0x80)
+      str++;
+  return str;
+}
+
+unsigned get_char(const char *str)
+{
+  unsigned char *strp = (unsigned char *)str;
+  unsigned c = *strp++;
+  unsigned mask = 0x80;
+  int len = -1;
+  if (!utf8_mode)
+    return c;
+  while (c & mask) {
+    mask >>= 1;
+    len++;
+  }
+  if (len <= 0 || len > 4)
+    goto no_utf8;
+  c &= mask - 1;
+  while ((*strp & 0xc0) == 0x80) {
+    if (len-- <= 0)
+      goto no_utf8;
+    c = (c << 6) | (*strp++ & 0x3f);
+  }
+  if (len)
+    goto no_utf8;
+  return c;
+
+no_utf8:
+  return *str;
+}
+
+char *put_char(char *str, unsigned c)
+{
+  int mask = 0xffffffc0;
+  int i = 4;
+  char code[5];
+  if (!utf8_mode || c < 128) {
+    *str++ = c;
+    return str;
+  }
+  while (c & mask) {
+    code[i--] = 0x80 | (c & 0x3f);
+    c >>= 6;
+    mask >>= 1;
+    if (i < 0) {
+      *str++ = '?';
+      return str;
+    }
+  }
+  code[i] = (mask << 1) | c;
+  for (; i < 5; i++)
+    *str++ = code[i];
+  return str;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/utf8.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,49 @@
+#ifndef __MCABBER_UTF8_H__
+#define __MCABBER_UTF8_H__ 1
+
+#include <mcabber/config.h>
+
+#if defined HAVE_UNICODE && defined HAVE_WCHAR_H && defined HAVE_WCTYPE_H
+# define UNICODE
+#endif
+
+#ifdef HAVE_WCHAR_H
+# include <wchar.h>
+# define get_char_width(c) (utf8_mode ? wcwidth(get_char(c)) : 1)
+#else
+# define wcwidth(c) 1
+# define get_char_width(c) 1
+#endif
+
+#ifdef HAVE_WCTYPE_H
+# include <wctype.h>
+
+/* The following bit is a hack for Solaris 8&9 systems that don't have
+ * iswblank().
+ * For now i made sure it comes after wctype.h so it doesn't create
+ * problems (wctype.h has calls to iswblank() before wctype() is declared).
+ * (Sebastian Kayser)
+ */
+# ifndef HAVE_ISWBLANK
+#  define iswblank(wc) iswctype(wc, wctype("blank"))
+# endif
+
+#else
+# define iswblank(c) (c == ' ')
+# define iswalnum(c) isalnum(c)
+# define iswprint(c) isprint(c)
+# define towupper(c) toupper(c)
+# define towlower(c) tolower(c)
+# define iswalpha(c) isalpha(c)
+#endif
+
+extern int utf8_mode;
+
+char *prev_char(char *str, const char *limit);
+char *next_char(char *str);
+unsigned get_char(const char *str);
+char *put_char(char *str, unsigned c);
+
+#endif /* __MCABBER_UTF8_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/utils.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,789 @@
+/*
+ * utils.c      -- Various utility functions
+ *
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ * Some of the ut_* functions are derived from Cabber debug/log code.
+ * from_iso8601() comes from the Pidgin (libpurple) project.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#ifdef HAVE_LIBIDN
+#include <idna.h>
+#include <stringprep.h>
+static char idnprep[1024];
+#endif
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+/* For Cygwin (thanks go to Yitzchak Scott-Thoennes) */
+#ifdef __CYGWIN__
+#  define timezonevar
+   extern long timezone;
+#endif
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#include "utils.h"
+#include "logprint.h"
+
+static int DebugEnabled;
+static char *FName;
+
+//  jidtodisp(jid)
+// Strips the resource part from the jid
+// The caller should g_free the result after use.
+char *jidtodisp(const char *fjid)
+{
+  char *ptr;
+  char *alias;
+
+  alias = g_strdup(fjid);
+
+  if ((ptr = strchr(alias, JID_RESOURCE_SEPARATOR)) != NULL) {
+    *ptr = 0;
+  }
+  return alias;
+}
+
+char *jid_get_username(const char *fjid)
+{
+  char *ptr;
+  char *username;
+
+  username = g_strdup(fjid);
+  if ((ptr = strchr(username, JID_DOMAIN_SEPARATOR)) != NULL) {
+    *ptr = 0;
+  }
+  return username;
+}
+
+char *compose_jid(const char *username, const char *servername,
+                  const char *resource)
+{
+  char *fjid;
+
+  if (!strchr(username, JID_DOMAIN_SEPARATOR)) {
+    fjid = g_strdup_printf("%s%c%s%c%s", username,
+                           JID_DOMAIN_SEPARATOR, servername,
+                           JID_RESOURCE_SEPARATOR, resource);
+  } else {
+    fjid = g_strdup_printf("%s%c%s", username,
+                           JID_RESOURCE_SEPARATOR, resource);
+  }
+  return fjid;
+}
+
+gboolean jid_equal(const char *jid1, const char *jid2)
+{
+  char *a,*b;
+  int ret;
+  if (!jid1 && !jid2)
+    return TRUE;
+  if (!jid1 || !jid2)
+    return FALSE;
+
+  a = jidtodisp(jid1);
+  b = jidtodisp(jid2);
+  ret = strcasecmp(a, b);
+  g_free(a);
+  g_free(b);
+  return (ret == 0) ? TRUE : FALSE;
+}
+
+//  expand_filename(filename)
+// Expand "~/" with the $HOME env. variable in a file name.
+// The caller must free the string after use.
+char *expand_filename(const char *fname)
+{
+  if (!fname)
+    return NULL;
+  if (!strncmp(fname, "~/", 2)) {
+    char *homedir = getenv("HOME");
+    if (homedir)
+      return g_strdup_printf("%s%s", homedir, fname+1);
+  }
+  return g_strdup(fname);
+}
+
+void fingerprint_to_hex(const unsigned char *fpr, char hex[49])
+{
+  int i;
+  char *p;
+
+  for (p = hex, i = 0; i < 15; i++, p+=3)
+    g_sprintf(p, "%02X:", fpr[i]);
+  g_sprintf(p, "%02X", fpr[i]);
+  hex[48] = '\0';
+}
+
+gboolean hex_to_fingerprint(const char *hex, char fpr[16])
+{
+  int i;
+  char *p;
+
+  if (strlen(hex) != 47)
+    return FALSE;
+  for (i = 0, p = (char*)hex; *p && *(p+1); i++, p += 3)
+    fpr[i] = (char) g_ascii_strtoull (p, NULL, 16);
+  return TRUE;
+}
+
+void ut_InitDebug(int level, const char *filename)
+{
+  FILE *fp;
+  struct stat buf;
+  int err;
+
+  if (level < 1) {
+    DebugEnabled = 0;
+    FName = NULL;
+    return;
+  }
+
+  if (filename)
+    FName = expand_filename(filename);
+  else {
+    FName = getenv("HOME");
+    if (!FName)
+      FName = g_strdup("/tmp/mcabberlog");
+    else {
+      FName = g_strdup_printf("%s/mcabberlog", FName);
+    }
+  }
+
+  DebugEnabled = level;
+
+  fp = fopen(FName, "a");
+  if (!fp) {
+    fprintf(stderr, "ERROR: Cannot open tracelog file\n");
+    return;
+  }
+
+  err = fstat(fileno(fp), &buf);
+  if (err || buf.st_uid != geteuid()) {
+    fclose(fp);
+    DebugEnabled = 0;
+    FName = NULL;
+    if (err) {
+      fprintf(stderr, "ERROR: cannot stat the tracelog file!\n");
+    } else {
+      fprintf(stderr, "ERROR: tracelog file does not belong to you!\n");
+    }
+    return;
+  }
+  fchmod(fileno(fp), S_IRUSR|S_IWUSR);
+
+  fprintf(fp, "New trace log started.\n----------------------\n");
+  fclose(fp);
+}
+
+void ut_WriteLog(unsigned int flag, const char *data)
+{
+  if (!DebugEnabled || !FName) return;
+
+  if (((DebugEnabled >= 2) && (flag & (LPRINT_LOG|LPRINT_DEBUG))) ||
+      ((DebugEnabled == 1) && (flag & LPRINT_LOG))) {
+    FILE *fp = fopen(FName, "a+");
+    if (!fp) {
+      scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot open tracelog file");
+      return;
+    }
+    if (fputs(data, fp) == EOF)
+      scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot write to tracelog file");
+    fclose(fp);
+  }
+}
+
+//  checkset_perm(name, setmode)
+// Check the permissions of the "name" file/dir
+// If setmode is true, correct the permissions if they are wrong
+// Return values: -1 == bad file/dir, 0 == success, 1 == cannot correct
+int checkset_perm(const char *name, unsigned int setmode)
+{
+  int fd;
+  struct stat buf;
+
+#ifdef __CYGWIN__
+  // Permission checking isn't efficient on Cygwin
+  return 0;
+#endif
+
+  fd = stat(name, &buf);
+  if (fd == -1) return -1;
+
+  if (buf.st_uid != geteuid()) {
+    scr_LogPrint(LPRINT_LOGNORM, "Wrong file owner [%s]", name);
+    return 1;
+  }
+
+  if (buf.st_mode & (S_IRGRP | S_IWGRP | S_IXGRP) ||
+      buf.st_mode & (S_IROTH | S_IWOTH | S_IXOTH)) {
+    if (setmode) {
+      mode_t newmode = 0;
+      scr_LogPrint(LPRINT_LOGNORM, "Bad permissions [%s]", name);
+      if (S_ISDIR(buf.st_mode))
+        newmode |= S_IXUSR;
+      newmode |= S_IRUSR | S_IWUSR;
+      if (chmod(name, newmode)) {
+        scr_LogPrint(LPRINT_LOGNORM, "WARNING: Failed to correct permissions!");
+        return 1;
+      }
+      scr_LogPrint(LPRINT_LOGNORM, "Permissions have been corrected");
+    } else {
+      scr_LogPrint(LPRINT_LOGNORM, "WARNING: Bad permissions [%s]", name);
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+const char *ut_get_tmpdir(void)
+{
+  static const char *tmpdir;
+  const char *tmpvars[] = { "MCABBERTMPDIR", "TMP", "TMPDIR", "TEMP" };
+  unsigned int i;
+
+  if (tmpdir)
+    return tmpdir;
+
+  for (i = 0; i < (sizeof(tmpvars) / sizeof(const char *)); i++) {
+    tmpdir = getenv(tmpvars[i]);
+    if (tmpdir && tmpdir[0] && tmpdir[0] == '/' && tmpdir[1]) {
+      // Looks ok.
+      return tmpdir;
+    }
+  }
+
+  // Default temporary directory
+  tmpdir = "/tmp";
+  return tmpdir;
+}
+
+//  to_iso8601(dststr, timestamp)
+// Convert timestamp to iso8601 format, and store it in dststr.
+// NOTE: dststr should be at last 19 chars long.
+// Return should be 0
+int to_iso8601(char *dststr, time_t timestamp)
+{
+  struct tm *tm_time;
+  int ret;
+
+  tm_time = gmtime(&timestamp);
+
+  ret = snprintf(dststr, 19, "%.4d%02d%02dT%02d:%02d:%02dZ",
+        (int)(1900+tm_time->tm_year), tm_time->tm_mon+1, tm_time->tm_mday,
+        tm_time->tm_hour, tm_time->tm_min, tm_time->tm_sec);
+
+  return ((ret == -1) ? -1 : 0);
+}
+
+//  from_iso8601(timestamp, utc)
+// This function came from the Pidgin project, gaim_str_to_time().
+// (Actually date may not be pure iso-8601)
+// Thanks, guys!
+// ** Modified by somian 10 Apr 2006 with advice from ysth.
+time_t from_iso8601(const char *timestamp, int utc)
+{
+  struct tm t;
+  time_t retval = 0;
+  char buf[32];
+  char *c;
+  int tzoff = 0;
+  int hms_succ = 0;
+  int tmpyear;
+
+  time(&retval);
+  localtime_r(&retval, &t);
+
+  /* Reset time to midnight (00:00:00) */
+  t.tm_hour = t.tm_min = t.tm_sec = 0;
+
+  snprintf(buf, sizeof(buf), "%s", timestamp);
+  c = buf;
+
+  /* 4 digit year */
+  if (!sscanf(c, "%04d", &tmpyear)) return 0;
+  t.tm_year = tmpyear;
+  c+=4;
+  if (*c == '-')
+    c++;
+
+  t.tm_year -= 1900;
+
+  /* 2 digit month */
+  if (!sscanf(c, "%02d", &t.tm_mon)) return 0;
+  c+=2;
+  if (*c == '-')
+    c++;
+
+  t.tm_mon -= 1;
+
+  /* 2 digit day */
+  if (!sscanf(c, "%02d", &t.tm_mday)) return 0;
+  c+=2;
+  if (*c == 'T' || *c == '.') { /* we have more than a date, keep going */
+    c++; /* skip the "T" */
+
+    /* 2 digit hour */
+    if (sscanf(c, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3)
+    {
+      hms_succ = 1;
+      c += 8;
+    }
+    else if (sscanf(c, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3)
+    {
+       hms_succ = 1;
+       c += 6;
+    }
+
+    if (hms_succ) {
+      int tzhrs, tzmins;
+
+      if (*c == '.') /* dealing with precision we don't care about */
+        c += 4;
+
+      if ((*c == '+' || *c == '-') &&
+          sscanf(c+1, "%02d:%02d", &tzhrs, &tzmins)) {
+        tzoff = tzhrs*60*60 + tzmins*60;
+        if (*c == '+')
+          tzoff *= -1;
+      }
+
+      if (tzoff || utc) {
+#ifdef HAVE_TM_GMTOFF
+        tzoff += t.tm_gmtoff;
+#else
+#  ifdef HAVE_TIMEZONE
+        tzset();    /* making sure */
+        tzoff -= timezone;
+#  endif
+#endif
+      }
+    }
+  }
+
+  t.tm_isdst = -1;
+
+  retval = mktime(&t);
+
+  retval += tzoff;
+
+  return retval;
+}
+
+/**
+ * Derived from libjabber/jid.c, because the libjabber version is not
+ * really convenient for our usage.
+ *
+ * Check if the full JID is valid
+ * Return 0 if it is valid, non zero otherwise
+ */
+int check_jid_syntax(const char *fjid)
+{
+  const char *str;
+  const char *domain, *resource;
+  int domlen;
+#ifdef HAVE_LIBIDN
+  char *idnpp;
+  int r;
+#endif
+
+  if (!fjid) return 1;
+
+  domain = strchr(fjid, JID_DOMAIN_SEPARATOR);
+
+  /* the username is optional */
+  if (!domain) {
+    domain = fjid;
+  } else {
+    /* node identifiers may not be longer than 1023 bytes */
+    if ((domain == fjid) || (domain-fjid > 1023))
+      return 1;
+    domain++;
+
+#ifdef HAVE_LIBIDN
+    idnpp = idnprep;
+    str = fjid;
+    while (*str != JID_DOMAIN_SEPARATOR)
+      *idnpp++ = *str++;
+    *idnpp = 0;
+
+    r = stringprep(idnprep, 1023, 0, stringprep_xmpp_nodeprep);
+    if (r != STRINGPREP_OK || !idnprep[0])
+      return 1;
+    /* the username looks okay */
+#else
+    /* check for low and invalid ascii characters in the username */
+    for (str = fjid; *str != JID_DOMAIN_SEPARATOR; str++) {
+      if (*str <= ' ' || *str == ':' || *str == JID_DOMAIN_SEPARATOR ||
+              *str == '<' || *str == '>' || *str == '\'' ||
+              *str == '"' || *str == '&') {
+        return 1;
+      }
+    }
+    /* the username is okay as far as we can tell without LIBIDN */
+#endif
+  }
+
+  resource = strchr(domain, JID_RESOURCE_SEPARATOR);
+
+  /* the resource is optional */
+  if (resource) {
+    domlen = resource - domain;
+    resource++;
+    /* resources may not be longer than 1023 bytes */
+    if ((*resource == '\0') || strlen(resource) > 1023)
+      return 1;
+#ifdef HAVE_LIBIDN
+    strncpy(idnprep, resource, sizeof(idnprep));
+    r = stringprep(idnprep, 1023, 0, stringprep_xmpp_resourceprep);
+    if (r != STRINGPREP_OK || !idnprep[0])
+      return 1;
+#endif
+  } else {
+    domlen = strlen(domain);
+  }
+
+  /* there must be a domain identifier */
+  if (domlen == 0) return 1;
+
+  /* and it must not be longer than 1023 bytes */
+  if (domlen > 1023) return 1;
+
+#ifdef HAVE_LIBIDN
+  idnpp = idnprep;
+  str = domain;
+  while (*str != '\0' && *str != JID_RESOURCE_SEPARATOR)
+    *idnpp++ = *str++;
+  *idnpp = 0;
+
+  r = stringprep_nameprep(idnprep, 1023);
+  if (r != STRINGPREP_OK || !idnprep[0])
+    return 1;
+
+  if (idna_to_ascii_8z(idnprep, &idnpp, IDNA_USE_STD3_ASCII_RULES) !=
+      IDNA_SUCCESS)
+    return 1;
+  else
+    free(idnpp);
+#else
+  /* make sure the hostname is valid characters */
+  for (str = domain; *str != '\0' && *str != JID_RESOURCE_SEPARATOR; str++) {
+    if (!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_'))
+      return 1;
+  }
+#endif
+
+  /* it's okay as far as we can tell */
+  return 0;
+}
+
+
+inline void mc_strtolower(char *str)
+{
+  if (!str) return;
+  for ( ; *str; str++)
+    *str = tolower(*str);
+}
+
+//  strip_arg_special_chars(string)
+// Remove quotes and backslashes before an escaped quote
+// Only quotes need a backslash
+// Ex.: ["a b"] -> [a b]; [a\"b] -> [a"b]
+void strip_arg_special_chars(char *s)
+{
+  int instring = FALSE;
+  int escape = FALSE;
+  char *p;
+
+  if (!s) return;
+
+  for (p = s; *p; p++) {
+    if (*p == '"') {
+      if (!escape) {
+        instring = !instring;
+        strcpy(p, p+1);
+        p--;
+      } else
+        escape = FALSE;
+    } else if (*p == '\\') {
+      if (!escape) {
+        strcpy(p, p+1);
+        p--;
+      }
+      escape = !escape;
+    } else
+      escape = FALSE;
+  }
+}
+
+//  split_arg(arg, n, preservelast)
+// Split the string arg into a maximum of n pieces, taking care of
+// double quotes.
+// Return a null-terminated array of strings.  This array should be freed
+// by the caller after use, for example with free_arg_lst().
+// If dontstriplast is true, the Nth argument isn't stripped (i.e. no
+// processing of quote chars)
+char **split_arg(const char *arg, unsigned int n, int dontstriplast)
+{
+  char **arglst;
+  const char *p, *start, *end;
+  unsigned int i = 0;
+  int instring = FALSE;
+  int escape = FALSE;
+
+  arglst = g_new0(char*, n+1);
+
+  if (!arg || !n) return arglst;
+
+  // Skip leading space
+  for (start = arg; *start && *start == ' '; start++) ;
+  // End of string pointer
+  for (end = start; *end; end++) ;
+  // Skip trailing space
+  while (end > start+1 && *(end-1) == ' ')
+    end--;
+
+  for (p = start; p < end; p++) {
+    if (*p == '"' && !escape)
+      instring = !instring;
+    if (*p == '\\' && !escape)
+      escape = TRUE;
+    else if (escape)
+      escape = FALSE;
+    if (*p == ' ' && !instring && i+1 < n) {
+      // end of parameter
+      *(arglst+i) = g_strndup(start, p-start);
+      strip_arg_special_chars(*(arglst+i));
+      for (start = p+1; *start && *start == ' '; start++) ;
+      p = start-1;
+      i++;
+    }
+  }
+
+  if (start < end) {
+    *(arglst+i) = g_strndup(start, end-start);
+    if (!dontstriplast || i+1 < n)
+      strip_arg_special_chars(*(arglst+i));
+  }
+
+  return arglst;
+}
+
+//  free_arg_lst(arglst)
+// Free an array allocated by split_arg()
+void free_arg_lst(char **arglst)
+{
+  char **arg_elt;
+
+  for (arg_elt = arglst; *arg_elt; arg_elt++)
+    g_free(*arg_elt);
+  g_free(arglst);
+}
+
+//  replace_nl_with_dots(bufstr)
+// Replace '\n' with "(...)" (or with a NUL if the string is too short)
+void replace_nl_with_dots(char *bufstr)
+{
+  char *p = strchr(bufstr, '\n');
+  if (p) {
+    if (strlen(p) >= 5)
+      strcpy(p, "(...)");
+    else
+      *p = 0;
+  }
+}
+
+//  ut_expand_tabs(text)
+// Expand tabs and filter out some bad chars in string text.
+// If there is no tab and no bad chars in the string, a pointer to text
+// is returned (be careful _not_ to free the pointer in this case).
+// If there are some tabs or bad chars, a new string with expanded chars
+// and no bad chars is returned; this is up to the caller to free this
+// string after use.
+char *ut_expand_tabs(const char *text)
+{
+  char *xtext, *linestart;
+  char *p, *q;
+  guint n = 0, bc = 0;
+
+  xtext = (char*)text;
+  for (p=xtext; *p; p++)
+    if (*p == '\t')
+      n++;
+    else if (*p == '\x0d')
+      bc++;
+  // XXX Are there other special chars we should filter out?
+
+  if (!n && !bc)
+    return (char*)text;
+
+  xtext = g_new(char, strlen(text) + 1 + 8*n);
+  p = (char*)text;
+  q = linestart = xtext;
+  do {
+    if (*p == '\t') {
+      do { *q++ = ' '; } while ((q-linestart)%8);
+    } else if (*p != '\x0d') {
+      *q++ = *p;
+      if (*p =='\n')
+        linestart = q;
+    }
+  } while (*p++);
+
+  return xtext;
+}
+
+
+/* Cygwin's newlib does not have strcasestr() */
+/* The author of the code before the endif is
+ *    Jeffrey Stedfast <fejj@ximian.com>
+ * and this code is reusable in compliance with the GPL v2. -- somian */
+
+#if !defined(HAVE_STRCASESTR)
+
+#  define lowercase(c)  (isupper ((int) (c)) ? tolower ((int) (c)) : (int) (c))
+#  define bm_index(c, icase)      ((icase) ? lowercase (c) : (int) (c))
+#  define bm_equal(c1, c2, icase) ((icase) ? lowercase (c1) == lowercase (c2) : (c1) == (c2))
+
+/* FIXME: this is just a guess... should really do some performace tests to get an accurate measure */
+#  define bm_optimal(hlen, nlen)  (((hlen) ? (hlen) > 20 : 1) && (nlen) > 10 ? 1 : 0)
+
+static unsigned char *
+__boyer_moore (const unsigned char *haystack, size_t haystacklen,
+               const unsigned char *needle, size_t needlelen, int icase)
+{
+  register unsigned char *hc_ptr, *nc_ptr;
+  unsigned char *he_ptr, *ne_ptr, *h_ptr;
+  size_t skiptable[256], n;
+  register int i;
+
+#ifdef BOYER_MOORE_CHECKS
+  /* we don't need to do these checks since memmem/strstr/etc do it already */
+  /* if the haystack is shorter than the needle then we can't possibly match */
+  if (haystacklen < needlelen)
+    return NULL;
+
+  /* instant match if the pattern buffer is 0-length */
+  if (needlelen == 0)
+    return (unsigned char *) haystack;
+#endif /* BOYER_MOORE_CHECKS */
+
+  /* set a pointer at the end of each string */
+  ne_ptr = (unsigned char *) needle + needlelen - 1;
+  he_ptr = (unsigned char *) haystack + haystacklen - 1;
+
+  /* create our skip table */
+  for (i = 0; i < 256; i++)
+    skiptable[i] = needlelen;
+  for (nc_ptr = (unsigned char *) needle; nc_ptr < ne_ptr; nc_ptr++)
+    skiptable[bm_index (*nc_ptr, icase)] = (size_t) (ne_ptr - nc_ptr);
+
+  h_ptr = (unsigned char *) haystack;
+  while (haystacklen >= needlelen) {
+    hc_ptr = h_ptr + needlelen - 1;   /* set the haystack compare pointer */
+    nc_ptr = ne_ptr;                  /* set the needle compare pointer */
+
+    /* work our way backwards till they don't match */
+    for (i = 0; nc_ptr > (unsigned char *) needle; nc_ptr--, hc_ptr--, i++)
+      if (!bm_equal (*nc_ptr, *hc_ptr, icase))
+        break;
+
+    if (!bm_equal (*nc_ptr, *hc_ptr, icase)) {
+      n = skiptable[bm_index (*hc_ptr, icase)];
+      if (n == needlelen && i)
+        if (bm_equal (*ne_ptr, ((unsigned char *) needle)[0], icase))
+          n--;
+      h_ptr += n;
+      haystacklen -= n;
+    } else
+      return (unsigned char *) h_ptr;
+  }
+
+  return NULL;
+}
+
+/*
+ * strcasestr:
+ * @haystack: string to search
+ * @needle: substring to search for
+ *
+ * Finds the first occurence of the substring @needle within the
+ * string @haystack ignoring case.
+ *
+ * Returns a pointer to the beginning of the substring match within
+ * @haystack, or NULL if the substring is not found.
+ **/
+char *
+strcasestr (const char *haystack, const char *needle)
+{
+  register unsigned char *h, *n, *hc, *nc;
+  size_t needlelen;
+
+  needlelen = strlen (needle);
+
+  if (needlelen == 0) {
+    return (char *) haystack;
+  } else if (bm_optimal (0, needlelen)) {
+    return (char *) __boyer_moore ((const unsigned char *) haystack,
+                                   strlen (haystack),
+                                   (const unsigned char *) needle,
+                                   needlelen, 1);
+  }
+
+  h = (unsigned char *) haystack;
+  n = (unsigned char *) needle;
+
+  while (*(h + needlelen - 1)) {
+    if (lowercase (*h) == lowercase (*n)) {
+      for (hc = h + 1, nc = n + 1; *hc && *nc; hc++, nc++)
+        if (lowercase (*hc) != lowercase (*nc))
+          break;
+
+      if (!*nc)
+        return (char *) h;
+    }
+    h++;
+  }
+  return NULL;
+}
+#endif /* !HAVE_STRCASESTR */
+
+//  startswith(str, word, ignore_case)
+// Returns TRUE if string str starts with word.
+int startswith(const char *str, const char *word, guint ignore_case)
+{
+  if (ignore_case && !strncasecmp(str, word, strlen(word)))
+    return TRUE;
+  else if (!ignore_case && !strncmp(str, word, strlen(word)))
+    return TRUE;
+  return FALSE;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/utils.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,57 @@
+#ifndef __MCABBER_UTILS_H__
+#define __MCABBER_UTILS_H__ 1
+
+#include <mcabber/config.h>
+
+extern const char *LocaleCharSet;
+
+#define to_utf8(s)   ((s) ? g_locale_to_utf8((s),   -1, NULL,NULL,NULL) : NULL)
+#define from_utf8(s) ((s) ? g_convert_with_fallback((s), -1, LocaleCharSet, \
+                                        "UTF-8", NULL,NULL,NULL,NULL) : NULL)
+
+#define JID_RESOURCE_SEPARATOR      '/'
+#define JID_RESOURCE_SEPARATORSTR   "/"
+#define JID_DOMAIN_SEPARATOR        '@'
+#define JID_DOMAIN_SEPARATORSTR     "@"
+
+char *jidtodisp(const char *fjid);
+char *jid_get_username(const char *fjid);
+char *compose_jid(const char *username, const char *servername,
+                  const char *resource);
+gboolean jid_equal(const char *jid1, const char *jid2);
+
+void fingerprint_to_hex(const unsigned char *fpr, char hex[49]);
+gboolean hex_to_fingerprint(const char * hex, char fpr[16]);
+
+void ut_InitDebug(int level, const char *file);
+void ut_WriteLog(unsigned int flag, const char *data);
+
+char *expand_filename(const char *fname);
+
+int checkset_perm(const char *name, unsigned int setmode);
+
+const char *ut_get_tmpdir(void);
+
+int    to_iso8601(char *dststr, time_t timestamp);
+time_t from_iso8601(const char *timestamp, int utc);
+
+int check_jid_syntax(const char *fjid);
+
+void mc_strtolower(char *str);
+
+void strip_arg_special_chars(char *s);
+char **split_arg(const char *arg, unsigned int n, int dontstriplast);
+void free_arg_lst(char **arglst);
+
+void replace_nl_with_dots(char *bufstr);
+char *ut_expand_tabs(const char *text);
+
+#if !defined (HAVE_STRCASESTR)
+char *strcasestr(const char *haystack, const char *needle);
+#endif
+
+int startswith(const char *str, const char *word, guint ignore_case);
+
+#endif // __MCABBER_UTILS_H__
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,2200 @@
+/*
+ * xmpp.c       -- Jabber protocol handling
+ *
+ * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ * Parts come from the centericq project:
+ * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+#include <stdlib.h>
+#include <string.h>
+
+#include "xmpp.h"
+#include "xmpp_helper.h"
+#include "xmpp_iq.h"
+#include "xmpp_iqrequest.h"
+#include "xmpp_muc.h"
+#include "xmpp_s10n.h"
+#include "caps.h"
+#include "events.h"
+#include "histolog.h"
+#include "hooks.h"
+#include "otr.h"
+#include "roster.h"
+#include "screen.h"
+#include "settings.h"
+#include "utils.h"
+#include "main.h"
+
+#define RECONNECTION_TIMEOUT    60L
+
+LmConnection* lconnection;
+static guint AutoConnection;
+
+inline void update_last_use(void);
+inline gboolean xmpp_reconnect();
+
+enum imstatus mystatus = offline;
+static enum imstatus mywantedstatus = available;
+gchar *mystatusmsg;
+
+char imstatus2char[imstatus_size+1] = {
+    '_', 'o', 'f', 'd', 'n', 'a', 'i', '\0'
+};
+
+char *imstatus_showmap[] = {
+  "",
+  "",
+  "chat",
+  "dnd",
+  "xa",
+  "away",
+  ""
+};
+
+LmMessageNode *bookmarks = NULL;
+LmMessageNode *rosternotes = NULL;
+
+static struct IqHandlers
+{
+  const gchar *xmlns;
+  LmHandleMessageFunction handler;
+} iq_handlers[] = {
+  {NS_PING,       &handle_iq_ping},
+  {NS_VERSION,    &handle_iq_version},
+  {NS_TIME,       &handle_iq_time},
+  {NS_ROSTER,     &handle_iq_roster},
+  {NS_XMPP_TIME,  &handle_iq_time202},
+  {NS_LAST,       &handle_iq_last},
+  {NS_DISCO_INFO, &handle_iq_disco_info},
+  {NS_DISCO_ITEMS,&handle_iq_disco_items},
+  {NS_COMMANDS,   &handle_iq_commands},
+  {NS_VCARD,      &handle_iq_vcard},
+  {NULL, NULL}
+};
+
+void update_last_use(void)
+{
+  iqlast = time(NULL);
+}
+
+// Note: the caller should check the jid is correct
+void xmpp_addbuddy(const char *bjid, const char *name, const char *group)
+{
+  LmMessageNode *query, *y;
+  LmMessage *iq;
+  char *cleanjid;
+
+  if (!lm_connection_is_authenticated(lconnection)) return;
+
+  cleanjid = jidtodisp(bjid); // Stripping resource, just in case...
+
+  // We don't check if the jabber user already exists in the roster,
+  // because it allows to re-ask for notification.
+
+  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_SET);
+  query = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_ROSTER);
+  y = lm_message_node_add_child(query, "item", NULL);
+  lm_message_node_set_attribute(y, "jid", cleanjid);
+
+  if (name)
+    lm_message_node_set_attribute(y, "name", name);
+
+  if (group)
+    lm_message_node_add_child(y, "group", group);
+
+  lm_connection_send(lconnection, iq, NULL);
+  lm_message_unref(iq);
+
+  xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_SUBSCRIBE);
+
+  roster_add_user(cleanjid, name, group, ROSTER_TYPE_USER, sub_pending, -1);
+  g_free(cleanjid);
+  buddylist_build();
+
+  update_roster = TRUE;
+}
+
+void xmpp_updatebuddy(const char *bjid, const char *name, const char *group)
+{
+  LmMessage *iq;
+  LmMessageNode *x;
+  char *cleanjid;
+
+  if (!lm_connection_is_authenticated(lconnection)) return;
+
+  // XXX We should check name's and group's correctness
+
+  cleanjid = jidtodisp(bjid); // Stripping resource, just in case...
+
+  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_SET);
+  x = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(x, "xmlns", NS_ROSTER);
+  x = lm_message_node_add_child(x, "item", NULL);
+  lm_message_node_set_attributes(x,
+                                 "jid", cleanjid,
+                                 "name", name,
+                                 NULL);
+
+  if (group)
+    lm_message_node_add_child(x, "group", group);
+
+  lm_connection_send(lconnection, iq, NULL);
+  lm_message_unref(iq);
+  g_free(cleanjid);
+}
+
+void xmpp_delbuddy(const char *bjid)
+{
+  LmMessageNode *y, *z;
+  LmMessage *iq;
+  char *cleanjid;
+
+  if (!lm_connection_is_authenticated(lconnection)) return;
+
+  cleanjid = jidtodisp(bjid); // Stripping resource, just in case...
+
+  // If the current buddy is an agent, unsubscribe from it
+  if (roster_gettype(cleanjid) == ROSTER_TYPE_AGENT) {
+    scr_LogPrint(LPRINT_LOGNORM, "Unregistering from the %s agent", cleanjid);
+
+    iq = lm_message_new_with_sub_type(cleanjid, LM_MESSAGE_TYPE_IQ,
+                                      LM_MESSAGE_SUB_TYPE_SET);
+    y = lm_message_node_add_child(iq->node, "query", NULL);
+    lm_message_node_set_attribute(y, "xmlns", NS_REGISTER);
+    lm_message_node_add_child(y, "remove", NULL);
+    lm_connection_send(lconnection, iq, NULL);
+    lm_message_unref(iq);
+  }
+
+  // Cancel the subscriptions
+  xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); //cancel "from"
+  xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE);  //cancel "to"
+
+  // Ask for removal from roster
+  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_SET);
+
+  y = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(y, "xmlns", NS_ROSTER);
+  z = lm_message_node_add_child(y, "item", NULL);
+  lm_message_node_set_attributes(z,
+                                 "jid", cleanjid,
+                                 "subscription", "remove",
+                                 NULL);
+  lm_connection_send(lconnection, iq, NULL);
+  lm_message_unref(iq);
+
+  roster_del_user(cleanjid);
+  g_free(cleanjid);
+  buddylist_build();
+
+  update_roster = TRUE;
+}
+
+void xmpp_request(const char *fjid, enum iqreq_type reqtype)
+{
+  GSList *resources, *p_res;
+  GSList *roster_elt;
+  const char *strreqtype, *xmlns;
+
+  if (reqtype == iqreq_version) {
+    xmlns = NS_VERSION;
+    strreqtype = "version";
+  } else if (reqtype == iqreq_time) {
+    xmlns = NS_TIME;
+    strreqtype = "time";
+  } else if (reqtype == iqreq_last) {
+    xmlns = NS_LAST;
+    strreqtype = "last";
+  } else if (reqtype == iqreq_vcard) {
+    xmlns = NS_VCARD;
+    strreqtype = "vCard";
+    // Special case
+  } else
+    return;
+
+  if (strchr(fjid, JID_RESOURCE_SEPARATOR)) {
+    // This is a full JID
+    xmpp_iq_request(fjid, xmlns);
+    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid);
+    return;
+  }
+
+  // The resource has not been specified
+  roster_elt = roster_find(fjid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
+  if (!roster_elt) {
+    scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid);
+    xmpp_iq_request(fjid, xmlns); // Let's send a request anyway...
+    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid);
+    return;
+  }
+
+  // Send a request to each resource
+  resources = buddy_getresources(roster_elt->data);
+  if (!resources) {
+    scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid);
+    xmpp_iq_request(fjid, xmlns); // Let's send a request anyway...
+    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid);
+  }
+  for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
+    gchar *fulljid;
+    fulljid = g_strdup_printf("%s/%s", fjid, (char*)p_res->data);
+    xmpp_iq_request(fulljid, xmlns);
+    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fulljid);
+    g_free(fulljid);
+    g_free(p_res->data);
+  }
+  g_slist_free(resources);
+}
+
+static LmHandlerResult cb_xep184(LmMessageHandler *h, LmConnection *c,
+                                 LmMessage *m, gpointer user_data)
+{
+  char *from = jidtodisp(lm_message_get_from(m));
+  scr_RemoveReceiptFlag(from, h);
+  g_free(from);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+//  xmpp_send_msg(jid, text, type, subject,
+//                otrinject, *encrypted, type_overwrite)
+// When encrypted is not NULL, the function set *encrypted to 1 if the
+// message has been PGP-encrypted.  If encryption enforcement is set and
+// encryption fails, *encrypted is set to -1.
+void xmpp_send_msg(const char *fjid, const char *text, int type,
+                   const char *subject, gboolean otrinject, gint *encrypted,
+                   LmMessageSubType type_overwrite, gpointer *xep184)
+{
+  LmMessage *x;
+  LmMessageSubType subtype;
+#ifdef HAVE_LIBOTR
+  int otr_msg = 0;
+#endif
+#if defined HAVE_GPGME || defined JEP0022 || defined JEP0085
+  char *rname, *barejid;
+  GSList *sl_buddy;
+#endif
+#if defined JEP0022 || defined JEP0085
+  LmMessageNode *event;
+  guint use_jep85 = 0;
+  struct jep0085 *jep85 = NULL;
+#endif
+  gchar *enc = NULL;
+
+  if (encrypted)
+    *encrypted = 0;
+
+  if (!lm_connection_is_authenticated(lconnection))
+    return;
+
+  if (!text && type == ROSTER_TYPE_USER)
+    return;
+
+  if (type_overwrite != LM_MESSAGE_SUB_TYPE_NOT_SET)
+    subtype = type_overwrite;
+  else {
+    if (type == ROSTER_TYPE_ROOM)
+      subtype = LM_MESSAGE_SUB_TYPE_GROUPCHAT;
+    else
+      subtype = LM_MESSAGE_SUB_TYPE_CHAT;
+  }
+
+#if defined HAVE_GPGME || defined HAVE_LIBOTR || \
+    defined JEP0022 || defined JEP0085
+  rname = strchr(fjid, JID_RESOURCE_SEPARATOR);
+  barejid = jidtodisp(fjid);
+  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
+
+  // If we can get a resource name, we use it.  Else we use NULL,
+  // which hopefully will give us the most likely resource.
+  if (rname)
+    rname++;
+
+#ifdef HAVE_LIBOTR
+  if (otr_enabled() && !otrinject) {
+    if (type == ROSTER_TYPE_USER) {
+      otr_msg = otr_send((char **)&text, barejid);
+      if (!text) {
+        g_free(barejid);
+        if (encrypted)
+          *encrypted = -1;
+        return;
+      }
+    }
+    if (otr_msg && encrypted)
+      *encrypted = ENCRYPTED_OTR;
+  }
+#endif
+
+#ifdef HAVE_GPGME
+  if (type == ROSTER_TYPE_USER && sl_buddy && gpg_enabled()) {
+    if (!settings_pgp_getdisabled(barejid)) { // not disabled for this contact?
+      guint force;
+      struct pgp_data *res_pgpdata;
+      force = settings_pgp_getforce(barejid);
+      res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname);
+      if (force || (res_pgpdata && res_pgpdata->sign_keyid)) {
+        /* Remote client has PGP support (we have a signature)
+         * OR encryption is enforced (force = TRUE).
+         * If the contact has a specific KeyId, we'll use it;
+         * if not, we'll use the key used for the signature.
+         * Both keys should match, in theory (cf. XEP-0027). */
+        const char *key;
+        key = settings_pgp_getkeyid(barejid);
+        if (!key && res_pgpdata)
+          key = res_pgpdata->sign_keyid;
+        if (key)
+          enc = gpg_encrypt(text, key);
+        if (!enc && force) {
+          if (encrypted)
+            *encrypted = -1;
+          g_free(barejid);
+          return;
+        }
+      }
+    }
+  }
+#endif // HAVE_GPGME
+
+  g_free(barejid);
+#endif // HAVE_GPGME || defined JEP0022 || defined JEP0085
+
+  x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, subtype);
+  lm_message_node_add_child(x->node, "body",
+                            enc ? "This message is PGP-encrypted." : text);
+
+  if (subject)
+    lm_message_node_add_child(x->node, "subject", subject);
+
+  if (enc) {
+    LmMessageNode *y;
+    y = lm_message_node_add_child(x->node, "x", enc);
+    lm_message_node_set_attribute(y, "xmlns", NS_ENCRYPTED);
+    if (encrypted)
+      *encrypted = ENCRYPTED_PGP;
+    g_free(enc);
+  }
+
+  //XEP-0184: Message Receipts
+  if (sl_buddy && rname && xep184 &&
+      caps_has_feature(buddy_resource_getcaps(sl_buddy->data, rname),
+                       NS_RECEIPTS)) {
+    lm_message_node_set_attribute
+            (lm_message_node_add_child(x->node, "request", NULL),
+             "xmlns", NS_RECEIPTS);
+    *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL);
+  }
+
+#if defined JEP0022 || defined JEP0085
+  // If typing notifications are disabled, we can skip all this stuff...
+  if (chatstates_disabled || type == ROSTER_TYPE_ROOM)
+    goto xmpp_send_msg_no_chatstates;
+
+  if (sl_buddy)
+    jep85 = buddy_resource_jep85(sl_buddy->data, rname);
+#endif
+
+#ifdef JEP0085
+  /* JEP-0085 5.1
+   * "Until receiving a reply to the initial content message (or a standalone
+   * notification) from the Contact, the User MUST NOT send subsequent chat
+   * state notifications to the Contact."
+   * In our implementation support is initially "unknown", then it's "probed"
+   * and can become "ok".
+   */
+  if (jep85 && (jep85->support == CHATSTATES_SUPPORT_OK ||
+                jep85->support == CHATSTATES_SUPPORT_UNKNOWN)) {
+    event = lm_message_node_add_child(x->node, "active", NULL);
+    lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES);
+    if (jep85->support == CHATSTATES_SUPPORT_UNKNOWN)
+      jep85->support = CHATSTATES_SUPPORT_PROBED;
+    else
+      use_jep85 = 1;
+    jep85->last_state_sent = ROSTER_EVENT_ACTIVE;
+  }
+#endif
+#ifdef JEP0022
+  /* JEP-22
+   * If the Contact supports JEP-0085, we do not use JEP-0022.
+   * If not, we try to fall back to JEP-0022.
+   */
+  if (!use_jep85) {
+    struct jep0022 *jep22 = NULL;
+    event = lm_message_node_add_child(x->node, "x", NULL);
+    lm_message_node_set_attribute(event, "xmlns", NS_EVENT);
+    lm_message_node_add_child(event, "composing", NULL);
+
+    if (sl_buddy)
+      jep22 = buddy_resource_jep22(sl_buddy->data, rname);
+    if (jep22)
+      jep22->last_state_sent = ROSTER_EVENT_ACTIVE;
+
+    // An id is mandatory when using JEP-0022.
+    if (text || subject) {
+      const gchar *msgid = lm_message_get_id(x);
+      // Let's update last_msgid_sent
+      if (jep22) {
+        g_free(jep22->last_msgid_sent);
+        jep22->last_msgid_sent = g_strdup(msgid);
+      }
+    }
+  }
+#endif
+
+xmpp_send_msg_no_chatstates:
+  if (mystatus != invisible)
+    update_last_use();
+  if (xep184 && *xep184) {
+    lm_connection_send_with_reply(lconnection, x, *xep184, NULL);
+    lm_message_handler_unref(*xep184);
+  } else
+    lm_connection_send(lconnection, x, NULL);
+  lm_message_unref(x);
+}
+
+#ifdef JEP0085
+//  xmpp_send_jep85_chatstate()
+// Send a JEP-85 chatstate.
+static void xmpp_send_jep85_chatstate(const char *bjid, const char *resname,
+                                      guint state)
+{
+  LmMessage *m;
+  LmMessageNode *event;
+  GSList *sl_buddy;
+  const char *chattag;
+  char *rjid, *fjid = NULL;
+  struct jep0085 *jep85 = NULL;
+
+  if (!lm_connection_is_authenticated(lconnection)) return;
+
+  sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER);
+
+  // If we have a resource name, we use it.  Else we use NULL,
+  // which hopefully will give us the most likely resource.
+  if (sl_buddy)
+    jep85 = buddy_resource_jep85(sl_buddy->data, resname);
+
+  if (!jep85 || (jep85->support != CHATSTATES_SUPPORT_OK))
+    return;
+
+  if (state == jep85->last_state_sent)
+    return;
+
+  if (state == ROSTER_EVENT_ACTIVE)
+    chattag = "active";
+  else if (state == ROSTER_EVENT_COMPOSING)
+    chattag = "composing";
+  else if (state == ROSTER_EVENT_PAUSED)
+    chattag = "paused";
+  else {
+    scr_LogPrint(LPRINT_LOGNORM, "Error: unsupported JEP-85 state (%d)", state);
+    return;
+  }
+
+  jep85->last_state_sent = state;
+
+  if (resname)
+    fjid = g_strdup_printf("%s/%s", bjid, resname);
+
+  rjid = resname ? fjid : (char*)bjid;
+  m = lm_message_new_with_sub_type(rjid, LM_MESSAGE_TYPE_MESSAGE,
+                                   LM_MESSAGE_SUB_TYPE_CHAT);
+
+  event = lm_message_node_add_child(m->node, chattag, NULL);
+  lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES);
+
+  lm_connection_send(lconnection, m, NULL);
+  lm_message_unref(m);
+
+  g_free(fjid);
+}
+#endif
+
+#ifdef JEP0022
+//  xmpp_send_jep22_event()
+// Send a JEP-22 message event (delivered, composing...).
+static void xmpp_send_jep22_event(const char *fjid, guint type)
+{
+  LmMessage *x;
+  LmMessageNode *event;
+  const char *msgid;
+  char *rname, *barejid;
+  GSList *sl_buddy;
+  struct jep0022 *jep22 = NULL;
+  guint jep22_state;
+
+  if (!lm_connection_is_authenticated(lconnection)) return;
+
+  rname = strchr(fjid, JID_RESOURCE_SEPARATOR);
+  barejid = jidtodisp(fjid);
+  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
+  g_free(barejid);
+
+  // If we can get a resource name, we use it.  Else we use NULL,
+  // which hopefully will give us the most likely resource.
+  if (rname)
+    rname++;
+  if (sl_buddy)
+    jep22 = buddy_resource_jep22(sl_buddy->data, rname);
+
+  if (!jep22)
+    return; // XXX Maybe we could try harder (other resources?)
+
+  msgid = jep22->last_msgid_rcvd;
+
+  // For composing events (composing, active, inactive, paused...),
+  // JEP22 only has 2 states; we'll use composing and active.
+  if (type == ROSTER_EVENT_COMPOSING)
+    jep22_state = ROSTER_EVENT_COMPOSING;
+  else if (type == ROSTER_EVENT_ACTIVE ||
+           type == ROSTER_EVENT_PAUSED)
+    jep22_state = ROSTER_EVENT_ACTIVE;
+  else
+    jep22_state = 0; // ROSTER_EVENT_NONE
+
+  if (jep22_state) {
+    // Do not re-send a same event
+    if (jep22_state == jep22->last_state_sent)
+      return;
+    jep22->last_state_sent = jep22_state;
+  }
+
+  x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE,
+                                   LM_MESSAGE_SUB_TYPE_CHAT);
+
+  event = lm_message_node_add_child(x->node, "x", NULL);
+  lm_message_node_set_attribute(event, "xmlns", NS_EVENT);
+  if (type == ROSTER_EVENT_DELIVERED)
+    lm_message_node_add_child(event, "delivered", NULL);
+  else if (type == ROSTER_EVENT_COMPOSING)
+    lm_message_node_add_child(event, "composing", NULL);
+  lm_message_node_add_child(event, "id", msgid);
+
+  lm_connection_send(lconnection, x, NULL);
+  lm_message_unref(x);
+}
+#endif
+
+//  xmpp_send_chatstate(buddy, state)
+// Send a chatstate or event (JEP-22/85) according to the buddy's capabilities.
+// The message is sent to one of the resources with the highest priority.
+#if defined JEP0022 || defined JEP0085
+void xmpp_send_chatstate(gpointer buddy, guint chatstate)
+{
+  const char *bjid;
+#ifdef JEP0085
+  GSList *resources, *p_res, *p_next;
+  struct jep0085 *jep85 = NULL;
+#endif
+#ifdef JEP0022
+  struct jep0022 *jep22;
+#endif
+
+  bjid = buddy_getjid(buddy);
+  if (!bjid) return;
+
+#ifdef JEP0085
+  /* Send the chatstate to the last resource (which should have the highest
+     priority).
+     If chatstate is "active", send an "active" state to all resources
+     which do not curently have this state.
+   */
+  resources = buddy_getresources(buddy);
+  for (p_res = resources ; p_res ; p_res = p_next) {
+    p_next = g_slist_next(p_res);
+    jep85 = buddy_resource_jep85(buddy, p_res->data);
+    if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) {
+      // If p_next is NULL, this is the highest (prio) resource, i.e.
+      // the one we are probably writing to.
+      if (!p_next || (jep85->last_state_sent != ROSTER_EVENT_ACTIVE &&
+                      chatstate == ROSTER_EVENT_ACTIVE))
+        xmpp_send_jep85_chatstate(bjid, p_res->data, chatstate);
+    }
+    g_free(p_res->data);
+  }
+  g_slist_free(resources);
+  // If the last resource had chatstates support when can return now,
+  // we don't want to send a JEP22 event.
+  if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK)
+    return;
+#endif
+#ifdef JEP0022
+  jep22 = buddy_resource_jep22(buddy, NULL);
+  if (jep22 && jep22->support == CHATSTATES_SUPPORT_OK) {
+    xmpp_send_jep22_event(bjid, chatstate);
+  }
+#endif
+}
+#endif
+
+
+//  chatstates_reset_probed(fulljid)
+// If the JEP has been probed for this contact, set it back to unknown so
+// that we probe it again.  The parameter must be a full jid (w/ resource).
+#if defined JEP0022 || defined JEP0085
+static void chatstates_reset_probed(const char *fulljid)
+{
+  char *rname, *barejid;
+  GSList *sl_buddy;
+  struct jep0085 *jep85;
+  struct jep0022 *jep22;
+
+  rname = strchr(fulljid, JID_RESOURCE_SEPARATOR);
+  if (!rname++)
+    return;
+
+  barejid = jidtodisp(fulljid);
+  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
+  g_free(barejid);
+
+  if (!sl_buddy)
+    return;
+
+  jep85 = buddy_resource_jep85(sl_buddy->data, rname);
+  jep22 = buddy_resource_jep22(sl_buddy->data, rname);
+
+  if (jep85 && jep85->support == CHATSTATES_SUPPORT_PROBED)
+    jep85->support = CHATSTATES_SUPPORT_UNKNOWN;
+  if (jep22 && jep22->support == CHATSTATES_SUPPORT_PROBED)
+    jep22->support = CHATSTATES_SUPPORT_UNKNOWN;
+}
+#endif
+
+#ifdef HAVE_GPGME
+//  keys_mismatch(key, expectedkey)
+// Return TRUE if both keys are non-null and "expectedkey" doesn't match
+// the end of "key".
+// If one of the keys is null, return FALSE.
+// If expectedkey is less than 8 bytes long, return TRUE.
+//
+// Example: keys_mismatch("C9940A9BB0B92210", "B0B92210") will return FALSE.
+static bool keys_mismatch(const char *key, const char *expectedkey)
+{
+  int lk, lek;
+
+  if (!expectedkey || !key)
+    return FALSE;
+
+  lk = strlen(key);
+  lek = strlen(expectedkey);
+
+  // If the expectedkey is less than 8 bytes long, this is probably a
+  // user mistake so we consider it's a mismatch.
+  if (lek < 8)
+    return TRUE;
+
+  if (lek < lk)
+    key += lk - lek;
+
+  return strcasecmp(key, expectedkey);
+}
+#endif
+
+//  check_signature(barejid, resourcename, xmldata, text)
+// Verify the signature (in xmldata) of "text" for the contact
+// barejid/resourcename.
+// xmldata is the 'jabber:x:signed' stanza.
+// If the key id is found, the contact's PGP data are updated.
+static void check_signature(const char *barejid, const char *rname,
+                            LmMessageNode *node, const char *text)
+{
+#ifdef HAVE_GPGME
+  const char *p, *key;
+  GSList *sl_buddy;
+  struct pgp_data *res_pgpdata;
+  gpgme_sigsum_t sigsum;
+
+  // All parameters must be valid
+  if (!(node && barejid && rname && text))
+    return;
+
+  if (!gpg_enabled())
+    return;
+
+  // Get the resource PGP data structure
+  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
+  if (!sl_buddy)
+    return;
+  res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname);
+  if (!res_pgpdata)
+    return;
+
+  if (!node->name || strcmp(node->name, "x")) //XXX: probably useless
+    return; // We expect "<x xmlns='jabber:x:signed'>"
+
+  // Get signature
+  p = lm_message_node_get_value(node);
+  if (!p)
+    return;
+
+  key = gpg_verify(p, text, &sigsum);
+  if (key) {
+    const char *expectedkey;
+    char *buf;
+    g_free(res_pgpdata->sign_keyid);
+    res_pgpdata->sign_keyid = (char *)key;
+    res_pgpdata->last_sigsum = sigsum;
+    if (sigsum & GPGME_SIGSUM_RED) {
+      buf = g_strdup_printf("Bad signature from <%s/%s>", barejid, rname);
+      scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0);
+      scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+      g_free(buf);
+    }
+    // Verify that the key id is the one we expect.
+    expectedkey = settings_pgp_getkeyid(barejid);
+    if (keys_mismatch(key, expectedkey)) {
+      buf = g_strdup_printf("Warning: The KeyId from <%s/%s> doesn't match "
+                            "the key you set up", barejid, rname);
+      scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0);
+      scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+      g_free(buf);
+    }
+  }
+#endif
+}
+
+static LmSSLResponse ssl_cb(LmSSL *ssl, LmSSLStatus status, gpointer ud)
+{
+  scr_LogPrint(LPRINT_LOGNORM, "SSL status:%d", status);
+
+  switch (status) {
+  case LM_SSL_STATUS_NO_CERT_FOUND:
+    scr_LogPrint(LPRINT_LOGNORM, "No certificate found!");
+    break;
+  case LM_SSL_STATUS_UNTRUSTED_CERT:
+    scr_LogPrint(LPRINT_LOGNORM, "Certificate is not trusted!");
+    break;
+  case LM_SSL_STATUS_CERT_EXPIRED:
+    scr_LogPrint(LPRINT_LOGNORM, "Certificate has expired!");
+    break;
+  case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
+    scr_LogPrint(LPRINT_LOGNORM, "Certificate has not been activated!");
+    break;
+  case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Certificate hostname does not match expected hostname!");
+    break;
+  case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH: {
+    char fpr[49];
+    fingerprint_to_hex((const unsigned char*)lm_ssl_get_fingerprint(ssl),
+                       fpr);
+    scr_LogPrint(LPRINT_LOGNORM,
+              "Certificate fingerprint does not match expected fingerprint!");
+    scr_LogPrint(LPRINT_LOGNORM, "Remote fingerprint: %s", fpr);
+
+    scr_LogPrint(LPRINT_LOGNORM, "Expected fingerprint: %s",
+                 settings_opt_get("ssl_fingerprint"));
+
+    return LM_SSL_RESPONSE_STOP;
+    break;
+  }
+  case LM_SSL_STATUS_GENERIC_ERROR:
+    scr_LogPrint(LPRINT_LOGNORM, "Generic SSL error!");
+    break;
+  }
+
+  if (settings_opt_get_int("ssl_ignore_checks"))
+    return LM_SSL_RESPONSE_CONTINUE;
+  return LM_SSL_RESPONSE_STOP;
+}
+
+static void connection_auth_cb(LmConnection *connection, gboolean success,
+                               gpointer user_data)
+{
+  if (success) {
+    LmMessage *m;
+
+    m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE,
+                                     LM_MESSAGE_SUB_TYPE_AVAILABLE);
+    lm_connection_send(connection, m, NULL);
+
+    lm_message_unref(m);
+    xmpp_setprevstatus();
+    xmpp_iq_request(NULL, NS_ROSTER);
+    xmpp_request_storage("storage:bookmarks");
+    xmpp_request_storage("storage:rosternotes");
+
+    AutoConnection = TRUE;
+  } else
+    scr_LogPrint(LPRINT_LOGNORM, "Authentication failed");
+}
+
+gboolean xmpp_reconnect()
+{
+  if (!lm_connection_is_authenticated(lconnection))
+    xmpp_connect();
+  return FALSE;
+}
+
+static void _try_to_reconnect(void)
+{
+  if (AutoConnection)
+    g_timeout_add_seconds(RECONNECTION_TIMEOUT, xmpp_reconnect, NULL);
+}
+
+static void connection_open_cb(LmConnection *connection, gboolean success,
+                               gpointer user_data)
+{
+  GError *error = NULL;
+
+  if (success) {
+    const char *password, *resource;
+    char *username;
+    username   = jid_get_username(settings_opt_get("jid"));
+    password   = settings_opt_get("password");
+    resource   = strchr(lm_connection_get_jid(connection),
+                        JID_RESOURCE_SEPARATOR);
+    if (resource)
+      resource++;
+
+    if (!lm_connection_authenticate(lconnection, username, password, resource,
+                                    connection_auth_cb, NULL, FALSE, &error)) {
+      scr_LogPrint(LPRINT_LOGNORM, "Failed to authenticate: %s\n",
+                   error->message);
+      g_error_free (error);
+      _try_to_reconnect();
+    }
+    g_free(username);
+  } else {
+    scr_LogPrint(LPRINT_LOGNORM, "There was an error while connecting.");
+    _try_to_reconnect();
+  }
+}
+
+static void connection_close_cb(LmConnection *connection,
+                                LmDisconnectReason reason,
+                                gpointer user_data)
+{
+  const char *str;
+
+  switch (reason) {
+  case LM_DISCONNECT_REASON_OK:
+          str = "LM_DISCONNECT_REASON_OK";
+          break;
+  case LM_DISCONNECT_REASON_PING_TIME_OUT:
+          str = "LM_DISCONNECT_REASON_PING_TIME_OUT";
+          break;
+  case LM_DISCONNECT_REASON_HUP:
+          str = "LM_DISCONNECT_REASON_HUP";
+          break;
+  case LM_DISCONNECT_REASON_ERROR:
+          str = "LM_DISCONNECT_REASON_ERROR";
+          break;
+  case LM_DISCONNECT_REASON_UNKNOWN:
+  default:
+          str = "LM_DISCONNECT_REASON_UNKNOWN";
+          break;
+  }
+
+  if (reason != LM_DISCONNECT_REASON_OK)
+    _try_to_reconnect();
+
+  // Free bookmarks
+  if (bookmarks)
+    lm_message_node_unref(bookmarks);
+  bookmarks = NULL;
+  // Free roster
+  roster_free();
+  if (rosternotes)
+    lm_message_node_unref(rosternotes);
+  rosternotes = NULL;
+  // Update display
+  update_roster = TRUE;
+  scr_UpdateBuddyWindow();
+
+  scr_LogPrint(LPRINT_NORMAL, "Disconnected, reason:%d->'%s'\n", reason, str);
+}
+
+static void handle_state_events(const char *from, LmMessageNode *node)
+{
+#if defined JEP0022 || defined JEP0085
+  LmMessageNode *state_ns = NULL;
+  const char *body;
+  char *rname, *bjid;
+  GSList *sl_buddy;
+  guint events;
+  struct jep0022 *jep22 = NULL;
+  struct jep0085 *jep85 = NULL;
+  enum {
+    JEP_none,
+    JEP_85,
+    JEP_22
+  } which_jep = JEP_none;
+
+  rname = strchr(from, JID_RESOURCE_SEPARATOR);
+  if (rname)
+    ++rname;
+  else
+    rname = (char *)from + strlen(from);
+  bjid  = jidtodisp(from);
+  sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER);
+  g_free(bjid);
+
+  /* XXX Actually that's wrong, since it filters out server "offline"
+     messages (for JEP-0022).  This JEP is (almost) deprecated so
+     we don't really care. */
+  if (!sl_buddy) {
+    return;
+  }
+
+  /* Let's see chich JEP the contact uses.  If possible, we'll use
+     JEP-85, if not we'll look for JEP-22 support. */
+  events = buddy_resource_getevents(sl_buddy->data, rname);
+
+  jep85 = buddy_resource_jep85(sl_buddy->data, rname);
+  if (jep85) {
+    state_ns = lm_message_node_find_xmlns(node, NS_CHATSTATES);
+    if (state_ns)
+      which_jep = JEP_85;
+  }
+
+  if (which_jep != JEP_85) { /* Fall back to JEP-0022 */
+    jep22 = buddy_resource_jep22(sl_buddy->data, rname);
+    if (jep22) {
+      state_ns = lm_message_node_find_xmlns(node, NS_EVENT);
+      if (state_ns)
+        which_jep = JEP_22;
+    }
+  }
+
+  if (!which_jep) { /* Sender does not use chat states */
+    return;
+  }
+
+  body = lm_message_node_get_child_value(node, "body");
+
+  if (which_jep == JEP_85) { /* JEP-0085 */
+    jep85->support = CHATSTATES_SUPPORT_OK;
+
+    if (!strcmp(state_ns->name, "composing")) {
+      jep85->last_state_rcvd = ROSTER_EVENT_COMPOSING;
+    } else if (!strcmp(state_ns->name, "active")) {
+      jep85->last_state_rcvd = ROSTER_EVENT_ACTIVE;
+    } else if (!strcmp(state_ns->name, "paused")) {
+      jep85->last_state_rcvd = ROSTER_EVENT_PAUSED;
+    } else if (!strcmp(state_ns->name, "inactive")) {
+      jep85->last_state_rcvd = ROSTER_EVENT_INACTIVE;
+    } else if (!strcmp(state_ns->name, "gone")) {
+      jep85->last_state_rcvd = ROSTER_EVENT_GONE;
+    }
+    events = jep85->last_state_rcvd;
+  } else {              /* JEP-0022 */
+#ifdef JEP0022
+    const char *msgid;
+    jep22->support = CHATSTATES_SUPPORT_OK;
+    jep22->last_state_rcvd = ROSTER_EVENT_NONE;
+
+    msgid = lm_message_node_get_attribute(node, "id");
+
+    if (lm_message_node_get_child(state_ns, "composing")) {
+      // Clear composing if the message contains a body
+      if (body)
+        events &= ~ROSTER_EVENT_COMPOSING;
+      else
+        events |= ROSTER_EVENT_COMPOSING;
+      jep22->last_state_rcvd |= ROSTER_EVENT_COMPOSING;
+
+    } else {
+      events &= ~ROSTER_EVENT_COMPOSING;
+    }
+
+    // Cache the message id
+    g_free(jep22->last_msgid_rcvd);
+    if (msgid)
+      jep22->last_msgid_rcvd = g_strdup(msgid);
+    else
+      jep22->last_msgid_rcvd = NULL;
+
+    if (lm_message_node_get_child(state_ns, "delivered")) {
+      jep22->last_state_rcvd |= ROSTER_EVENT_DELIVERED;
+
+      // Do we have to send back an ACK?
+      if (body)
+        xmpp_send_jep22_event(from, ROSTER_EVENT_DELIVERED);
+    }
+#endif
+  }
+
+  buddy_resource_setevents(sl_buddy->data, rname, events);
+
+  update_roster = TRUE;
+#endif
+}
+
+static void gotmessage(LmMessageSubType type, const char *from,
+                       const char *body, const char *enc, const char *subject,
+                       time_t timestamp, LmMessageNode *node_signed)
+{
+  char *bjid;
+  const char *rname, *s;
+  char *decrypted_pgp = NULL;
+  char *decrypted_otr = NULL;
+  int otr_msg = 0, free_msg = 0;
+
+  bjid = jidtodisp(from);
+
+  rname = strchr(from, JID_RESOURCE_SEPARATOR);
+  if (rname) rname++;
+
+#ifdef HAVE_GPGME
+  if (enc && gpg_enabled()) {
+    decrypted_pgp = gpg_decrypt(enc);
+    if (decrypted_pgp) {
+      body = decrypted_pgp;
+    }
+  }
+  // Check signature of an unencrypted message
+  if (node_signed && gpg_enabled())
+    check_signature(bjid, rname, node_signed, decrypted_pgp);
+#endif
+
+#ifdef HAVE_LIBOTR
+  if (otr_enabled()) {
+    decrypted_otr = (char*)body;
+    otr_msg = otr_receive(&decrypted_otr, bjid, &free_msg);
+    if (!decrypted_otr) {
+      goto gotmessage_return;
+    }
+    body = decrypted_otr;
+  }
+#endif
+
+  // Check for unexpected groupchat messages
+  // If we receive a groupchat message from a room we're not a member of,
+  // this is probably a server issue and the best we can do is to send
+  // a type unavailable.
+  if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT && !roster_getnickname(bjid)) {
+    // It shouldn't happen, probably a server issue
+    GSList *room_elt;
+    char *mbuf;
+
+    mbuf = g_strdup_printf("Unexpected groupchat packet!");
+    scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
+    scr_WriteIncomingMessage(bjid, mbuf, 0, HBB_PREFIX_INFO, 0);
+    g_free(mbuf);
+
+    // Send back an unavailable packet
+    xmpp_setstatus(offline, bjid, "", TRUE);
+
+    // MUC
+    // Make sure this is a room (it can be a conversion user->room)
+    room_elt = roster_find(bjid, jidsearch, 0);
+    if (!room_elt) {
+      roster_add_user(bjid, NULL, NULL, ROSTER_TYPE_ROOM, sub_none, -1);
+    } else {
+      buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
+    }
+
+    buddylist_build();
+    scr_DrawRoster();
+    goto gotmessage_return;
+  }
+
+  // We don't call the message_in hook if 'block_unsubscribed' is true and
+  // this is a regular message from an unsubscribed user.
+  // System messages (from our server) are allowed.
+  if ((!settings_opt_get_int("block_unsubscribed") ||
+       (roster_getsubscription(bjid) & sub_from) ||
+       (type == LM_MESSAGE_SUB_TYPE_CHAT)) ||
+      ((s = settings_opt_get("server")) != NULL && !strcasecmp(bjid, s))) {
+    gchar *fullbody = NULL;
+    guint encrypted;
+
+    if (decrypted_pgp)
+      encrypted = ENCRYPTED_PGP;
+    else if (otr_msg)
+      encrypted = ENCRYPTED_OTR;
+    else
+      encrypted = 0;
+
+    if (subject) {
+      if (body)
+        fullbody = g_strdup_printf("[%s]\n%s", subject, body);
+      else
+        fullbody = g_strdup_printf("[%s]\n", subject);
+      body = fullbody;
+    }
+    hk_message_in(bjid, rname, timestamp, body, type, encrypted);
+    g_free(fullbody);
+  } else {
+    scr_LogPrint(LPRINT_LOGNORM, "Blocked a message from <%s>", bjid);
+  }
+
+gotmessage_return:
+  // Clean up and exit
+  g_free(bjid);
+  g_free(decrypted_pgp);
+  if (free_msg)
+    g_free(decrypted_otr);
+}
+
+
+static LmHandlerResult handle_messages(LmMessageHandler *handler,
+                                       LmConnection *connection,
+                                       LmMessage *m, gpointer user_data)
+{
+  const char *p, *from=lm_message_get_from(m);
+  char *r, *s;
+  LmMessageNode *x;
+  const char *body = NULL;
+  const char *enc = NULL;
+  const char *subject = NULL;
+  time_t timestamp = 0L;
+  LmMessageSubType mstype;
+
+  mstype = lm_message_get_sub_type(m);
+
+  body = lm_message_node_get_child_value(m->node, "body");
+
+  x = lm_message_node_find_xmlns(m->node, NS_ENCRYPTED);
+  if (x && (p = lm_message_node_get_value(x)) != NULL)
+    enc = p;
+
+  p = lm_message_node_get_child_value(m->node, "subject");
+  if (p != NULL) {
+    if (mstype != LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
+      // Chat message
+      subject = p;
+    } else {                                      // Room topic
+      GSList *roombuddy;
+      gchar *mbuf;
+      const gchar *subj = p;
+      // Get the room (s) and the nickname (r)
+      s = g_strdup(lm_message_get_from(m));
+      r = strchr(s, JID_RESOURCE_SEPARATOR);
+      if (r) *r++ = 0;
+      else   r = s;
+      // Set the new topic
+      roombuddy = roster_find(s, jidsearch, 0);
+      if (roombuddy)
+        buddy_settopic(roombuddy->data, subj);
+      // Display inside the room window
+      if (r == s) {
+        // No specific resource (this is certainly history)
+        mbuf = g_strdup_printf("The topic has been set to: %s", subj);
+      } else {
+        mbuf = g_strdup_printf("%s has set the topic to: %s", r, subj);
+      }
+      scr_WriteIncomingMessage(s, mbuf, 0,
+                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
+      if (settings_opt_get_int("log_muc_conf"))
+        hlog_write_message(s, 0, -1, mbuf);
+      g_free(s);
+      g_free(mbuf);
+      // The topic is displayed in the chat status line, so refresh now.
+      scr_UpdateChatStatus(TRUE);
+    }
+  }
+
+  // Timestamp?
+  timestamp = lm_message_node_get_timestamp(m->node);
+
+  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
+    x = lm_message_node_get_child(m->node, "error");
+    display_server_error(x);
+#if defined JEP0022 || defined JEP0085
+    // If the JEP85/22 support is probed, set it back to unknown so that
+    // we probe it again.
+    chatstates_reset_probed(from);
+#endif
+  } else {
+    handle_state_events(from, m->node);
+  }
+  if (from && (body || subject))
+    gotmessage(mstype, from, body, enc, subject, timestamp,
+               lm_message_node_find_xmlns(m->node, NS_SIGNED));
+  //report received message if message receipt was requested
+  if (lm_message_node_get_child(m->node, "request")) {
+    LmMessage *rcvd = lm_message_new(from, LM_MESSAGE_TYPE_MESSAGE);
+    lm_message_node_set_attribute(rcvd->node, "id", lm_message_get_id(m));
+    lm_message_node_set_attribute
+            (lm_message_node_add_child(rcvd->node, "received", NULL),
+             "xmlns", NS_RECEIPTS);
+    lm_connection_send(connection, rcvd, NULL);
+    lm_message_unref(rcvd);
+  }
+
+  if (from) {
+    x = lm_message_node_find_xmlns(m->node,
+                                   "http://jabber.org/protocol/muc#user");
+    if (x && !strcmp(x->name, "x"))
+      got_muc_message(from, x);
+  }
+
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult cb_caps(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data)
+{
+  char *ver = user_data;
+  LmMessageSubType mstype = lm_message_get_sub_type(m);
+
+  caps_add(ver);
+  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
+    display_server_error(lm_message_node_get_child(m->node, "error"));
+  } else if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) {
+    LmMessageNode *info;
+    LmMessageNode *query = lm_message_node_get_child(m->node, "query");
+
+    info = lm_message_node_get_child(query, "identity");
+    if (info)
+      caps_set_identity(ver, lm_message_node_get_attribute(info, "category"),
+                        lm_message_node_get_attribute(info, "name"),
+                        lm_message_node_get_attribute(info, "type"));
+    info = lm_message_node_get_child(query, "feature");
+    while (info) {
+      if (!g_strcmp0(info->name, "feature"))
+        caps_add_feature(ver, lm_message_node_get_attribute(info, "var"));
+      info = info->next;
+    }
+  }
+  g_free(ver);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult handle_presence(LmMessageHandler *handler,
+                                       LmConnection *connection,
+                                       LmMessage *m, gpointer user_data)
+{
+  char *r;
+  const char *from, *rname, *p=NULL, *ustmsg=NULL;
+  enum imstatus ust;
+  char bpprio;
+  time_t timestamp = 0L;
+  LmMessageNode *muc_packet, *caps;
+  LmMessageSubType mstype;
+
+  // Check for MUC presence packet
+  muc_packet = lm_message_node_find_xmlns
+          (m->node, "http://jabber.org/protocol/muc#user");
+
+  from = lm_message_get_from(m);
+
+  rname = strchr(from, JID_RESOURCE_SEPARATOR);
+  if (rname) rname++;
+
+  if (settings_opt_get_int("ignore_self_presence")) {
+    const char *self_fjid = lm_connection_get_jid(connection);
+    if (self_fjid && !strcasecmp(self_fjid, from)) {
+      return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Ignoring self presence
+    }
+  }
+
+  r = jidtodisp(from);
+  mstype = lm_message_get_sub_type(m);
+
+  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
+    LmMessageNode *x;
+    scr_LogPrint(LPRINT_LOGNORM, "Error presence packet from <%s>", r);
+    x = lm_message_node_find_child(m->node, "error");
+    display_server_error(x);
+    // Let's check it isn't a nickname conflict.
+    // XXX Note: We should handle the <conflict/> string condition.
+    if ((p = lm_message_node_get_attribute(x, "code")) != NULL) {
+      if (atoi(p) == 409) {
+        // 409 = conflict (nickname is in use or registered by another user)
+        // If we are not inside this room, we should reset the nickname
+        GSList *room_elt = roster_find(r, jidsearch, 0);
+        if (room_elt && !buddy_getinsideroom(room_elt->data))
+          buddy_setnickname(room_elt->data, NULL);
+      }
+    }
+
+    g_free(r);
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+
+  p = lm_message_node_get_child_value(m->node, "priority");
+  if (p && *p) bpprio = (gchar)atoi(p);
+  else         bpprio = 0;
+
+  ust = available;
+
+  p = lm_message_node_get_child_value(m->node, "show");
+  if (p) {
+    if (!strcmp(p, "away"))      ust = away;
+    else if (!strcmp(p, "dnd"))  ust = dontdisturb;
+    else if (!strcmp(p, "xa"))   ust = notavail;
+    else if (!strcmp(p, "chat")) ust = freeforchat;
+  }
+
+  if (mstype == LM_MESSAGE_SUB_TYPE_UNAVAILABLE)
+    ust = offline;
+
+  ustmsg = lm_message_node_get_child_value(m->node, "status");
+
+  // Timestamp?
+  timestamp = lm_message_node_get_timestamp(m->node);
+
+  if (muc_packet) {
+    // This is a MUC presence message
+    handle_muc_presence(from, muc_packet, r, rname,
+                        ust, ustmsg, timestamp, bpprio);
+  } else {
+    // Not a MUC message, so this is a regular buddy...
+    // Call hk_statuschange() if status has changed or if the
+    // status message is different
+    const char *msg;
+    msg = roster_getstatusmsg(r, rname);
+    if ((ust != roster_getstatus(r, rname)) ||
+        (!ustmsg && msg && msg[0]) || (ustmsg && (!msg || strcmp(ustmsg, msg))))
+      hk_statuschange(r, rname, bpprio, timestamp, ust, ustmsg);
+    // Presence signature processing
+    if (!ustmsg)
+      ustmsg = ""; // Some clients omit the <status/> element :-(
+    check_signature(r, rname, lm_message_node_find_xmlns(m->node, NS_SIGNED),
+                    ustmsg);
+  }
+
+  // XEP-0115 Entity Capabilities
+  caps = lm_message_node_find_xmlns(m->node, NS_CAPS);
+  if (caps && ust != offline) {
+    const char *ver = lm_message_node_get_attribute(caps, "ver");
+    GSList *sl_buddy = NULL;
+    if (rname)
+      sl_buddy = roster_find(r, jidsearch, ROSTER_TYPE_USER);
+    // Only cache the caps if the user is on the roster
+    if (sl_buddy && buddy_getonserverflag(sl_buddy->data)) {
+      buddy_resource_setcaps(sl_buddy->data, rname, ver);
+
+      if (!caps_has_hash(ver)) {
+        char *node;
+        LmMessageHandler *handler;
+        LmMessage *iq = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ,
+                                                     LM_MESSAGE_SUB_TYPE_GET);
+        node = g_strdup_printf("%s#%s",
+                               lm_message_node_get_attribute(caps, "node"),
+                               ver);
+        lm_message_node_set_attributes
+                (lm_message_node_add_child(iq->node, "query", NULL),
+                 "xmlns", NS_DISCO_INFO,
+                 "node", node,
+                 NULL);
+        g_free(node);
+        handler = lm_message_handler_new(cb_caps, g_strdup(ver), NULL);
+        lm_connection_send_with_reply(connection, iq, handler, NULL);
+        lm_message_unref(iq);
+        lm_message_handler_unref(handler);
+      }
+    }
+  }
+
+  g_free(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+
+static LmHandlerResult handle_iq(LmMessageHandler *handler,
+                                 LmConnection *connection,
+                                 LmMessage *m, gpointer user_data)
+{
+  int i;
+  guint dbgflg;
+  const char *xmlns = NULL;
+  LmMessageNode *x;
+  LmMessageSubType mstype = lm_message_get_sub_type(m);
+
+  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
+    display_server_error(lm_message_node_get_child(m->node, "error"));
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+  }
+
+  for (x = m->node->children; x; x=x->next) {
+    xmlns = lm_message_node_get_attribute(x, "xmlns");
+    if (xmlns)
+      for (i=0; iq_handlers[i].xmlns; ++i)
+        if (!strcmp(iq_handlers[i].xmlns, xmlns))
+          return iq_handlers[i].handler(NULL, connection, m, user_data);
+    xmlns = NULL;
+  }
+
+  if ((mstype == LM_MESSAGE_SUB_TYPE_SET) ||
+      (mstype == LM_MESSAGE_SUB_TYPE_GET))
+    send_iq_error(connection, m, XMPP_ERROR_NOT_IMPLEMENTED);
+
+  if (mstype == LM_MESSAGE_SUB_TYPE_RESULT)
+    dbgflg = LPRINT_DEBUG;
+  else
+    dbgflg = LPRINT_NORMAL|LPRINT_DEBUG;
+
+  scr_LogPrint(dbgflg, "Unhandled IQ: %s", lm_message_node_to_string(m->node));
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult handle_s10n(LmMessageHandler *handler,
+                                   LmConnection *connection,
+                                   LmMessage *m, gpointer user_data)
+{
+  char *r;
+  char *buf;
+  int newbuddy;
+  const char *from = lm_message_get_from(m);
+  LmMessageSubType mstype;
+
+  r = jidtodisp(from);
+
+  newbuddy = !roster_find(r, jidsearch, 0);
+  mstype = lm_message_get_sub_type(m);
+
+  if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBE) {
+    /* The sender wishes to subscribe to our presence */
+    const char *msg;
+    eviqs *evn;
+
+    msg = lm_message_node_get_child_value(m->node, "status");
+
+    buf = g_strdup_printf("<%s> wants to subscribe to your presence updates",
+                          from);
+    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
+    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+    g_free(buf);
+
+    if (msg) {
+      buf = g_strdup_printf("<%s> said: %s", from, msg);
+      scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
+      replace_nl_with_dots(buf);
+      scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+      g_free(buf);
+    }
+
+    // Create a new event item
+    evn = evs_new(EVS_TYPE_SUBSCRIPTION, EVS_MAX_TIMEOUT);
+    if (evn) {
+      evn->callback = &evscallback_subscription;
+      evn->data = g_strdup(r);
+      evn->desc = g_strdup_printf("<%s> wants to subscribe to your "
+                                  "presence updates", r);
+      buf = g_strdup_printf("Please use /event %s accept|reject", evn->id);
+    } else {
+      buf = g_strdup_printf("Unable to create a new event!");
+    }
+    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
+    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+    g_free(buf);
+  } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE) {
+    /* The sender is unsubscribing from our presence */
+    xmpp_send_s10n(from, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);
+    buf = g_strdup_printf("<%s> is unsubscribing from your "
+                          "presence updates", from);
+    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
+    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+    g_free(buf);
+  } else if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBED) {
+    /* The sender has allowed us to receive their presence */
+    buf = g_strdup_printf("<%s> has allowed you to receive their "
+                          "presence updates", from);
+    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
+    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+    g_free(buf);
+  } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED) {
+    /* The subscription request has been denied or a previously-granted
+       subscription has been cancelled */
+    roster_unsubscribed(from);
+    update_roster = TRUE;
+    buf = g_strdup_printf("<%s> has cancelled your subscription to "
+                          "their presence updates", from);
+    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
+    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+    g_free(buf);
+  } else {
+    g_free(r);
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+
+  if (newbuddy)
+    update_roster = TRUE;
+  g_free(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+//TODO: Use the enum of loudmouth, when it's included in the header...
+typedef enum {
+  LM_LOG_LEVEL_VERBOSE = 1 << (G_LOG_LEVEL_USER_SHIFT),
+  LM_LOG_LEVEL_NET     = 1 << (G_LOG_LEVEL_USER_SHIFT + 1),
+  LM_LOG_LEVEL_PARSER  = 1 << (G_LOG_LEVEL_USER_SHIFT + 2),
+  LM_LOG_LEVEL_SSL     = 1 << (G_LOG_LEVEL_USER_SHIFT + 3),
+  LM_LOG_LEVEL_SASL    = 1 << (G_LOG_LEVEL_USER_SHIFT + 4),
+  LM_LOG_LEVEL_ALL     = (LM_LOG_LEVEL_NET |
+        LM_LOG_LEVEL_VERBOSE |
+        LM_LOG_LEVEL_PARSER |
+        LM_LOG_LEVEL_SSL |
+        LM_LOG_LEVEL_SASL)
+} LmLogLevelFlags;
+
+static void lm_debug_handler (const gchar    *log_domain,
+                              GLogLevelFlags  log_level,
+                              const gchar    *message,
+                              gpointer        user_data)
+{
+  if (message && *message) {
+    char *msg;
+    int mcabber_loglevel = settings_opt_get_int("tracelog_level");
+
+    if (mcabber_loglevel < 2)
+      return;
+
+    if (message[0] == '\n')
+      msg = g_strdup(&message[1]);
+    else
+      msg = g_strdup(message);
+
+    if (msg[strlen(msg)-1] == '\n')
+      msg[strlen(msg)-1] = '\0';
+
+    if (log_level & LM_LOG_LEVEL_VERBOSE) {
+      scr_LogPrint(LPRINT_DEBUG, "LM-VERBOSE: %s", msg);
+    }
+    if (log_level & LM_LOG_LEVEL_NET) {
+      if (mcabber_loglevel > 2)
+        scr_LogPrint(LPRINT_DEBUG, "LM-NET: %s", msg);
+    } else if (log_level & LM_LOG_LEVEL_PARSER) {
+      if (mcabber_loglevel > 3)
+        scr_LogPrint(LPRINT_DEBUG, "LM-PARSER: %s", msg);
+    } else if (log_level & LM_LOG_LEVEL_SASL) {
+      scr_LogPrint(LPRINT_DEBUG, "LM-SASL: %s", msg);
+    } else if (log_level & LM_LOG_LEVEL_SSL) {
+      scr_LogPrint(LPRINT_DEBUG, "LM-SSL: %s", msg);
+    }
+    g_free(msg);
+  }
+}
+
+
+void xmpp_connect(void)
+{
+  const char *userjid, *password, *resource, *servername, *ssl_fpr;
+  char *dynresource = NULL;
+  char fpr[16];
+  const char *proxy_host;
+  const char *resource_prefix = PACKAGE_NAME;
+  char *fjid;
+  int ssl, tls;
+  LmSSL *lssl;
+  unsigned int port;
+  unsigned int ping;
+  LmMessageHandler *handler;
+  GError *error = NULL;
+
+  if (lconnection && lm_connection_is_open(lconnection))
+    xmpp_disconnect();
+
+  servername = settings_opt_get("server");
+  userjid    = settings_opt_get("jid");
+  password   = settings_opt_get("password");
+  resource   = settings_opt_get("resource");
+  proxy_host = settings_opt_get("proxy_host");
+  ssl_fpr    = settings_opt_get("ssl_fingerprint");
+
+  if (!userjid) {
+    scr_LogPrint(LPRINT_LOGNORM, "Your JID has not been specified!");
+    return;
+  }
+  if (!password) {
+    scr_LogPrint(LPRINT_LOGNORM, "Your password has not been specified!");
+    return;
+  }
+
+  lconnection = lm_connection_new_with_context(NULL, main_context);
+
+  g_log_set_handler("LM", LM_LOG_LEVEL_ALL, lm_debug_handler, NULL);
+
+  ping = 40;
+  if (settings_opt_get("pinginterval"))
+    ping = (unsigned int) settings_opt_get_int("pinginterval");
+  lm_connection_set_keep_alive_rate(lconnection, ping);
+  scr_LogPrint(LPRINT_DEBUG, "Ping interval established: %d secs", ping);
+
+  lm_connection_set_disconnect_function(lconnection, connection_close_cb,
+                                        NULL, NULL);
+
+  handler = lm_message_handler_new(handle_messages, NULL, NULL);
+  lm_connection_register_message_handler(lconnection, handler,
+                                         LM_MESSAGE_TYPE_MESSAGE,
+                                         LM_HANDLER_PRIORITY_NORMAL);
+  lm_message_handler_unref(handler);
+
+  handler = lm_message_handler_new(handle_iq, NULL, NULL);
+  lm_connection_register_message_handler(lconnection, handler,
+                                         LM_MESSAGE_TYPE_IQ,
+                                         LM_HANDLER_PRIORITY_NORMAL);
+  lm_message_handler_unref(handler);
+
+  handler = lm_message_handler_new(handle_presence, NULL, NULL);
+  lm_connection_register_message_handler(lconnection, handler,
+                                         LM_MESSAGE_TYPE_PRESENCE,
+                                         LM_HANDLER_PRIORITY_LAST);
+  lm_message_handler_unref(handler);
+
+  handler = lm_message_handler_new(handle_s10n, NULL, NULL);
+  lm_connection_register_message_handler(lconnection, handler,
+                                         LM_MESSAGE_TYPE_PRESENCE,
+                                         LM_HANDLER_PRIORITY_NORMAL);
+  lm_message_handler_unref(handler);
+
+  /* Connect to server */
+  scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Connecting to server: %s",
+               servername ? servername : "...");
+  if (!resource)
+    resource = resource_prefix;
+
+  if (!settings_opt_get("disable_random_resource")) {
+#if HAVE_ARC4RANDOM
+    dynresource = g_strdup_printf("%s.%08x", resource, arc4random());
+#else
+    unsigned int tab[2];
+    srand(time(NULL));
+    tab[0] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0)));
+    tab[1] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0)));
+    dynresource = g_strdup_printf("%s.%04x%04x", resource, tab[0], tab[1]);
+#endif
+    resource = dynresource;
+  }
+
+  port = (unsigned int) settings_opt_get_int("port");
+
+  if (port)
+    scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using port %d", port);
+  scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " resource %s", resource);
+
+  if (proxy_host) {
+    int proxy_port = settings_opt_get_int("proxy_port");
+    if (proxy_port <= 0 || proxy_port > 65535) {
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Invalid proxy port: %d",
+                   proxy_port);
+    } else {
+      const char *proxy_user, *proxy_pass;
+      LmProxy *lproxy;
+      proxy_user = settings_opt_get("proxy_user");
+      proxy_pass = settings_opt_get("proxy_pass");
+      // Proxy initialization
+      lproxy = lm_proxy_new_with_server(LM_PROXY_TYPE_HTTP,
+                                        proxy_host, proxy_port);
+      lm_proxy_set_username(lproxy, proxy_user);
+      lm_proxy_set_password(lproxy, proxy_pass);
+      lm_connection_set_proxy(lconnection, lproxy);
+      lm_proxy_unref(lproxy);
+      scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using proxy %s:%d",
+                   proxy_host, proxy_port);
+    }
+  }
+
+  fjid = compose_jid(userjid, servername, resource);
+  lm_connection_set_jid(lconnection, fjid);
+  if (servername)
+    lm_connection_set_server(lconnection, servername);
+#if defined(HAVE_LIBOTR)
+  otr_init(fjid);
+#endif
+  g_free(fjid);
+  g_free(dynresource);
+
+  ssl = settings_opt_get_int("ssl");
+  tls = settings_opt_get_int("tls");
+
+  if (!lm_ssl_is_supported()) {
+    if (ssl || tls) {
+      scr_LogPrint(LPRINT_LOGNORM, "** Error: SSL is NOT available, "
+                   "please recompile loudmouth with SSL enabled.");
+      return;
+    }
+  }
+
+  if (ssl && tls) {
+    scr_LogPrint(LPRINT_LOGNORM, "You can only set ssl or tls, not both.");
+    return;
+  }
+
+  if (!port)
+    port = (ssl ? LM_CONNECTION_DEFAULT_PORT_SSL : LM_CONNECTION_DEFAULT_PORT);
+  lm_connection_set_port(lconnection, port);
+
+  if (ssl_fpr && (!hex_to_fingerprint(ssl_fpr, fpr))) {
+    scr_LogPrint(LPRINT_LOGNORM, "** Plese set the fingerprint in the format "
+                 "97:5C:00:3F:1D:77:45:25:E2:C5:70:EC:83:C8:87:EE");
+    return;
+  }
+
+  lssl = lm_ssl_new((ssl_fpr ? fpr : NULL), ssl_cb, NULL, NULL);
+  if (lssl) {
+    lm_ssl_use_starttls(lssl, !ssl, tls);
+    lm_connection_set_ssl(lconnection, lssl);
+    lm_ssl_unref(lssl);
+  } else if (ssl || tls) {
+    scr_LogPrint(LPRINT_LOGNORM, "** Error: Couldn't create SSL struct.");
+    return;
+  }
+
+  if (!lm_connection_open(lconnection, connection_open_cb,
+                          NULL, FALSE, &error)) {
+    _try_to_reconnect();
+    scr_LogPrint(LPRINT_LOGNORM, "Failed to open: %s\n", error->message);
+    g_error_free (error);
+  }
+}
+
+//  insert_entity_capabilities(presence_stanza)
+// Entity Capabilities (XEP-0115)
+static void insert_entity_capabilities(LmMessageNode *x, enum imstatus status)
+{
+  LmMessageNode *y;
+  const char *ver = entity_version(status);
+
+  y = lm_message_node_add_child(x, "c", NULL);
+  lm_message_node_set_attribute(y, "xmlns", NS_CAPS);
+  lm_message_node_set_attribute(y, "hash", "sha-1");
+  lm_message_node_set_attribute(y, "node", MCABBER_CAPS_NODE);
+  lm_message_node_set_attribute(y, "ver", ver);
+}
+
+void xmpp_disconnect(void)
+{
+  if (!lconnection || !lm_connection_is_authenticated(lconnection))
+    return;
+
+  // Launch pre-disconnect internal hook
+  hook_execute_internal("hook-pre-disconnect");
+  // Announce it to  everyone else
+  xmpp_setstatus(offline, NULL, "", FALSE);
+  lm_connection_close(lconnection, NULL);
+}
+
+void xmpp_setstatus(enum imstatus st, const char *recipient, const char *msg,
+                  int do_not_sign)
+{
+  LmMessage *m;
+
+  if (msg) {
+    // The status message has been specified.  We'll use it, unless it is
+    // "-" which is a special case (option meaning "no status message").
+    if (!strcmp(msg, "-"))
+      msg = "";
+  } else {
+    // No status message specified; we'll use:
+    // a) the default status message (if provided by the user);
+    // b) the current status message;
+    // c) no status message (i.e. an empty one).
+    msg = settings_get_status_msg(st);
+    if (!msg) {
+      if (mystatusmsg)
+        msg = mystatusmsg;
+      else
+        msg = "";
+    }
+  }
+
+  // Only send the packet if we're online.
+  // (But we want to update internal status even when disconnected,
+  // in order to avoid some problems during network failures)
+  if (lm_connection_is_authenticated(lconnection)) {
+    const char *s_msg = (st != invisible ? msg : NULL);
+    m = lm_message_new_presence(st, recipient, s_msg);
+    insert_entity_capabilities(m->node, st); // Entity Capabilities (XEP-0115)
+#ifdef HAVE_GPGME
+    if (!do_not_sign && gpg_enabled()) {
+      char *signature;
+      signature = gpg_sign(s_msg ? s_msg : "");
+      if (signature) {
+        LmMessageNode *y;
+        y = lm_message_node_add_child(m->node, "x", signature);
+        lm_message_node_set_attribute(y, "xmlns", NS_SIGNED);
+        g_free(signature);
+      }
+    }
+#endif
+    lm_connection_send(lconnection, m, NULL);
+    lm_message_unref(m);
+  }
+
+  // If we didn't change our _global_ status, we are done
+  if (recipient) return;
+
+  if (lm_connection_is_authenticated(lconnection)) {
+    // Send presence to chatrooms
+    if (st != invisible) {
+      struct T_presence room_presence;
+      room_presence.st = st;
+      room_presence.msg = msg;
+      foreach_buddy(ROSTER_TYPE_ROOM, &roompresence, &room_presence);
+    }
+
+    // We'll have to update the roster if we switch to/from offline because
+    // we don't know the presences of buddies when offline...
+    if (mystatus == offline || st == offline)
+      update_roster = TRUE;
+
+    hk_mystatuschange(0, mystatus, st, (st != invisible ? msg : ""));
+    mystatus = st;
+  }
+
+  if (st)
+    mywantedstatus = st;
+
+  if (msg != mystatusmsg) {
+    g_free(mystatusmsg);
+    if (*msg)
+      mystatusmsg = g_strdup(msg);
+    else
+      mystatusmsg = NULL;
+  }
+
+  if (!Autoaway)
+    update_last_use();
+
+  // Update status line
+  scr_UpdateMainStatus(TRUE);
+}
+
+
+enum imstatus xmpp_getstatus(void)
+{
+  return mystatus;
+}
+
+const char *xmpp_getstatusmsg(void)
+{
+  return mystatusmsg;
+}
+
+//  xmpp_setprevstatus()
+// Set previous status.  This wrapper function is used after a disconnection.
+void xmpp_setprevstatus(void)
+{
+  xmpp_setstatus(mywantedstatus, NULL, mystatusmsg, FALSE);
+}
+
+//  send_storage(store)
+// Send the node "store" to update the server.
+// Note: the sender should check we're online.
+void send_storage(LmMessageNode *store)
+{
+  LmMessage *iq;
+  LmMessageNode *query;
+
+  if (!rosternotes) return;
+
+  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_SET);
+  query = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE);
+  lm_message_node_insert_childnode(query, store);
+
+  lm_connection_send(lconnection, iq, NULL);
+  lm_message_unref(iq);
+}
+
+
+//  xmpp_is_bookmarked(roomjid)
+// Return TRUE if there's a bookmark for the given jid.
+guint xmpp_is_bookmarked(const char *bjid)
+{
+  LmMessageNode *x;
+
+  if (!bookmarks)
+    return FALSE;
+
+  // Walk through the storage bookmark tags
+  for (x = bookmarks->children ; x; x = x->next) {
+    // If the node is a conference item, check the jid.
+    if (x->name && !strcmp(x->name, "conference")) {
+      const char *fjid = lm_message_node_get_attribute(x, "jid");
+      if (fjid && !strcasecmp(bjid, fjid))
+        return TRUE;
+    }
+  }
+  return FALSE;
+}
+
+//  xmpp_get_bookmark_nick(roomjid)
+// Return the room nickname if it is present in a bookmark.
+const char *xmpp_get_bookmark_nick(const char *bjid)
+{
+  LmMessageNode *x;
+
+  if (!bookmarks || !bjid)
+    return NULL;
+
+  // Walk through the storage bookmark tags
+  for (x = bookmarks->children ; x; x = x->next) {
+    // If the node is a conference item, check the jid.
+    if (x->name && !strcmp(x->name, "conference")) {
+      const char *fjid = lm_message_node_get_attribute(x, "jid");
+      if (fjid && !strcasecmp(bjid, fjid))
+        return lm_message_node_get_child_value(x, "nick");
+    }
+  }
+  return NULL;
+}
+
+
+//  xmpp_get_all_storage_bookmarks()
+// Return a GSList with all storage bookmarks.
+// The caller should g_free the list (not the MUC jids).
+GSList *xmpp_get_all_storage_bookmarks(void)
+{
+  LmMessageNode *x;
+  GSList *sl_bookmarks = NULL;
+
+  // If we have no bookmarks, probably the server doesn't support them.
+  if (!bookmarks)
+    return NULL;
+
+  // Walk through the storage bookmark tags
+  for (x = bookmarks->children ; x; x = x->next) {
+    // If the node is a conference item, let's add the note to our list.
+    if (x->name && !strcmp(x->name, "conference")) {
+      struct bookmark *bm_elt;
+      const char *autojoin, *name, *nick;
+      const char *fjid = lm_message_node_get_attribute(x, "jid");
+      if (!fjid)
+        continue;
+      bm_elt = g_new0(struct bookmark, 1);
+      bm_elt->roomjid = g_strdup(fjid);
+      autojoin = lm_message_node_get_attribute(x, "autojoin");
+      nick = lm_message_node_get_attribute(x, "nick");
+      name = lm_message_node_get_attribute(x, "name");
+      if (autojoin && !strcmp(autojoin, "1"))
+        bm_elt->autojoin = 1;
+      if (nick)
+        bm_elt->nick = g_strdup(nick);
+      if (name)
+        bm_elt->name = g_strdup(name);
+      sl_bookmarks = g_slist_append(sl_bookmarks, bm_elt);
+    }
+  }
+  return sl_bookmarks;
+}
+
+//  xmpp_set_storage_bookmark(roomid, name, nick, passwd, autojoin,
+//                          printstatus, autowhois)
+// Update the private storage bookmarks: add a conference room.
+// If name is nil, we remove the bookmark.
+void xmpp_set_storage_bookmark(const char *roomid, const char *name,
+                               const char *nick, const char *passwd,
+                               int autojoin, enum room_printstatus pstatus,
+                               enum room_autowhois awhois)
+{
+  LmMessageNode *x;
+  bool changed = FALSE;
+
+  if (!roomid)
+    return;
+
+  // If we have no bookmarks, probably the server doesn't support them.
+  if (!bookmarks) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Sorry, your server doesn't seem to support private storage.");
+    return;
+  }
+
+  // Walk through the storage tags
+  for (x = bookmarks->children ; x; x = x->next) {
+    // If the current node is a conference item, see if we have to replace it.
+    if (x->name && !strcmp(x->name, "conference")) {
+      const char *fjid = lm_message_node_get_attribute(x, "jid");
+      if (!fjid)
+        continue;
+      if (!strcmp(fjid, roomid)) {
+        // We've found a bookmark for this room.  Let's hide it and we'll
+        // create a new one.
+        lm_message_node_hide(x);
+        changed = TRUE;
+        if (!name)
+          scr_LogPrint(LPRINT_LOGNORM, "Deleting bookmark...");
+      }
+    }
+  }
+
+  // Let's create a node/bookmark for this roomid, if the name is not NULL.
+  if (name) {
+    x = lm_message_node_add_child(bookmarks, "conference", NULL);
+    lm_message_node_set_attributes(x,
+                                   "jid", roomid,
+                                   "name", name,
+                                   "autojoin", autojoin ? "1" : "0",
+                                   NULL);
+    if (nick)
+      lm_message_node_add_child(x, "nick", nick);
+    if (passwd)
+      lm_message_node_add_child(x, "password", passwd);
+    if (pstatus)
+      lm_message_node_add_child(x, "print_status", strprintstatus[pstatus]);
+    if (awhois)
+      lm_message_node_set_attributes(x, "autowhois",
+                                     (awhois == autowhois_on) ? "1" : "0",
+                                     NULL);
+    changed = TRUE;
+    scr_LogPrint(LPRINT_LOGNORM, "Updating bookmarks...");
+  }
+
+  if (!changed)
+    return;
+
+  if (lm_connection_is_authenticated(lconnection))
+    send_storage(bookmarks);
+  else
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Warning: you're not connected to the server.");
+}
+
+static struct annotation *parse_storage_rosternote(LmMessageNode *notenode)
+{
+  const char *p;
+  struct annotation *note = g_new0(struct annotation, 1);
+  p = lm_message_node_get_attribute(notenode, "cdate");
+  if (p)
+    note->cdate = from_iso8601(p, 1);
+  p = lm_message_node_get_attribute(notenode, "mdate");
+  if (p)
+    note->mdate = from_iso8601(p, 1);
+  note->text = g_strdup(lm_message_node_get_value(notenode));
+  note->jid = g_strdup(lm_message_node_get_attribute(notenode, "jid"));
+  return note;
+}
+
+//  xmpp_get_all_storage_rosternotes()
+// Return a GSList with all storage annotations.
+// The caller should g_free the list and its contents.
+GSList *xmpp_get_all_storage_rosternotes(void)
+{
+  LmMessageNode *x;
+  GSList *sl_notes = NULL;
+
+  // If we have no rosternotes, probably the server doesn't support them.
+  if (!rosternotes)
+    return NULL;
+
+  // Walk through the storage rosternotes tags
+  for (x = rosternotes->children ; x; x = x->next) {
+    struct annotation *note;
+
+    // We want a note item
+    if (!x->name || strcmp(x->name, "note"))
+      continue;
+    // Just in case, check the jid...
+    if (!lm_message_node_get_attribute(x, "jid"))
+      continue;
+    // Ok, let's add the note to our list
+    note = parse_storage_rosternote(x);
+    sl_notes = g_slist_append(sl_notes, note);
+  }
+  return sl_notes;
+}
+
+//  xmpp_get_storage_rosternotes(barejid, silent)
+// Return the annotation associated with this jid.
+// If silent is TRUE, no warning is displayed when rosternotes is disabled
+// The caller should g_free the string and structure after use.
+struct annotation *xmpp_get_storage_rosternotes(const char *barejid, int silent)
+{
+  LmMessageNode *x;
+
+  if (!barejid)
+    return NULL;
+
+  // If we have no rosternotes, probably the server doesn't support them.
+  if (!rosternotes) {
+    if (!silent)
+      scr_LogPrint(LPRINT_NORMAL, "Sorry, "
+                   "your server doesn't seem to support private storage.");
+    return NULL;
+  }
+
+  // Walk through the storage rosternotes tags
+  for (x = rosternotes->children ; x; x = x->next) {
+    const char *fjid;
+    // We want a note item
+    if (!x->name || strcmp(x->name, "note"))
+      continue;
+    // Just in case, check the jid...
+    fjid = lm_message_node_get_attribute(x, "jid");
+    if (fjid && !strcmp(fjid, barejid)) // We've found a note for this contact.
+      return parse_storage_rosternote(x);
+  }
+  return NULL;  // No note found
+}
+
+//  xmpp_set_storage_rosternotes(barejid, note)
+// Update the private storage rosternotes: add/delete a note.
+// If note is nil, we remove the existing note.
+void xmpp_set_storage_rosternotes(const char *barejid, const char *note)
+{
+  LmMessageNode *x;
+  bool changed = FALSE;
+  const char *cdate = NULL;
+
+  if (!barejid)
+    return;
+
+  // If we have no rosternotes, probably the server doesn't support them.
+  if (!rosternotes) {
+    scr_LogPrint(LPRINT_NORMAL,
+                 "Sorry, your server doesn't seem to support private storage.");
+    return;
+  }
+
+  // Walk through the storage tags
+  for (x = rosternotes->children ; x; x = x->next) {
+    // If the current node is a conference item, see if we have to replace it.
+    if (x->name && !strcmp(x->name, "note")) {
+      const char *fjid = lm_message_node_get_attribute(x, "jid");
+      if (!fjid)
+        continue;
+      if (!strcmp(fjid, barejid)) {
+        // We've found a note for this jid.  Let's hide it and we'll
+        // create a new one.
+        cdate = lm_message_node_get_attribute(x, "cdate");
+        lm_message_node_hide(x);
+        changed = TRUE;
+        break;
+      }
+    }
+  }
+
+  // Let's create a node for this jid, if the note is not NULL.
+  if (note) {
+    char mdate[20];
+    time_t now;
+    time(&now);
+    to_iso8601(mdate, now);
+    if (!cdate)
+      cdate = mdate;
+    x = lm_message_node_add_child(rosternotes, "note", note);
+    lm_message_node_set_attributes(x,
+                                   "jid", barejid,
+                                   "cdate", cdate,
+                                   "mdate", mdate,
+                                   NULL);
+    changed = TRUE;
+  }
+
+  if (!changed)
+    return;
+
+  if (lm_connection_is_authenticated(lconnection))
+    send_storage(rosternotes);
+  else
+    scr_LogPrint(LPRINT_LOGNORM,
+                 "Warning: you're not connected to the server.");
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,82 @@
+#ifndef __MCABBER_XMPP_H__
+#define __MCABBER_XMPP_H__ 1
+
+#include <loudmouth/loudmouth.h>
+#include <mcabber/roster.h>
+
+enum iqreq_type {
+  iqreq_none,
+  iqreq_version,
+  iqreq_time,
+  iqreq_last,
+  iqreq_vcard
+};
+
+struct annotation {
+  time_t cdate;
+  time_t mdate;
+  gchar *jid;
+  gchar *text;
+};
+
+struct bookmark {
+  gchar *roomjid;
+  gchar *name;
+  gchar *nick;
+  guint autojoin;
+  /* enum room_printstatus pstatus; */
+  /* enum room_autowhois awhois; */
+};
+
+extern LmConnection* lconnection;
+extern LmSSL* lssl;
+
+void xmpp_connect(void);
+void xmpp_disconnect(void);
+
+void xmpp_room_join(const char *room, const char *nickname, const char *passwd);
+int xmpp_room_setattrib(const char *roomid, const char *fjid,
+                        const char *nick, struct role_affil ra,
+                        const char *reason);
+void xmpp_room_invite(const char *room, const char *fjid, const char *reason);
+void xmpp_room_unlock(const char *room);
+void xmpp_room_destroy(const char *room, const char *venue, const char *reason);
+
+void xmpp_addbuddy(const char *bjid, const char *name, const char *group);
+void xmpp_updatebuddy(const char *bjid, const char *name, const char *group);
+void xmpp_delbuddy(const char *bjid);
+
+void xmpp_send_msg(const char *fjid, const char *text, int type,
+                   const char *subject, gboolean otrinject, gint *encrypted,
+                   LmMessageSubType type_overwrite, gpointer *xep184);
+
+void xmpp_send_s10n(const char *bjid, LmMessageSubType type);
+
+enum imstatus xmpp_getstatus(void);
+const char *xmpp_getstatusmsg(void);
+void xmpp_setprevstatus(void);
+
+void xmpp_setstatus(enum imstatus st, const char *recipient,
+                    const char *msg, int do_not_sign);
+
+void xmpp_send_chatstate(gpointer buddy, guint chatstate);
+
+GSList *xmpp_get_all_storage_bookmarks(void);
+GSList *xmpp_get_all_storage_rosternotes(void);
+void xmpp_set_storage_bookmark(const char *roomid, const char *name,
+                               const char *nick, const char *passwd,
+                               int autojoin, enum room_printstatus pstatus,
+                               enum room_autowhois awhois);
+struct annotation *xmpp_get_storage_rosternotes(const char *barejid,
+                                                int silent);
+void xmpp_set_storage_rosternotes(const char *barejid, const char *note);
+guint xmpp_is_bookmarked(const char *bjid);
+const char *xmpp_get_bookmark_nick(const char *bjid);
+
+void xmpp_request(const char *fjid, enum iqreq_type reqtype);
+void request_vcard(const char *bjid);
+void xmpp_request_storage(const gchar *storage);
+
+#endif /* __MCABBER_XMPP_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_defines.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,82 @@
+#ifndef __MCABBER_XMPP_DEFINES_H__
+#define __MCABBER_XMPP_DEFINES_H__ 1
+
+#define MCABBER_CAPS_NODE "http://mcabber.com/caps"
+
+#define NS_CLIENT    "jabber:client"
+#define NS_SERVER    "jabber:server"
+#define NS_DIALBACK  "jabber:server:dialback"
+#define NS_AUTH      "jabber:iq:auth"
+#define NS_AUTH_CRYPT "jabber:iq:auth:crypt"
+#define NS_REGISTER  "jabber:iq:register"
+#define NS_ROSTER    "jabber:iq:roster"
+#define NS_OFFLINE   "jabber:x:offline"
+#define NS_AGENT     "jabber:iq:agent"
+#define NS_AGENTS    "jabber:iq:agents"
+#define NS_DELAY     "jabber:x:delay"
+#define NS_VERSION   "jabber:iq:version"
+#define NS_TIME      "jabber:iq:time"
+#define NS_VCARD     "vcard-temp"
+#define NS_PRIVATE   "jabber:iq:private"
+#define NS_SEARCH    "jabber:iq:search"
+#define NS_OOB       "jabber:iq:oob"
+#define NS_XOOB      "jabber:x:oob"
+#define NS_ADMIN     "jabber:iq:admin"
+#define NS_FILTER    "jabber:iq:filter"
+#define NS_AUTH_0K   "jabber:iq:auth:0k"
+#define NS_BROWSE    "jabber:iq:browse"
+#define NS_EVENT     "jabber:x:event"
+#define NS_CONFERENCE "jabber:iq:conference"
+#define NS_SIGNED    "jabber:x:signed"
+#define NS_ENCRYPTED "jabber:x:encrypted"
+#define NS_GATEWAY   "jabber:iq:gateway"
+#define NS_LAST      "jabber:iq:last"
+#define NS_ENVELOPE  "jabber:x:envelope"
+#define NS_EXPIRE    "jabber:x:expire"
+#define NS_XHTML     "http://www.w3.org/1999/xhtml"
+#define NS_DISCO_INFO "http://jabber.org/protocol/disco#info"
+#define NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items"
+#define NS_IQ_AUTH    "http://jabber.org/features/iq-auth"
+#define NS_REGISTER_FEATURE "http://jabber.org/features/iq-register"
+
+#define NS_CAPS       "http://jabber.org/protocol/caps"
+#define NS_CHATSTATES "http://jabber.org/protocol/chatstates"
+#define NS_COMMANDS   "http://jabber.org/protocol/commands"
+#define NS_MUC        "http://jabber.org/protocol/muc"
+
+#define NS_XDBGINSERT "jabber:xdb:ginsert"
+#define NS_XDBNSLIST  "jabber:xdb:nslist"
+
+#define NS_XMPP_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas"
+#define NS_XMPP_TLS  "urn:ietf:params:xml:ns:xmpp-tls"
+#define NS_XMPP_STREAMS "urn:ietf:params:xml:ns:xmpp-streams"
+
+#define NS_XMPP_DELAY "urn:xmpp:delay"
+#define NS_XMPP_TIME  "urn:xmpp:time"
+#define NS_PING       "urn:xmpp:ping"
+#define NS_RECEIPTS   "urn:xmpp:receipts"
+
+#define NS_JABBERD_STOREDPRESENCE "http://jabberd.org/ns/storedpresence"
+#define NS_JABBERD_HISTORY "http://jabberd.org/ns/history"
+
+#define XMPP_ERROR_REDIRECT              302
+#define XMPP_ERROR_BAD_REQUEST           400
+#define XMPP_ERROR_NOT_AUTHORIZED        401
+#define XMPP_ERROR_PAYMENT_REQUIRED      402
+#define XMPP_ERROR_FORBIDDEN             403
+#define XMPP_ERROR_NOT_FOUND             404
+#define XMPP_ERROR_NOT_ALLOWED           405
+#define XMPP_ERROR_NOT_ACCEPTABLE        406
+#define XMPP_ERROR_REGISTRATION_REQUIRED 407
+#define XMPP_ERROR_REQUEST_TIMEOUT       408
+#define XMPP_ERROR_CONFLICT              409
+#define XMPP_ERROR_INTERNAL_SERVER_ERROR 500
+#define XMPP_ERROR_NOT_IMPLEMENTED       501
+#define XMPP_ERROR_REMOTE_SERVER_ERROR   502
+#define XMPP_ERROR_SERVICE_UNAVAILABLE   503
+#define XMPP_ERROR_REMOTE_SERVER_TIMEOUT 504
+#define XMPP_ERROR_DISCONNECTED          510
+
+#endif
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_helper.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,400 @@
+/*
+ * xmpp_helper.c    -- Jabber protocol helper functions
+ *
+ * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ * Some parts initially came from the centericq project:
+ * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "xmpp_helper.h"
+#include "settings.h"
+#include "utils.h"
+#include "caps.h"
+#include "logprint.h"
+#include "config.h"
+
+time_t iqlast; // last message/status change time
+
+extern char *imstatus_showmap[];
+
+struct xmpp_error xmpp_errors[] = {
+  {XMPP_ERROR_REDIRECT,              "302",
+    "Redirect",              "redirect",                "modify"},
+  {XMPP_ERROR_BAD_REQUEST,           "400",
+    "Bad Request",           "bad-request",             "modify"},
+  {XMPP_ERROR_NOT_AUTHORIZED,        "401",
+    "Not Authorized",        "not-authorized",          "auth"},
+  {XMPP_ERROR_PAYMENT_REQUIRED,      "402",
+    "Payment Required",      "payment-required",        "auth"},
+  {XMPP_ERROR_FORBIDDEN,             "403",
+    "Forbidden",             "forbidden",               "auth"},
+  {XMPP_ERROR_NOT_FOUND,             "404",
+    "Not Found",             "item-not-found",          "cancel"},
+  {XMPP_ERROR_NOT_ALLOWED,           "405",
+    "Not Allowed",           "not-allowed",             "cancel"},
+  {XMPP_ERROR_NOT_ACCEPTABLE,        "406",
+    "Not Acceptable",        "not-acceptable",          "modify"},
+  {XMPP_ERROR_REGISTRATION_REQUIRED, "407",
+    "Registration required", "registration-required",   "auth"},
+  {XMPP_ERROR_REQUEST_TIMEOUT,       "408",
+    "Request Timeout",       "remote-server-timeout",   "wait"},
+  {XMPP_ERROR_CONFLICT,              "409",
+    "Conflict",               "conflict",               "cancel"},
+  {XMPP_ERROR_INTERNAL_SERVER_ERROR, "500",
+    "Internal Server Error", "internal-server-error",   "wait"},
+  {XMPP_ERROR_NOT_IMPLEMENTED,       "501",
+    "Not Implemented",       "feature-not-implemented", "cancel"},
+  {XMPP_ERROR_REMOTE_SERVER_ERROR,   "502",
+    "Remote Server Error",   "service-unavailable",     "wait"},
+  {XMPP_ERROR_SERVICE_UNAVAILABLE,   "503",
+    "Service Unavailable",   "service-unavailable",     "cancel"},
+  {XMPP_ERROR_REMOTE_SERVER_TIMEOUT, "504",
+    "Remote Server Timeout", "remote-server-timeout",   "wait"},
+  {XMPP_ERROR_DISCONNECTED,          "510",
+    "Disconnected",          "service-unavailable",     "cancel"},
+  {0, NULL, NULL, NULL, NULL}
+};
+
+
+#ifdef MODULES_ENABLE
+static GSList *xmpp_additional_features = NULL;
+static char *ver, *ver_notavail;
+
+void xmpp_add_feature (const char *xmlns)
+{
+  if (xmlns) {
+    ver = NULL;
+    ver_notavail = NULL;
+    xmpp_additional_features = g_slist_append(xmpp_additional_features,
+                                              g_strdup (xmlns));
+  }
+}
+
+void xmpp_del_feature (const char *xmlns)
+{
+  GSList *feature = xmpp_additional_features;
+  while (feature) {
+    if (!strcmp(feature->data, xmlns)) {
+      ver = NULL;
+      ver_notavail = NULL;
+      g_free (feature->data);
+      xmpp_additional_features = g_slist_delete_link(xmpp_additional_features,
+                                                     feature);
+      return;
+    }
+    feature = g_slist_next (feature);
+  }
+}
+#endif
+
+const gchar* lm_message_node_get_child_value(LmMessageNode *node,
+                                             const gchar *child)
+{
+  LmMessageNode *tmp;
+  tmp = lm_message_node_find_child(node, child);
+  if (tmp)
+    return lm_message_node_get_value(tmp);
+  else return NULL;
+}
+
+static LmMessageNode *hidden = NULL;
+
+void lm_message_node_hide(LmMessageNode *node)
+{
+  LmMessageNode *parent = node->parent, *prev_sibling = node->prev;
+
+  if (hidden) {
+    hidden->children = hidden->next = hidden->prev = hidden->parent = NULL;
+    lm_message_node_unref(hidden);
+  }
+
+  if (parent->children == node)
+    parent->children = node->next;
+  if (prev_sibling)
+    prev_sibling->next = node->next;
+  if (node->next)
+    node->next->prev = prev_sibling;
+}
+
+//maybe not a good idea, because it uses internals of loudmouth...
+//it's used for rosternotes/bookmarks
+LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns)
+{
+  LmMessageNode *node;
+
+  node = g_new0 (LmMessageNode, 1);
+  node->name       = g_strdup (name);
+  node->value      = NULL;
+  node->raw_mode   = FALSE;
+  node->attributes = NULL;
+  node->next       = NULL;
+  node->prev       = NULL;
+  node->parent     = NULL;
+  node->children   = NULL;
+
+  node->ref_count  = 1;
+  lm_message_node_set_attribute(node, "xmlns", xmlns);
+  return node;
+}
+
+void lm_message_node_insert_childnode(LmMessageNode *node,
+                                      LmMessageNode *child)
+{
+  LmMessageNode *x;
+  lm_message_node_deep_ref(child);
+
+  if (node->children == NULL)
+    node->children = child;
+  else {
+    for (x = node->children; x->next; x = x->next)
+      ;
+    x->next = child;
+  }
+}
+
+void lm_message_node_deep_ref(LmMessageNode *node)
+{
+  if (node == NULL)
+    return;
+  lm_message_node_ref(node);
+  lm_message_node_deep_ref(node->next);
+  lm_message_node_deep_ref(node->children);
+}
+
+const gchar* lm_message_get_from(LmMessage *m)
+{
+  return lm_message_node_get_attribute(m->node, "from");
+}
+
+const gchar* lm_message_get_id(LmMessage *m)
+{
+  return lm_message_node_get_attribute(m->node, "id");
+}
+
+LmMessage *lm_message_new_iq_from_query(LmMessage *m,
+                                        LmMessageSubType type)
+{
+  LmMessage *new;
+  const char *from = lm_message_node_get_attribute(m->node, "from");
+  const char *id = lm_message_node_get_attribute(m->node, "id");
+
+  new = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ,
+                                     type);
+  if (id)
+    lm_message_node_set_attribute(new->node, "id", id);
+
+  return new;
+}
+
+//  entity_version(enum imstatus status)
+// Return a static version string for Entity Capabilities.
+// It should be specific to the client version, please change the id
+// if you alter mcabber's disco support (or add something to the version
+// number) so that it doesn't conflict with the official client.
+const char *entity_version(enum imstatus status)
+{
+#ifndef MODULES_ENABLE
+  static char *ver, *ver_notavail;
+#endif
+
+  if (ver && (status != notavail))
+    return ver;
+  if (ver_notavail)
+    return ver_notavail;
+
+  caps_add("");
+  caps_set_identity("", "client", PACKAGE_STRING, "pc");
+  caps_add_feature("", NS_DISCO_INFO);
+  caps_add_feature("", NS_MUC);
+  // advertise ChatStates only if they aren't disabled
+  if (!settings_opt_get_int("disable_chatstates"))
+   caps_add_feature("", NS_CHATSTATES);
+  caps_add_feature("", NS_TIME);
+  caps_add_feature("", NS_XMPP_TIME);
+  caps_add_feature("", NS_VERSION);
+  caps_add_feature("", NS_PING);
+  caps_add_feature("", NS_COMMANDS);
+  caps_add_feature("", NS_RECEIPTS);
+  if (!settings_opt_get_int("iq_last_disable") &&
+      (!settings_opt_get_int("iq_last_disable_when_notavail") ||
+       status != notavail))
+   caps_add_feature("", NS_LAST);
+#ifdef MODULES_ENABLE
+  {
+    GSList *el = xmpp_additional_features;
+    while (el) {
+      caps_add_feature("", el->data);
+      el = g_slist_next (el);
+    }
+  }
+#endif
+
+  if (status == notavail) {
+    ver_notavail = caps_generate();
+    return ver_notavail;
+  }
+
+  ver = caps_generate();
+  return ver;
+}
+
+LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
+                                          const char *xmlns)
+{
+  LmMessageNode *x;
+  const char *p;
+
+  for (x = node->children ; x; x = x->next) {
+    if ((p = lm_message_node_get_attribute(x, "xmlns")) && !strcmp(p, xmlns))
+      break;
+  }
+  return x;
+}
+
+time_t lm_message_node_get_timestamp(LmMessageNode *node)
+{
+  LmMessageNode *x;
+  const char *p;
+
+  x = lm_message_node_find_xmlns(node, NS_XMPP_DELAY);
+  if (x && (!strcmp(x->name, "delay")) &&
+      (p = lm_message_node_get_attribute(x, "stamp")) != NULL)
+    return from_iso8601(p, 1);
+  x = lm_message_node_find_xmlns(node, NS_DELAY);
+  if (x && (p = lm_message_node_get_attribute(x, "stamp")) != NULL)
+    return from_iso8601(p, 1);
+  return 0;
+}
+
+//  lm_message_new_presence(status, recipient, message)
+// Create an xmlnode with default presence attributes
+// Note: the caller must free the node after use
+LmMessage *lm_message_new_presence(enum imstatus st,
+                                   const char *recipient,
+                                   const char *msg)
+{
+  unsigned int prio;
+  LmMessage *x = lm_message_new(recipient, LM_MESSAGE_TYPE_PRESENCE);
+
+  switch(st) {
+    case away:
+    case notavail:
+    case dontdisturb:
+    case freeforchat:
+        lm_message_node_add_child(x->node, "show", imstatus_showmap[st]);
+        break;
+
+    case invisible:
+        lm_message_node_set_attribute(x->node, "type", "invisible");
+        break;
+
+    case offline:
+        lm_message_node_set_attribute(x->node, "type", "unavailable");
+        break;
+
+    default:
+        break;
+  }
+
+  if (st == away || st == notavail)
+    prio = settings_opt_get_int("priority_away");
+  else
+    prio = settings_opt_get_int("priority");
+
+  if (prio) {
+    char strprio[8];
+    snprintf(strprio, 8, "%d", (int)prio);
+    lm_message_node_add_child(x->node, "priority", strprio);
+  }
+
+  if (msg)
+    lm_message_node_add_child(x->node, "status", msg);
+
+  return x;
+}
+
+static const char *defaulterrormsg(guint code)
+{
+  int i;
+
+  for (i = 0; xmpp_errors[i].code; ++i) {
+    if (xmpp_errors[i].code == code)
+      return xmpp_errors[i].meaning;
+  }
+  return NULL;
+}
+
+//  display_server_error(x)
+// Display the error to the user
+// x: error tag xmlnode pointer
+void display_server_error(LmMessageNode *x)
+{
+  const char *desc = NULL, *p=NULL, *s;
+  char *sdesc, *tmp;
+  int code = 0;
+
+  if (!x) return;
+
+  /* RFC3920:
+   *    The <error/> element:
+   *       o  MUST contain a child element corresponding to one of the defined
+   *          stanza error conditions specified below; this element MUST be
+   *          qualified by the 'urn:ietf:params:xml:ns:xmpp-stanzas' namespace.
+   */
+  if (x->children)
+    p = x->children->name;
+  if (p)
+    scr_LogPrint(LPRINT_LOGNORM, "Received error packet [%s]", p);
+
+  // For backward compatibility
+  if ((s = lm_message_node_get_attribute(x, "code")) != NULL) {
+    code = atoi(s);
+    // Default message
+    desc = defaulterrormsg(code);
+  }
+
+  // Error tag data is better, if available
+  s = lm_message_node_get_value(x);
+  if (s && *s) desc = s;
+
+  // And sometimes there is a text message
+  s = lm_message_node_get_child_value(x, "text");
+
+  if (s && *s) desc = s;
+
+  // If we still have no description, let's give up
+  if (!desc)
+    return;
+
+  // Strip trailing newlines
+  sdesc = g_strdup(desc);
+  for (tmp = sdesc; *tmp; tmp++) ;
+  if (tmp > sdesc)
+    tmp--;
+  while (tmp >= sdesc && (*tmp == '\n' || *tmp == '\r'))
+    *tmp-- = '\0';
+
+  scr_LogPrint(LPRINT_LOGNORM, "Error code from server: %d %s", code, sdesc);
+  g_free(sdesc);
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_helper.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,58 @@
+#ifndef __MCABBER_XMPPHELPER_H__
+#define __MCABBER_XMPPHELPER_H__ 1
+
+#include <time.h>
+#include <loudmouth/loudmouth.h>
+
+#include <mcabber/xmpp.h>
+#include <mcabber/xmpp_defines.h>
+#include <mcabber/config.h>
+
+extern time_t iqlast;           /* last message/status change time */
+
+struct T_presence {
+  enum imstatus st;
+  const char *msg;
+};
+
+struct xmpp_error {
+  guint code;
+  const char *code_str;
+  const char *meaning;
+  const char *condition;
+  const char *type;
+};
+
+
+#ifdef MODULES_ENABLE
+void xmpp_add_feature (const char *xmlns);
+void xmpp_del_feature (const char *xmlns);
+#endif
+
+LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns);
+LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
+                                          const char *xmlns);
+const gchar* lm_message_node_get_child_value(LmMessageNode *node,
+                                             const gchar *child);
+void lm_message_node_hide(LmMessageNode *node);
+void lm_message_node_insert_childnode(LmMessageNode *node,
+                                      LmMessageNode *child);
+void lm_message_node_deep_ref(LmMessageNode *node);
+time_t lm_message_node_get_timestamp(LmMessageNode *node);
+
+LmMessage *lm_message_new_iq_from_query(LmMessage *m, LmMessageSubType type);
+
+LmMessage *lm_message_new_presence(enum imstatus st,
+                                   const char *recipient, const char *msg);
+
+const gchar* lm_message_get_from(LmMessage *m);
+const gchar* lm_message_get_id(LmMessage *m);
+
+void display_server_error(LmMessageNode *x);
+
+/* XEP-0115 (Entity Capabilities) node */
+const char *entity_version(enum imstatus status);
+
+#endif
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_iq.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,836 @@
+/*
+ * xmpp_iq.c    -- Jabber protocol IQ-related stuff
+ *
+ * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ * Parts come from the centericq project:
+ * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua>
+ * Some small parts come from the Pidgin project <http://pidgin.im/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+#include <sys/utsname.h>
+
+#include "xmpp_helper.h"
+#include "commands.h"
+#include "screen.h"
+#include "utils.h"
+#include "logprint.h"
+#include "settings.h"
+#include "caps.h"
+#include "main.h"
+
+extern struct xmpp_error xmpp_errors[];
+
+static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h,
+                                                    LmConnection *c,
+                                                    LmMessage *m,
+                                                    gpointer ud);
+
+static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h,
+                                                          LmConnection *c,
+                                                          LmMessage *m,
+                                                          gpointer ud);
+
+inline double seconds_since_last_use(void);
+
+struct adhoc_command {
+  char *name;
+  char *description;
+  bool only_for_self;
+  LmHandleMessageFunction callback;
+};
+
+const struct adhoc_command adhoc_command_list[] = {
+  { "http://jabber.org/protocol/rc#set-status",
+    "Change client status",
+    1,
+    &handle_iq_command_set_status },
+  { "http://jabber.org/protocol/rc#leave-groupchats",
+    "Leave groupchat(s)",
+    1,
+    &handle_iq_command_leave_groupchats },
+  { NULL, NULL, 0, NULL },
+};
+
+struct adhoc_status {
+  char *name;   // the name used by adhoc
+  char *description;
+  char *status; // the string, used by setstus
+};
+// It has to match imstatus of roster.h!
+const struct adhoc_status adhoc_status_list[] = {
+  {"offline", "Offline", "offline"},
+  {"online", "Online", "avail"},
+  {"chat", "Chat", "free"},
+  {"dnd", "Do not disturb", "dnd"},
+  {"xd", "Extended away", "notavail"},
+  {"away", "Away", "away"},
+  {"invisible", "Invisible", "invisible"},
+  {NULL, NULL, NULL},
+};
+
+static char *generate_session_id(char *prefix)
+{
+  char *result;
+  static int counter = 0;
+  counter++;
+  // TODO better use timestamp?
+  result = g_strdup_printf("%s-%i", prefix, counter);
+  return result;
+}
+
+static LmMessage *lm_message_new_iq_error(LmMessage *m, guint error)
+{
+  LmMessage *r;
+  LmMessageNode *err;
+  int i;
+
+  for (i = 0; xmpp_errors[i].code; ++i)
+    if (xmpp_errors[i].code == error)
+      break;
+  g_return_val_if_fail(xmpp_errors[i].code > 0, NULL);
+
+  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_ERROR);
+  err = lm_message_node_add_child(r->node, "error", NULL);
+  lm_message_node_set_attribute(err, "code", xmpp_errors[i].code_str);
+  lm_message_node_set_attribute(err, "type", xmpp_errors[i].type);
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(err,
+                                     xmpp_errors[i].condition, NULL),
+           "xmlns", NS_XMPP_STANZAS);
+
+  return r;
+}
+
+void send_iq_error(LmConnection *c, LmMessage *m, guint error)
+{
+  LmMessage *r;
+  r = lm_message_new_iq_error(m, error);
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+}
+
+static void lm_message_node_add_dataform_result(LmMessageNode *node,
+                                                const char *message)
+{
+  LmMessageNode *x, *field;
+
+  x = lm_message_node_add_child(node, "x", NULL);
+  lm_message_node_set_attributes(x,
+                                 "type", "result",
+                                 "xmlns", "jabber:x:data",
+                                 NULL);
+  field = lm_message_node_add_child(x, "field", NULL);
+  lm_message_node_set_attributes(field,
+                                 "type", "text-single",
+                                 "var", "message",
+                                 NULL);
+  lm_message_node_add_child(field, "value", message);
+}
+
+static LmHandlerResult handle_iq_commands_list(LmMessageHandler *h,
+                                               LmConnection *c,
+                                               LmMessage *m, gpointer ud)
+{
+  LmMessage *iq;
+  LmMessageNode *query;
+  const char *requester_jid;
+  const struct adhoc_command *command;
+  const char *node;
+  gboolean from_self;
+
+  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  query = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_COMMANDS);
+  node = lm_message_node_get_attribute
+          (lm_message_node_get_child(m->node, "query"),
+           "node");
+  if (node)
+    lm_message_node_set_attribute(query, "node", node);
+
+  requester_jid = lm_message_get_from(m);
+  from_self = jid_equal(lm_connection_get_jid(c), requester_jid);
+
+  for (command = adhoc_command_list ; command->name ; command++) {
+    if (!command->only_for_self || from_self) {
+      lm_message_node_set_attributes
+              (lm_message_node_add_child(query, "item", NULL),
+               "node", command->name,
+               "name", command->description,
+               "jid", lm_connection_get_jid(c),
+               NULL);
+    }
+  }
+
+  lm_connection_send(c, iq, NULL);
+  lm_message_unref(iq);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h,
+                                                    LmConnection *c,
+                                                    LmMessage *m, gpointer ud)
+{
+  const char *action, *node;
+  char *sessionid;
+  LmMessage *iq;
+  LmMessageNode *command, *x, *y;
+  const struct adhoc_status *s;
+
+  x = lm_message_node_get_child(m->node, "command");
+  action = lm_message_node_get_attribute(x, "action");
+  node = lm_message_node_get_attribute(x, "node");
+  sessionid = (char *)lm_message_node_get_attribute(x, "sessionid");
+
+  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  command = lm_message_node_add_child(iq->node, "command", NULL);
+  lm_message_node_set_attribute(command, "node", node);
+  lm_message_node_set_attribute(command, "xmlns", NS_COMMANDS);
+
+  if (!sessionid) {
+    sessionid = generate_session_id("set-status");
+    lm_message_node_set_attribute(command, "sessionid", sessionid);
+    g_free(sessionid);
+    sessionid = NULL;
+    lm_message_node_set_attribute(command, "status", "executing");
+
+    x = lm_message_node_add_child(command, "x", NULL);
+    lm_message_node_set_attribute(x, "type", "form");
+    lm_message_node_set_attribute(x, "xmlns", "jabber:x:data");
+
+    lm_message_node_add_child(x, "title", "Change Status");
+
+    lm_message_node_add_child(x, "instructions",
+                              "Choose the status and status message");
+
+    // TODO see if factorisation is possible
+    y = lm_message_node_add_child(x, "field", NULL);
+    lm_message_node_set_attribute(y, "type", "hidden");
+    lm_message_node_set_attribute(y, "var", "FORM_TYPE");
+
+    lm_message_node_add_child(y, "value", "http://jabber.org/protocol/rc");
+
+    y = lm_message_node_add_child(x, "field", NULL);
+    lm_message_node_set_attributes(y,
+                                   "type", "list-single",
+                                   "var", "status",
+                                   "label", "Status",
+                                   NULL);
+    lm_message_node_add_child(y, "required", NULL);
+
+    // XXX: ugly
+    lm_message_node_add_child(y, "value",
+                              adhoc_status_list[xmpp_getstatus()].name);
+    for (s = adhoc_status_list; s->name; s++) {
+        LmMessageNode *option = lm_message_node_add_child(y, "option", NULL);
+        lm_message_node_add_child(option, "value", s->name);
+        lm_message_node_set_attribute(option, "label", s->description);
+    }
+    // TODO add priority ?
+    // I do not think this is useful, user should not have to care of the
+    // priority like gossip and gajim do (misc)
+    lm_message_node_set_attributes
+            (lm_message_node_add_child(x, "field", NULL),
+             "type", "text-multi",
+             "var", "status-message",
+             "label", "Message",
+             NULL);
+  } else if (action && !strcmp(action, "cancel")) {
+    lm_message_node_set_attribute(command, "status", "canceled");
+  } else  { // (if sessionid and not canceled)
+    y = lm_message_node_find_xmlns(x, "jabber:x:data"); //x?xmlns=jabber:x:data
+    if (y) {
+      const char *value=NULL, *message=NULL;
+      LmMessageNode *fields, *field;
+      field = fields = lm_message_node_get_child(y, "field"); //field?var=status
+      while (field && strcmp("status",
+                             lm_message_node_get_attribute(field, "var")))
+        field = field->next;
+      field = lm_message_node_get_child(field, "value");
+      if (field)
+        value = lm_message_node_get_value(field);
+      field = fields; //field?var=status-message
+      while (field && strcmp("status-message",
+                             lm_message_node_get_attribute(field, "var")))
+        field = field->next;
+      field = lm_message_node_get_child(field, "value");
+      if (field)
+        message = lm_message_node_get_value(field);
+      if (value) {
+        for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++);
+        if (s->name) {
+          char *status = g_strdup_printf("%s %s", s->status,
+                                         message ? message : "");
+          cmd_setstatus(NULL, status);
+          g_free(status);
+          lm_message_node_set_attribute(command, "status", "completed");
+          lm_message_node_add_dataform_result(command,
+                                              "Status has been changed");
+        }
+      }
+    }
+  }
+  if (sessionid)
+    lm_message_node_set_attribute(command, "sessionid", sessionid);
+  lm_connection_send(c, iq, NULL);
+  lm_message_unref(iq);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void _callback_foreach_buddy_groupchat(gpointer rosterdata, void *param)
+{
+  LmMessageNode *field, *option;
+  const char *room_jid, *nickname;
+  char *desc;
+
+  room_jid = buddy_getjid(rosterdata);
+  if (!room_jid) return;
+  nickname = buddy_getnickname(rosterdata);
+  if (!nickname) return;
+  field = param;
+
+  option = lm_message_node_add_child(field, "option", NULL);
+  lm_message_node_add_child(option, "value", room_jid);
+  desc = g_strdup_printf("%s on %s", nickname, room_jid);
+  lm_message_node_set_attribute(option, "label", desc);
+  g_free(desc);
+}
+
+static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h,
+                                                          LmConnection *c,
+                                                          LmMessage *m,
+                                                          gpointer ud)
+{
+  const char *action, *node;
+  char *sessionid;
+  LmMessage *iq;
+  LmMessageNode *command, *x;
+
+  x = lm_message_node_get_child(m->node, "command");
+  action = lm_message_node_get_attribute(x, "action");
+  node = lm_message_node_get_attribute(x, "node");
+  sessionid = (char*)lm_message_node_get_attribute(x, "sessionid");
+
+  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  command = lm_message_node_add_child(iq->node, "command", NULL);
+  lm_message_node_set_attributes(command,
+                                 "node", node,
+                                 "xmlns", NS_COMMANDS,
+                                 NULL);
+
+  if (!sessionid) {
+    LmMessageNode *field;
+
+    sessionid = generate_session_id("leave-groupchats");
+    lm_message_node_set_attribute(command, "sessionid", sessionid);
+    g_free(sessionid);
+    sessionid = NULL;
+    lm_message_node_set_attribute(command, "status", "executing");
+
+    x = lm_message_node_add_child(command, "x", NULL);
+    lm_message_node_set_attributes(x,
+                                   "type", "form",
+                                   "xmlns", "jabber:x:data",
+                                   NULL);
+
+    lm_message_node_add_child(x, "title", "Leave groupchat(s)");
+
+    lm_message_node_add_child(x, "instructions",
+                              "What groupchats do you want to leave?");
+
+    field = lm_message_node_add_child(x, "field", NULL);
+    lm_message_node_set_attributes(field,
+                                   "type", "hidden",
+                                   "var", "FORM_TYPE",
+                                   NULL);
+
+    lm_message_node_add_child(field, "value",
+                              "http://jabber.org/protocol/rc");
+
+    field = lm_message_node_add_child(x, "field", NULL);
+    lm_message_node_set_attributes(field,
+                                   "type", "list-multi",
+                                   "var", "groupchats",
+                                   "label", "Groupchats: ",
+                                   NULL);
+    lm_message_node_add_child(field, "required", NULL);
+
+    foreach_buddy(ROSTER_TYPE_ROOM, &_callback_foreach_buddy_groupchat, field);
+    //TODO: return an error if we are not connected to groupchats
+  } else if (action && !strcmp(action, "cancel")) {
+    lm_message_node_set_attribute(command, "status", "canceled");
+  } else  { // (if sessionid and not canceled)
+    LmMessageNode *form = lm_message_node_find_xmlns(x, "jabber:x:data");//TODO
+    if (form) {
+      LmMessageNode *field;
+
+      lm_message_node_set_attribute(command, "status", "completed");
+      //TODO: implement sth. like "field?var=groupchats" in xmlnode...
+      field  = lm_message_node_get_child(form, "field");
+      while (field && strcmp("groupchats",
+                             lm_message_node_get_attribute(field, "var")))
+        field = field->next;
+
+      if (field)
+        for (x = field->children ; x ; x = x->next)
+        {
+          if (!strcmp (x->name, "value")) {
+            GList* b = buddy_search_jid(lm_message_node_get_value(x));
+            if (b)
+              cmd_room_leave(b->data, "Requested by remote command");
+          }
+        }
+      lm_message_node_add_dataform_result(command,
+                                          "Groupchats have been left");
+    }
+  }
+  if (sessionid)
+    lm_message_node_set_attribute(command, "sessionid", sessionid);
+  lm_connection_send(c, iq, NULL);
+  lm_message_unref(iq);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+LmHandlerResult handle_iq_commands(LmMessageHandler *h,
+                                   LmConnection *c,
+                                   LmMessage *m, gpointer ud)
+{
+  const char *requester_jid = NULL;
+  LmMessageNode *cmd;
+  const struct adhoc_command *command;
+
+  // mcabber has only partial XEP-0146 support...
+  if (LM_MESSAGE_SUB_TYPE_SET != lm_message_get_sub_type(m))
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+  requester_jid = lm_message_get_from(m);
+
+  cmd = lm_message_node_get_child(m->node, "command");
+  if (jid_equal(lm_connection_get_jid(c), requester_jid)) {
+    const char *action, *node;
+    action = lm_message_node_get_attribute(cmd, "action");
+    node = lm_message_node_get_attribute(cmd, "node");
+    // action can be NULL, in which case it seems to take the default,
+    // ie execute
+    if (!action || !strcmp(action, "execute") || !strcmp(action, "cancel")
+        || !strcmp(action, "next") || !strcmp(action, "complete")) {
+      for (command = adhoc_command_list; command->name; command++) {
+        if (!strcmp(node, command->name))
+          command->callback(h, c, m, ud);
+      }
+      // "prev" action will get there, as we do not implement it,
+      // and do not authorize it
+    } else {
+      LmMessage *r;
+      LmMessageNode *err;
+      r = lm_message_new_iq_error(m, XMPP_ERROR_BAD_REQUEST);
+      err = lm_message_node_get_child(r->node, "error");
+      lm_message_node_set_attribute
+              (lm_message_node_add_child(err, "malformed-action", NULL),
+               "xmlns", NS_COMMANDS);
+      lm_connection_send(c, r, NULL);
+      lm_message_unref(r);
+    }
+  } else {
+    send_iq_error(c, m, XMPP_ERROR_FORBIDDEN);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+
+LmHandlerResult handle_iq_disco_items(LmMessageHandler *h,
+                                      LmConnection *c,
+                                      LmMessage *m, gpointer ud)
+{
+  LmMessageNode *query;
+  const char *node;
+  query = lm_message_node_get_child(m->node, "query");
+  node = lm_message_node_get_attribute(query, "node");
+  if (node) {
+    if (!strcmp(node, NS_COMMANDS)) {
+      return handle_iq_commands_list(NULL, c, m, ud);
+    } else {
+      send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED);
+    }
+  } else {
+    // not sure about this one
+    send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+
+void _disco_add_feature_helper(gpointer data, gpointer user_data)
+{
+  LmMessageNode *node = user_data;
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(node, "feature", NULL), "var", data);
+}
+
+//  disco_info_set_caps(ansquery, entitycaps)
+// Add features attributes to ansquery.  entitycaps should either be a
+// valid capabilities hash or NULL. If it is NULL, the node attribute won't
+// be added to the query child and Entity Capabilities will be announced
+// as a feature.
+// Please change the entity version string if you modify mcabber disco
+// source code, so that it doesn't conflict with the upstream client.
+static void disco_info_set_caps(LmMessageNode *ansquery,
+                                const char *entitycaps)
+{
+  if (entitycaps) {
+    char *eversion;
+    eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entitycaps);
+    lm_message_node_set_attribute(ansquery, "node", eversion);
+    g_free(eversion);
+  }
+
+  lm_message_node_set_attributes
+          (lm_message_node_add_child(ansquery, "identity", NULL),
+           "category", "client",
+           "name", PACKAGE_STRING,
+           "type", "pc",
+           NULL);
+
+  if (entitycaps)
+    caps_foreach_feature(entitycaps, _disco_add_feature_helper, ansquery);
+  else {
+    caps_foreach_feature(entity_version(xmpp_getstatus()),
+                         _disco_add_feature_helper,
+                         ansquery);
+    lm_message_node_set_attribute
+            (lm_message_node_add_child(ansquery, "feature", NULL),
+             "var", NS_CAPS);
+  }
+}
+
+LmHandlerResult handle_iq_disco_info(LmMessageHandler *h,
+                                     LmConnection *c,
+                                     LmMessage *m, gpointer ud)
+{
+  LmMessage *r;
+  LmMessageNode *query, *tmp;
+  const char *node = NULL;
+  const char *param = NULL;
+
+  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_RESULT)
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+
+  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  query = lm_message_node_add_child(r->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_DISCO_INFO);
+  tmp = lm_message_node_find_child(m->node, "query");
+  if (tmp) {
+    node = lm_message_node_get_attribute(tmp, "node");
+    param = node+strlen(MCABBER_CAPS_NODE)+1;
+  }
+  if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE))
+    disco_info_set_caps(query, param);  // client#version
+  else
+    // Basic discovery request
+    disco_info_set_caps(query, NULL);
+
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c,
+                                 LmMessage *m, gpointer ud)
+{
+  LmMessageNode *y;
+  const char *fjid, *name, *group, *sub, *ask;
+  char *cleanalias;
+  enum subscr esub;
+  int need_refresh = FALSE;
+  guint roster_type;
+
+  for (y = lm_message_node_find_child(lm_message_node_find_xmlns
+                                      (m->node, NS_ROSTER),
+                                      "item");
+       y;
+       y = y->next) {
+    char *name_tmp = NULL;
+
+    fjid = lm_message_node_get_attribute(y, "jid");
+    name = lm_message_node_get_attribute(y, "name");
+    sub = lm_message_node_get_attribute(y, "subscription");
+    ask = lm_message_node_get_attribute(y, "ask");
+
+    if (lm_message_node_find_child(y, "group"))
+      group = lm_message_node_get_value(lm_message_node_find_child(y, "group"));
+    else
+      group = NULL;
+
+    if (!fjid)
+      continue;
+
+    cleanalias = jidtodisp(fjid);
+
+    esub = sub_none;
+    if (sub) {
+      if (!strcmp(sub, "to"))          esub = sub_to;
+      else if (!strcmp(sub, "from"))   esub = sub_from;
+      else if (!strcmp(sub, "both"))   esub = sub_both;
+      else if (!strcmp(sub, "remove")) esub = sub_remove;
+    }
+
+    if (esub == sub_remove) {
+      roster_del_user(cleanalias);
+      scr_LogPrint(LPRINT_LOGNORM, "Buddy <%s> has been removed "
+                   "from the roster", cleanalias);
+      g_free(cleanalias);
+      need_refresh = TRUE;
+      continue;
+    }
+
+    if (ask && !strcmp(ask, "subscribe"))
+      esub |= sub_pending;
+
+    if (!name) {
+      if (!settings_opt_get_int("roster_hide_domain")) {
+        name = cleanalias;
+      } else {
+        char *p;
+        name = name_tmp = g_strdup(cleanalias);
+        p = strchr(name_tmp, JID_DOMAIN_SEPARATOR);
+        if (p)  *p = '\0';
+      }
+    }
+
+    // Tricky... :-\  My guess is that if there is no JID_DOMAIN_SEPARATOR,
+    // this is an agent.
+    if (strchr(cleanalias, JID_DOMAIN_SEPARATOR))
+      roster_type = ROSTER_TYPE_USER;
+    else
+      roster_type = ROSTER_TYPE_AGENT;
+
+    roster_add_user(cleanalias, name, group, roster_type, esub, 1);
+
+    g_free(name_tmp);
+    g_free(cleanalias);
+  }
+
+  buddylist_build();
+  update_roster = TRUE;
+  if (need_refresh)
+    scr_UpdateBuddyWindow();
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer ud)
+{
+  LmMessage *r;
+
+  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+double seconds_since_last_use(void)
+{
+  return difftime(time(NULL), iqlast);
+}
+
+LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer ud)
+{
+  LmMessage *r;
+  LmMessageNode *query;
+  char *seconds;
+
+  if (!settings_opt_get_int("iq_hide_requests")) {
+    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ last time request from <%s>",
+                 lm_message_get_from(m));
+  }
+
+  if (settings_opt_get_int("iq_last_disable") ||
+      (settings_opt_get_int("iq_last_disable_when_notavail") &&
+       xmpp_getstatus() == notavail))
+  {
+    send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+  }
+
+  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  query = lm_message_node_add_child(r->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_LAST);
+  seconds = g_strdup_printf("%.0f", seconds_since_last_use());
+  lm_message_node_set_attribute(query, "seconds", seconds);
+  g_free(seconds);
+
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer ud)
+{
+  LmMessage *r;
+  LmMessageNode *query;
+  char *os = NULL;
+  char *ver = mcabber_version();
+
+  if (!settings_opt_get_int("iq_hide_requests")) {
+    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ version request from <%s>",
+                 lm_message_get_from(m));
+  }
+  if (!settings_opt_get_int("iq_version_hide_os")) {
+    struct utsname osinfo;
+    uname(&osinfo);
+    os = g_strdup_printf("%s %s %s", osinfo.sysname, osinfo.release,
+                         osinfo.machine);
+  }
+
+  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+
+  query = lm_message_node_add_child(r->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_VERSION);
+
+  lm_message_node_add_child(query, "name", PACKAGE_NAME);
+  lm_message_node_add_child(query, "version", ver);
+  if (os) {
+    lm_message_node_add_child(query, "os", os);
+    g_free(os);
+  }
+
+  g_free(ver);
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+// This function borrows some code from the Pidgin project
+LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer ud)
+{
+  LmMessage *r;
+  LmMessageNode *query;
+  char *buf, *utf8_buf;
+  time_t now_t;
+  struct tm *now;
+
+  time(&now_t);
+
+  if (!settings_opt_get_int("iq_hide_requests")) {
+    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>",
+                 lm_message_get_from(m));
+  }
+
+  buf = g_new0(char, 512);
+
+  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  query = lm_message_node_add_child(r->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_TIME);
+
+  now = gmtime(&now_t);
+
+  strftime(buf, 512, "%Y%m%dT%T", now);
+  lm_message_node_add_child(query, "utc", buf);
+
+  now = localtime(&now_t);
+
+  strftime(buf, 512, "%Z", now);
+  if ((utf8_buf = to_utf8(buf))) {
+    lm_message_node_add_child(query, "tz", utf8_buf);
+    g_free(utf8_buf);
+  }
+
+  strftime(buf, 512, "%d %b %Y %T", now);
+  if ((utf8_buf = to_utf8(buf))) {
+    lm_message_node_add_child(query, "display", utf8_buf);
+    g_free(utf8_buf);
+  }
+
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+  g_free(buf);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+// This function borrows some code from the Pidgin project
+LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer ud)
+{
+  LmMessage *r;
+  LmMessageNode *query;
+  char *buf, *utf8_buf;
+  time_t now_t;
+  struct tm *now;
+  char const *sign;
+  int diff = 0;
+
+  time(&now_t);
+
+  if (!settings_opt_get_int("iq_hide_requests")) {
+    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>",
+                 lm_message_get_from(m));
+  }
+
+  buf = g_new0(char, 512);
+
+  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
+  query = lm_message_node_add_child(r->node, "time", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_XMPP_TIME);
+
+  now = localtime(&now_t);
+
+  if (now->tm_isdst >= 0) {
+#if defined HAVE_TM_GMTOFF
+    diff = now->tm_gmtoff;
+#elif defined HAVE_TIMEZONE
+    tzset();
+    diff = -timezone;
+#endif
+  }
+
+  if (diff < 0) {
+    sign = "-";
+    diff = -diff;
+  } else {
+    sign = "+";
+  }
+  diff /= 60;
+  snprintf(buf, 512, "%c%02d:%02d", *sign, diff / 60, diff % 60);
+  if ((utf8_buf = to_utf8(buf))) {
+    lm_message_node_add_child(query, "tzo", utf8_buf);
+    g_free(utf8_buf);
+  }
+
+  now = gmtime(&now_t);
+
+  strftime(buf, 512, "%Y-%m-%dT%TZ", now);
+  lm_message_node_add_child(query, "utc", buf);
+
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+  g_free(buf);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c,
+                                LmMessage *m, gpointer ud)
+{
+  send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_iq.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,32 @@
+#ifndef __MCABBER_XMPP_IQ_H__
+#define __MCABBER_XMPP_IQ_H__ 1
+
+LmHandlerResult handle_iq_commands(LmMessageHandler *h,
+                                   LmConnection *c,
+                                   LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_disco_items(LmMessageHandler *h,
+                                      LmConnection *c,
+                                      LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_disco_info(LmMessageHandler *h,
+                                     LmConnection *c,
+                                     LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c,
+                                 LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer ud);
+LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c,
+                                LmMessage *m, gpointer ud);
+
+void send_iq_error(LmConnection *c, LmMessage *m, guint error);
+
+#endif /* __MCABBER_XMPP_IQ_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_iqrequest.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,636 @@
+/*
+ * xmpp_iqrequest.c -- Jabber IQ request handling
+ *
+ * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "xmpp_helper.h"
+#include "xmpp_iq.h"
+#include "screen.h"
+#include "utils.h"
+#include "settings.h"
+#include "hooks.h"
+#include "hbuf.h"
+
+extern LmMessageNode *bookmarks;
+extern LmMessageNode *rosternotes;
+
+static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c,
+                                 LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data);
+static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data);
+
+static struct IqRequestHandlers
+{
+  const gchar *xmlns;
+  const gchar *querytag;
+  LmHandleMessageFunction handler;
+} iq_request_handlers[] = {
+  {NS_ROSTER, "query", &cb_roster},
+  {NS_VERSION,"query", &cb_version},
+  {NS_TIME,   "query", &cb_time},
+  {NS_LAST,   "query", &cb_last},
+  {NS_VCARD,  "vCard", &cb_vcard},
+  {NULL, NULL, NULL}
+};
+
+// Enum for vCard attributes
+enum vcard_attr {
+  vcard_home    = 1<<0,
+  vcard_work    = 1<<1,
+  vcard_postal  = 1<<2,
+  vcard_voice   = 1<<3,
+  vcard_fax     = 1<<4,
+  vcard_cell    = 1<<5,
+  vcard_inet    = 1<<6,
+  vcard_pref    = 1<<7,
+};
+
+// xmlns has to be a namespace from iq_request_handlers[].xmlns
+void xmpp_iq_request(const char *fulljid, const char *xmlns)
+{
+  LmMessage *iq;
+  LmMessageNode *query;
+  LmMessageHandler *handler;
+  int i;
+
+  iq = lm_message_new_with_sub_type(fulljid, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_GET);
+  for (i = 0; strcmp(iq_request_handlers[i].xmlns, xmlns) != 0 ; ++i)
+       ;
+  query = lm_message_node_add_child(iq->node,
+                                    iq_request_handlers[i].querytag,
+                                    NULL);
+  lm_message_node_set_attribute(query, "xmlns", xmlns);
+  handler = lm_message_handler_new(iq_request_handlers[i].handler,
+                                   NULL, FALSE);
+  lm_connection_send_with_reply(lconnection, iq, handler, NULL);
+  lm_message_handler_unref(handler);
+  lm_message_unref(iq);
+}
+
+//  This callback is reached when mcabber receives the first roster update
+// after the connection.
+static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c,
+                                 LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *x;
+  const char *ns;
+
+  // Only execute the hook if the roster has been successfully retrieved
+  if (lm_message_get_sub_type(m) != LM_MESSAGE_SUB_TYPE_RESULT)
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+  x = lm_message_node_find_child(m->node, "query");
+  if (!x)
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+
+  ns = lm_message_node_get_attribute(x, "xmlns");
+  if (ns && !strcmp(ns, NS_ROSTER))
+    handle_iq_roster(NULL, c, m, user_data);
+
+  // Post-login stuff
+  hook_execute_internal("hook-post-connect");
+
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c,
+                                  LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *tmp;
+  char *buf;
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result!");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:version result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // bjid should now really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  p = lm_message_node_get_child_value(ansqry, "name");
+  if (p) {
+    buf = g_strdup_printf("Name:    %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "version");
+  if (p) {
+    buf = g_strdup_printf("Version: %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "os");
+  if (p) {
+    buf = g_strdup_printf("OS:      %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *tmp;
+  char *buf;
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result!");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:time result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // bjid should now really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  p = lm_message_node_get_child_value(ansqry, "utc");
+  if (p) {
+    buf = g_strdup_printf("UTC:  %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "tz");
+  if (p) {
+    buf = g_strdup_printf("TZ:   %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  p = lm_message_node_get_child_value(ansqry, "display");
+  if (p) {
+    buf = g_strdup_printf("Time: %s", p);
+    scr_WriteIncomingMessage(bjid, buf,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_free(buf);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *buf, *tmp;
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result!");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:last result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // bjid should now really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  p = lm_message_node_get_attribute(ansqry, "seconds");
+  if (p) {
+    long int s;
+    GString *sbuf;
+    sbuf = g_string_new("Idle time: ");
+    s = atol(p);
+    // Days
+    if (s > 86400L) {
+      g_string_append_printf(sbuf, "%ldd ", s/86400L);
+      s %= 86400L;
+    }
+    // hh:mm:ss
+    g_string_append_printf(sbuf, "%02ld:", s/3600L);
+    s %= 3600L;
+    g_string_append_printf(sbuf, "%02ld:%02ld", s/60L, s%60L);
+    scr_WriteIncomingMessage(bjid, sbuf->str,
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+    g_string_free(sbuf, TRUE);
+  } else {
+    scr_WriteIncomingMessage(bjid, "No idle time reported.",
+                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+  }
+  p = lm_message_node_get_value(ansqry);
+  if (p) {
+    buf = g_strdup_printf("Status message: %s", p);
+    scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+    g_free(buf);
+  }
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void display_vcard_item(const char *bjid, const char *label,
+                               enum vcard_attr vcard_attrib, const char *text)
+{
+  char *buf;
+
+  if (!text || !bjid || !label)
+    return;
+
+  buf = g_strdup_printf("%s: %s%s%s%s%s%s%s%s%s%s", label,
+                        (vcard_attrib & vcard_home ? "[home]" : ""),
+                        (vcard_attrib & vcard_work ? "[work]" : ""),
+                        (vcard_attrib & vcard_postal ? "[postal]" : ""),
+                        (vcard_attrib & vcard_voice ? "[voice]" : ""),
+                        (vcard_attrib & vcard_fax  ? "[fax]"  : ""),
+                        (vcard_attrib & vcard_cell ? "[cell]" : ""),
+                        (vcard_attrib & vcard_inet ? "[inet]" : ""),
+                        (vcard_attrib & vcard_pref ? "[pref]" : ""),
+                        (vcard_attrib ? " " : ""),
+                        text);
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+  g_free(buf);
+}
+
+static void handle_vcard_node(const char *barejid, LmMessageNode *vcardnode)
+{
+  LmMessageNode *x;
+  const char *p;
+
+  for (x = vcardnode->children ; x; x = x->next) {
+    const char *data;
+    enum vcard_attr vcard_attrib = 0;
+
+    p = x->name;
+    data = lm_message_node_get_value(x);
+    if (!p || !data)
+      continue;
+
+    if (!strcmp(p, "FN"))
+      display_vcard_item(barejid, "Name", vcard_attrib, data);
+    else if (!strcmp(p, "NICKNAME"))
+      display_vcard_item(barejid, "Nickname", vcard_attrib, data);
+    else if (!strcmp(p, "URL"))
+      display_vcard_item(barejid, "URL", vcard_attrib, data);
+    else if (!strcmp(p, "BDAY"))
+      display_vcard_item(barejid, "Birthday", vcard_attrib, data);
+    else if (!strcmp(p, "TZ"))
+      display_vcard_item(barejid, "Timezone", vcard_attrib, data);
+    else if (!strcmp(p, "TITLE"))
+      display_vcard_item(barejid, "Title", vcard_attrib, data);
+    else if (!strcmp(p, "ROLE"))
+      display_vcard_item(barejid, "Role", vcard_attrib, data);
+    else if (!strcmp(p, "DESC"))
+      display_vcard_item(barejid, "Comment", vcard_attrib, data);
+    else if (!strcmp(p, "N")) {
+      data = lm_message_node_get_child_value(x, "FAMILY");
+      display_vcard_item(barejid, "Family Name", vcard_attrib, data);
+      data = lm_message_node_get_child_value(x, "GIVEN");
+      display_vcard_item(barejid, "Given Name", vcard_attrib, data);
+      data = lm_message_node_get_child_value(x, "MIDDLE");
+      display_vcard_item(barejid, "Middle Name", vcard_attrib, data);
+    } else if (!strcmp(p, "ORG")) {
+      data = lm_message_node_get_child_value(x, "ORGNAME");
+      display_vcard_item(barejid, "Organisation name", vcard_attrib, data);
+      data = lm_message_node_get_child_value(x, "ORGUNIT");
+      display_vcard_item(barejid, "Organisation unit", vcard_attrib, data);
+    } else {
+      // The HOME, WORK and PREF attributes are common to the remaining fields
+      // (ADR, TEL & EMAIL)
+      if (lm_message_node_get_child(x, "HOME"))
+        vcard_attrib |= vcard_home;
+      if (lm_message_node_get_child(x, "WORK"))
+        vcard_attrib |= vcard_work;
+      if (lm_message_node_get_child(x, "PREF"))
+        vcard_attrib |= vcard_pref;
+      if (!strcmp(p, "ADR")) {          // Address
+        if (lm_message_node_get_child(x, "POSTAL"))
+          vcard_attrib |= vcard_postal;
+        data = lm_message_node_get_child_value(x, "EXTADD");
+        display_vcard_item(barejid, "Addr (ext)", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "STREET");
+        display_vcard_item(barejid, "Street", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "LOCALITY");
+        display_vcard_item(barejid, "Locality", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "REGION");
+        display_vcard_item(barejid, "Region", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "PCODE");
+        display_vcard_item(barejid, "Postal code", vcard_attrib, data);
+        data = lm_message_node_get_child_value(x, "CTRY");
+        display_vcard_item(barejid, "Country", vcard_attrib, data);
+      } else if (!strcmp(p, "TEL")) {   // Telephone
+        data = lm_message_node_get_child_value(x, "NUMBER");
+        if (data) {
+          if (lm_message_node_get_child(x, "VOICE"))
+            vcard_attrib |= vcard_voice;
+          if (lm_message_node_get_child(x, "FAX"))
+            vcard_attrib |= vcard_fax;
+          if (lm_message_node_get_child(x, "CELL"))
+            vcard_attrib |= vcard_cell;
+          display_vcard_item(barejid, "Phone", vcard_attrib, data);
+        }
+      } else if (!strcmp(p, "EMAIL")) { // Email
+        if (lm_message_node_get_child(x, "INTERNET"))
+          vcard_attrib |= vcard_inet;
+        data = lm_message_node_get_child_value(x, "USERID");
+        display_vcard_item(barejid, "Email", vcard_attrib, data);
+      }
+    }
+  }
+}
+
+static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c,
+                               LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+  const char *p, *bjid;
+  char *buf, *tmp;
+
+  // Display IQ result sender...
+  p = lm_message_get_from(m);
+  if (!p) {
+    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:vCard result (no sender name).");
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+  }
+  bjid = p;
+
+  buf = g_strdup_printf("Received IQ:vCard result from <%s>", bjid);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+
+  // Get the vCard node
+  ansqry = lm_message_node_get_child(m->node, "vCard");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOGNORM, "Empty IQ:vCard result!");
+    g_free(buf);
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+  }
+
+  // bjid should really be the "bare JID", let's strip the resource
+  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
+  if (tmp) *tmp = '\0';
+
+  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
+  g_free(buf);
+
+  // Get result data...
+  handle_vcard_node(bjid, ansqry);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void storage_bookmarks_parse_conference(LmMessageNode *node)
+{
+  const char *fjid, *name, *autojoin;
+  const char *pstatus, *awhois;
+  char *bjid;
+  GSList *room_elt;
+
+  fjid = lm_message_node_get_attribute(node, "jid");
+  if (!fjid)
+    return;
+  name = lm_message_node_get_attribute(node, "name");
+  autojoin = lm_message_node_get_attribute(node, "autojoin");
+  awhois = lm_message_node_get_attribute(node, "autowhois");
+  pstatus = lm_message_node_get_child_value(node, "print_status");
+
+  bjid = jidtodisp(fjid); // Bare jid
+
+  // Make sure this is a room (it can be a conversion user->room)
+  room_elt = roster_find(bjid, jidsearch, 0);
+  if (!room_elt) {
+    room_elt = roster_add_user(bjid, name, NULL, ROSTER_TYPE_ROOM,
+                               sub_none, -1);
+  } else {
+    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
+    /*
+    // If the name is available, should we use it?
+    // I don't think so, it would be confusing because this item is already
+    // in the roster.
+    if (name)
+      buddy_setname(room_elt->data, name);
+    */
+  }
+
+  // Set the print_status and auto_whois values
+  if (pstatus) {
+    enum room_printstatus i;
+    for (i = status_none; i <= status_all; i++)
+      if (!strcasecmp(pstatus, strprintstatus[i]))
+        break;
+    if (i <= status_all)
+      buddy_setprintstatus(room_elt->data, i);
+  }
+  if (awhois) {
+    enum room_autowhois i = autowhois_default;
+    if (!strcmp(awhois, "1"))
+      i = autowhois_on;
+    else if (!strcmp(awhois, "0"))
+      i = autowhois_off;
+    if (i != autowhois_default)
+      buddy_setautowhois(room_elt->data, i);
+  }
+
+  // Is autojoin set?
+  // If it is, we'll look up for more information (nick? password?) and
+  // try to join the room.
+  if (autojoin && !strcmp(autojoin, "1")) {
+    const char *nick, *passwd;
+    char *tmpnick = NULL;
+    nick = lm_message_node_get_child_value(node, "nick");
+    passwd = lm_message_node_get_child_value(node, "password");
+    if (!nick || !*nick)
+      nick = tmpnick = default_muc_nickname(NULL);
+    // Let's join now
+    scr_LogPrint(LPRINT_LOGNORM, "Auto-join bookmark <%s>", bjid);
+    xmpp_room_join(bjid, nick, passwd);
+    g_free(tmpnick);
+  }
+  g_free(bjid);
+}
+
+static LmHandlerResult cb_storage_bookmarks(LmMessageHandler *h,
+                                            LmConnection *c,
+                                            LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *x, *ansqry;
+  char *p;
+
+  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) {
+    // No server support, or no bookmarks?
+    p = m->node->children->name;
+    if (p && !strcmp(p, "item-not-found")) {
+      // item-no-found means the server has Private Storage, but it's
+      // currently empty.
+      if (bookmarks)
+        lm_message_node_unref(bookmarks);
+      bookmarks = lm_message_node_new("storage", "storage:bookmarks");
+      // We return 0 so that the IQ error message be
+      // not displayed, as it isn't a real error.
+      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+    }
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error
+  }
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  ansqry = lm_message_node_get_child(ansqry, "storage");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! (storage:bookmarks)");
+    return 0;
+  }
+
+  // Walk through the storage tags
+  for (x = ansqry->children ; x; x = x->next) {
+    // If the current node is a conference item, parse it and update the roster
+    if (x->name && !strcmp(x->name, "conference"))
+      storage_bookmarks_parse_conference(x);
+  }
+  // "Copy" the bookmarks node
+  if (bookmarks)
+    lm_message_node_unref(bookmarks);
+  lm_message_node_deep_ref(ansqry);
+  bookmarks = ansqry;
+  return 0;
+}
+
+
+static LmHandlerResult cb_storage_rosternotes(LmMessageHandler *h,
+                                              LmConnection *c,
+                                              LmMessage *m, gpointer user_data)
+{
+  LmMessageNode *ansqry;
+
+  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) {
+    const char *p;
+    // No server support, or no roster notes?
+    p = m->node->children->name;
+    if (p && !strcmp(p, "item-not-found")) {
+      // item-no-found means the server has Private Storage, but it's
+      // currently empty.
+      if (rosternotes)
+        lm_message_node_unref(rosternotes);
+      rosternotes = lm_message_node_new("storage", "storage:rosternotes");
+      // We return 0 so that the IQ error message be
+      // not displayed, as it isn't a real error.
+      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+    }
+    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error
+  }
+
+  ansqry = lm_message_node_get_child(m->node, "query");
+  ansqry = lm_message_node_get_child(ansqry, "storage");
+  if (!ansqry) {
+    scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! "
+                 "(storage:rosternotes)");
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+  }
+  // Copy the rosternotes node
+  if (rosternotes)
+    lm_message_node_unref(rosternotes);
+  lm_message_node_deep_ref(ansqry);
+  rosternotes = ansqry;
+  return 0;
+}
+
+
+static struct IqRequestStorageHandlers
+{
+  const gchar *storagens;
+  LmHandleMessageFunction handler;
+} iq_request_storage_handlers[] = {
+  {"storage:rosternotes", &cb_storage_rosternotes},
+  {"storage:bookmarks", &cb_storage_bookmarks},
+  {NULL, NULL}
+};
+
+void xmpp_request_storage(const gchar *storage)
+{
+  LmMessage *iq;
+  LmMessageNode *query;
+  LmMessageHandler *handler;
+  int i;
+
+  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_GET);
+  query = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE);
+  lm_message_node_set_attribute(lm_message_node_add_child
+                                (query, "storage", NULL),
+                                "xmlns", storage);
+
+  for (i = 0;
+       strcmp(iq_request_storage_handlers[i].storagens, storage) != 0;
+       ++i) ;
+
+  handler = lm_message_handler_new(iq_request_storage_handlers[i].handler,
+                                   NULL, FALSE);
+  lm_connection_send_with_reply(lconnection, iq, handler, NULL);
+  lm_message_handler_unref(handler);
+  lm_message_unref(iq);
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_iqrequest.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,8 @@
+#ifndef __MCABBER_XMPP_IQREQUEST_H__
+#define __MCABBER_XMPP_IQREQUEST_H__ 1
+
+void xmpp_iq_request(const char *fulljid, const char *xmlns);
+
+#endif /* __MCABBER_XMPP_IQREQUEST_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_muc.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,715 @@
+/*
+ * xmpp_muc.c   -- Jabber MUC protocol handling
+ *
+ * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "xmpp_helper.h"
+#include "events.h"
+#include "hooks.h"
+#include "screen.h"
+#include "hbuf.h"
+#include "roster.h"
+#include "commands.h"
+#include "settings.h"
+#include "utils.h"
+#include "histolog.h"
+
+extern enum imstatus mystatus;
+extern gchar *mystatusmsg;
+
+static void decline_invitation(event_muc_invitation *invitation, char *reason)
+{
+  // cut and paste from xmpp_room_invite
+  LmMessage *m;
+  LmMessageNode *x, *y;
+
+  if (!invitation) return;
+  if (!invitation->to || !invitation->from) return;
+
+  m = lm_message_new(invitation->to, LM_MESSAGE_TYPE_MESSAGE);
+
+  x = lm_message_node_add_child(m->node, "x", NULL);
+  lm_message_node_set_attribute(x, "xmlns",
+                                "http://jabber.org/protocol/muc#user");
+
+  y = lm_message_node_add_child(x, "decline", NULL);
+  lm_message_node_set_attribute(y, "to", invitation->from);
+
+  if (reason)
+    lm_message_node_add_child(y, "reason", reason);
+
+  lm_connection_send(lconnection, m, NULL);
+  lm_message_unref(m);
+}
+
+static int evscallback_invitation(eviqs *evp, guint evcontext)
+{
+  event_muc_invitation *invitation = evp->data;
+
+  // Sanity check
+  if (!invitation) {
+    // Shouldn't happen.
+    scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback.");
+    return 0;
+  }
+
+  if (evcontext == EVS_CONTEXT_TIMEOUT) {
+    scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.", evp->id);
+    goto evscallback_invitation_free;
+  }
+  if (evcontext == EVS_CONTEXT_CANCEL) {
+    scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id);
+    goto evscallback_invitation_free;
+  }
+  if (!(evcontext & EVS_CONTEXT_USER))
+    goto evscallback_invitation_free;
+  // Ok, let's work now.
+  // evcontext: 0, 1 == reject, accept
+
+  if (evcontext & ~EVS_CONTEXT_USER) {
+    char *nickname = default_muc_nickname(invitation->to);
+    xmpp_room_join(invitation->to, nickname, invitation->passwd);
+    g_free(nickname);
+  } else {
+    scr_LogPrint(LPRINT_LOGNORM, "Invitation to %s refused.", invitation->to);
+    decline_invitation(invitation, NULL);
+  }
+
+evscallback_invitation_free:
+  g_free(invitation->to);
+  g_free(invitation->from);
+  g_free(invitation->passwd);
+  g_free(invitation->reason);
+  g_free(invitation);
+  evp->data = NULL;
+  return 0;
+}
+
+// Join a MUC room
+void xmpp_room_join(const char *room, const char *nickname, const char *passwd)
+{
+  LmMessage *x;
+  LmMessageNode *y;
+  gchar *roomid;
+  GSList *room_elt;
+
+  if (!lm_connection_is_authenticated(lconnection) || !room) return;
+  if (!nickname)        return;
+
+  roomid = g_strdup_printf("%s/%s", room, nickname);
+  if (check_jid_syntax(roomid)) {
+    scr_LogPrint(LPRINT_NORMAL, "<%s/%s> is not a valid Jabber room", room,
+                 nickname);
+    g_free(roomid);
+    return;
+  }
+
+  room_elt = roster_find(room, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
+  // Add room if it doesn't already exist
+  if (!room_elt) {
+    room_elt = roster_add_user(room, NULL, NULL, ROSTER_TYPE_ROOM,
+                               sub_none, -1);
+  } else {
+    // Make sure this is a room (it can be a conversion user->room)
+    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
+  }
+  // If insideroom is TRUE, this is a nickname change and we don't care here
+  if (!buddy_getinsideroom(room_elt->data)) {
+    // We're trying to enter a room
+    buddy_setnickname(room_elt->data, nickname);
+  }
+
+  // Send the XML request
+  lm_message_new(roomid, LM_MESSAGE_TYPE_PRESENCE);
+
+  x = lm_message_new_presence(mystatus, roomid, mystatusmsg);
+  y = lm_message_node_add_child(x->node, "x", NULL);
+  lm_message_node_set_attribute(y, "xmlns", "http://jabber.org/protocol/muc");
+  if (passwd)
+    lm_message_node_add_child(y, "password", passwd);
+
+  lm_connection_send(lconnection, x, NULL);
+  lm_message_unref(x);
+  g_free(roomid);
+}
+
+// Invite a user to a MUC room
+// room syntax: "room@server"
+// reason can be null.
+void xmpp_room_invite(const char *room, const char *fjid, const char *reason)
+{
+  LmMessage *msg;
+  LmMessageNode *x, *y;
+
+  if (!lm_connection_is_authenticated(lconnection) || !room || !fjid) return;
+
+  msg = lm_message_new(room, LM_MESSAGE_TYPE_MESSAGE);
+
+  x = lm_message_node_add_child(msg->node, "x", NULL);
+  lm_message_node_set_attribute(x, "xmlns",
+                                "http://jabber.org/protocol/muc#user");
+
+  y = lm_message_node_add_child(x, "invite", NULL);
+  lm_message_node_set_attribute(y, "to", fjid);
+
+  if (reason)
+    lm_message_node_add_child(y, "reason", reason);
+
+  lm_connection_send(lconnection, msg, NULL);
+  lm_message_unref(msg);
+}
+
+int xmpp_room_setattrib(const char *roomid, const char *fjid,
+                        const char *nick, struct role_affil ra,
+                        const char *reason)
+{
+  LmMessage *iq;
+  LmMessageNode *query, *x;
+
+  if (!lm_connection_is_authenticated(lconnection) || !roomid) return 1;
+  if (!fjid && !nick) return 1;
+
+  if (check_jid_syntax((char*)roomid)) {
+    scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", roomid);
+    return 1;
+  }
+  if (fjid && check_jid_syntax((char*)fjid)) {
+    scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", fjid);
+    return 1;
+  }
+
+  if (ra.type == type_affil && ra.val.affil == affil_outcast && !fjid)
+    return 1; // Shouldn't happen (jid mandatory when banning)
+
+  iq = lm_message_new_with_sub_type(roomid, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_SET);
+  query = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns",
+                                "http://jabber.org/protocol/muc#admin");
+  x = lm_message_node_add_child(query, "item", NULL);
+
+  if (fjid) {
+    lm_message_node_set_attribute(x, "jid", fjid);
+  } else { // nickname
+    lm_message_node_set_attribute(x, "nick", nick);
+  }
+
+  if (ra.type == type_affil)
+    lm_message_node_set_attribute(x, "affiliation", straffil[ra.val.affil]);
+  else if (ra.type == type_role)
+    lm_message_node_set_attribute(x, "role", strrole[ra.val.role]);
+
+  if (reason)
+    lm_message_node_add_child(x, "reason", reason);
+
+  lm_connection_send(lconnection, iq, NULL);
+  lm_message_unref(iq);
+
+  return 0;
+}
+
+// Unlock a MUC room
+// room syntax: "room@server"
+void xmpp_room_unlock(const char *room)
+{
+  LmMessageNode *node;
+  LmMessage *iq;
+
+  if (!lm_connection_is_authenticated(lconnection) || !room) return;
+
+  iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_SET);
+
+  node = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(node, "xmlns",
+                                "http://jabber.org/protocol/muc#owner");
+  node = lm_message_node_add_child(node, "x", NULL);
+  lm_message_node_set_attributes(node, "xmlns", "jabber:x:data",
+                                 "type", "submit", NULL);
+
+  lm_connection_send(lconnection, iq, NULL);
+  lm_message_unref(iq);
+}
+
+// Destroy a MUC room
+// room syntax: "room@server"
+void xmpp_room_destroy(const char *room, const char *venue, const char *reason)
+{
+  LmMessage *iq;
+  LmMessageNode *query, *x;
+
+  if (!lm_connection_is_authenticated(lconnection) || !room) return;
+
+  iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ,
+                                    LM_MESSAGE_SUB_TYPE_SET);
+  query = lm_message_node_add_child(iq->node, "query", NULL);
+  lm_message_node_set_attribute(query, "xmlns",
+                                "http://jabber.org/protocol/muc#owner");
+  x = lm_message_node_add_child(query, "destroy", NULL);
+
+  if (venue && *venue)
+    lm_message_node_set_attribute(x, "jid", venue);
+
+  if (reason)
+    lm_message_node_add_child(x, "reason", reason);
+
+  lm_connection_send(lconnection, iq, NULL);
+  lm_message_unref(iq);
+}
+
+//  muc_get_item_info(...)
+// Get room member's information from xmlndata.
+// The variables must be initialized before calling this function,
+// because they are not touched if the relevant information is missing.
+static void muc_get_item_info(const char *from, LmMessageNode *xmldata,
+                              enum imrole *mbrole, enum imaffiliation *mbaffil,
+                              const char **mbjid, const char **mbnick,
+                              const char **actorjid, const char **reason)
+{
+  LmMessageNode *y, *z;
+  const char *p;
+
+  y = lm_message_node_find_child(xmldata, "item");
+  if (!y)
+    return;
+
+  p = lm_message_node_get_attribute(y, "affiliation");
+  if (p) {
+    if (!strcmp(p, "owner"))        *mbaffil = affil_owner;
+    else if (!strcmp(p, "admin"))   *mbaffil = affil_admin;
+    else if (!strcmp(p, "member"))  *mbaffil = affil_member;
+    else if (!strcmp(p, "outcast")) *mbaffil = affil_outcast;
+    else if (!strcmp(p, "none"))    *mbaffil = affil_none;
+    else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown affiliation \"%s\"",
+                      from, p);
+  }
+  p = lm_message_node_get_attribute(y, "role");
+  if (p) {
+    if (!strcmp(p, "moderator"))        *mbrole = role_moderator;
+    else if (!strcmp(p, "participant")) *mbrole = role_participant;
+    else if (!strcmp(p, "visitor"))     *mbrole = role_visitor;
+    else if (!strcmp(p, "none"))        *mbrole = role_none;
+    else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown role \"%s\"",
+                      from, p);
+  }
+  *mbjid = lm_message_node_get_attribute(y, "jid");
+  *mbnick = lm_message_node_get_attribute(y, "nick");
+  // For kick/ban, there can be actor and reason tags
+  *reason = lm_message_node_get_child_value(y, "reason");
+  z = lm_message_node_find_child(y, "actor");
+  if (z)
+    *actorjid = lm_message_node_get_attribute(z, "jid");
+}
+
+//  muc_handle_join(...)
+// Handle a join event in a MUC room.
+// This function will return the new_member value TRUE if somebody else joins
+// the room (and FALSE if _we_ are joining the room).
+static bool muc_handle_join(const GSList *room_elt, const char *rname,
+                            const char *roomjid, const char *ournick,
+                            enum room_printstatus printstatus,
+                            time_t usttime, int log_muc_conf)
+{
+  bool new_member = FALSE; // True if somebody else joins the room (not us)
+  gchar *mbuf;
+
+  if (!buddy_getinsideroom(room_elt->data)) {
+    // We weren't inside the room yet.  Now we are.
+    // However, this could be a presence packet from another room member
+
+    buddy_setinsideroom(room_elt->data, TRUE);
+    // Set the message flag unless we're already in the room buffer window
+    scr_setmsgflag_if_needed(roomjid, FALSE);
+    // Add a message to the tracelog file
+    mbuf = g_strdup_printf("You have joined %s as \"%s\"", roomjid, ournick);
+    scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
+    g_free(mbuf);
+    mbuf = g_strdup_printf("You have joined as \"%s\"", ournick);
+
+    // The 1st presence message could be for another room member
+    if (strcmp(ournick, rname)) {
+      // Display current mbuf and create a new message for the member
+      // Note: the usttime timestamp is related to the other member,
+      //       so we use 0 here.
+      scr_WriteIncomingMessage(roomjid, mbuf, 0,
+                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
+      if (log_muc_conf)
+        hlog_write_message(roomjid, 0, -1, mbuf);
+      g_free(mbuf);
+      if (printstatus != status_none)
+        mbuf = g_strdup_printf("%s has joined", rname);
+      else
+        mbuf = NULL;
+      new_member = TRUE;
+    }
+  } else {
+    mbuf = NULL;
+    if (strcmp(ournick, rname)) {
+      if (printstatus != status_none)
+        mbuf = g_strdup_printf("%s has joined", rname);
+      new_member = TRUE;
+    }
+  }
+
+  if (mbuf) {
+    guint msgflags = HBB_PREFIX_INFO;
+    if (!settings_opt_get_int("muc_flag_joins"))
+      msgflags |= HBB_PREFIX_NOFLAG;
+    scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0);
+    if (log_muc_conf)
+      hlog_write_message(roomjid, 0, -1, mbuf);
+    g_free(mbuf);
+  }
+
+  return new_member;
+}
+
+void handle_muc_presence(const char *from, LmMessageNode *xmldata,
+                         const char *roomjid, const char *rname,
+                         enum imstatus ust, const char *ustmsg,
+                         time_t usttime, char bpprio)
+{
+  LmMessageNode *y;
+  const char *p;
+  char *mbuf;
+  const char *ournick;
+  enum imrole mbrole = role_none;
+  enum imaffiliation mbaffil = affil_none;
+  enum room_printstatus printstatus;
+  enum room_autowhois autowhois;
+  const char *mbjid = NULL, *mbnick = NULL;
+  const char *actorjid = NULL, *reason = NULL;
+  bool new_member = FALSE; // True if somebody else joins the room (not us)
+  guint statuscode = 0;
+  guint nickchange = 0;
+  GSList *room_elt;
+  int log_muc_conf;
+  guint msgflags;
+
+  log_muc_conf = settings_opt_get_int("log_muc_conf");
+
+  room_elt = roster_find(roomjid, jidsearch, 0);
+  if (!room_elt) {
+    // Add room if it doesn't already exist
+    // It shouldn't happen, there is probably something wrong (server or
+    // network issue?)
+    room_elt = roster_add_user(roomjid, NULL, NULL, ROSTER_TYPE_ROOM,
+                               sub_none, -1);
+    scr_LogPrint(LPRINT_LOGNORM, "Strange MUC presence message");
+  } else {
+    // Make sure this is a room (it can be a conversion user->room)
+    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
+  }
+
+  // Get room member's information
+  muc_get_item_info(from, xmldata, &mbrole, &mbaffil, &mbjid, &mbnick,
+                    &actorjid, &reason);
+
+  // Get our room nickname
+  ournick = buddy_getnickname(room_elt->data);
+
+  if (!ournick) {
+    // It shouldn't happen, probably a server issue
+    mbuf = g_strdup_printf("Unexpected groupchat packet!");
+
+    scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
+    scr_WriteIncomingMessage(roomjid, mbuf, 0, HBB_PREFIX_INFO, 0);
+    g_free(mbuf);
+    // Send back an unavailable packet
+    xmpp_setstatus(offline, roomjid, "", TRUE);
+    scr_DrawRoster();
+    return;
+  }
+
+  // Get the status code
+  // 201: a room has been created
+  // 301: the user has been banned from the room
+  // 303: new room nickname
+  // 307: the user has been kicked from the room
+  // 321,322,332: the user has been removed from the room
+  y = lm_message_node_find_child(xmldata, "status");
+  if (y) {
+    p = lm_message_node_get_attribute(y, "code");
+    if (p)
+      statuscode = atoi(p);
+  }
+
+  // Get the room's "print_status" settings
+  printstatus = buddy_getprintstatus(room_elt->data);
+  if (printstatus == status_default) {
+    printstatus = (guint) settings_opt_get_int("muc_print_status");
+    if (printstatus > 3)
+      printstatus = status_default;
+  }
+
+  // A new room has been created; accept MUC default config
+  if (statuscode == 201)
+    xmpp_room_unlock(roomjid);
+
+  // Check for nickname change
+  if (statuscode == 303 && mbnick) {
+    mbuf = g_strdup_printf("%s is now known as %s", rname, mbnick);
+    scr_WriteIncomingMessage(roomjid, mbuf, usttime,
+                             HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
+    if (log_muc_conf)
+      hlog_write_message(roomjid, 0, -1, mbuf);
+    g_free(mbuf);
+    buddy_resource_setname(room_elt->data, rname, mbnick);
+    // Maybe it's _our_ nickname...
+    if (ournick && !strcmp(rname, ournick))
+      buddy_setnickname(room_elt->data, mbnick);
+    nickchange = TRUE;
+  }
+
+  // Check for departure/arrival
+  if (!mbnick && ust == offline) {
+    // Somebody is leaving
+    enum { leave=0, kick, ban } how = leave;
+    bool we_left = FALSE;
+
+    if (statuscode == 307)
+      how = kick;
+    else if (statuscode == 301)
+      how = ban;
+
+    // If this is a leave, check if it is ourself
+    if (ournick && !strcmp(rname, ournick)) {
+      we_left = TRUE; // _We_ have left! (kicked, banned, etc.)
+      buddy_setinsideroom(room_elt->data, FALSE);
+      buddy_setnickname(room_elt->data, NULL);
+      buddy_del_all_resources(room_elt->data);
+      buddy_settopic(room_elt->data, NULL);
+      scr_UpdateChatStatus(FALSE);
+      update_roster = TRUE;
+    }
+
+    // The message depends on _who_ left, and _how_
+    if (how) {
+      gchar *mbuf_end;
+      // Forced leave
+      if (actorjid) {
+        mbuf_end = g_strdup_printf("%s from %s by <%s>.\nReason: %s",
+                                   (how == ban ? "banned" : "kicked"),
+                                   roomjid, actorjid, reason);
+      } else {
+        mbuf_end = g_strdup_printf("%s from %s.",
+                                   (how == ban ? "banned" : "kicked"),
+                                   roomjid);
+      }
+      if (we_left)
+        mbuf = g_strdup_printf("You have been %s", mbuf_end);
+      else
+        mbuf = g_strdup_printf("%s has been %s", rname, mbuf_end);
+
+      g_free(mbuf_end);
+    } else {
+      // Natural leave
+      if (we_left) {
+        LmMessageNode *destroynode = lm_message_node_find_child(xmldata,
+                                                                "destroy");
+        if (destroynode) {
+          if ((reason = lm_message_node_get_child_value(destroynode,
+                                                       "reason"))) {
+            mbuf = g_strdup_printf("You have left %s, "
+                                   "the room has been destroyed: %s",
+                                   roomjid, reason);
+          } else {
+            mbuf = g_strdup_printf("You have left %s, "
+                                   "the room has been destroyed", roomjid);
+          }
+        } else {
+          mbuf = g_strdup_printf("You have left %s", roomjid);
+        }
+      } else {
+        if (ust != offline) {
+          // This can happen when a network failure occurs,
+          // this isn't an official leave but the user isn't there anymore.
+          mbuf = g_strdup_printf("%s has disappeared!", rname);
+          ust = offline;
+        } else {
+          if (ustmsg)
+            mbuf = g_strdup_printf("%s has left: %s", rname, ustmsg);
+          else
+            mbuf = g_strdup_printf("%s has left", rname);
+        }
+      }
+    }
+
+    // Display the mbuf message if we're concerned
+    // or if the print_status isn't set to none.
+    if (we_left || printstatus != status_none) {
+      msgflags = HBB_PREFIX_INFO;
+      if (!we_left && settings_opt_get_int("muc_flag_joins") != 2)
+        msgflags |= HBB_PREFIX_NOFLAG;
+      scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0);
+    }
+
+    if (log_muc_conf)
+      hlog_write_message(roomjid, 0, -1, mbuf);
+
+    if (we_left) {
+      scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
+      g_free(mbuf);
+      return;
+    }
+    g_free(mbuf);
+  } else if (buddy_getstatus(room_elt->data, rname) == offline &&
+             ust != offline) {
+    // Somebody is joining
+    new_member = muc_handle_join(room_elt, rname, roomjid, ournick,
+                                 printstatus, usttime, log_muc_conf);
+  } else {
+    // This is a simple member status change
+
+    if (printstatus == status_all && !nickchange) {
+      mbuf = g_strdup_printf("Member status has changed: %s [%c] %s", rname,
+                             imstatus2char[ust], ((ustmsg) ? ustmsg : ""));
+      scr_WriteIncomingMessage(roomjid, mbuf, usttime,
+                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
+      g_free(mbuf);
+    }
+  }
+
+  // Sanity check, shouldn't happen...
+  if (!rname)
+    return;
+
+  // Update room member status
+  roster_setstatus(roomjid, rname, bpprio, ust, ustmsg, usttime,
+                   mbrole, mbaffil, mbjid);
+
+  autowhois = buddy_getautowhois(room_elt->data);
+  if (autowhois == autowhois_default)
+    autowhois = (settings_opt_get_int("muc_auto_whois") ?
+                 autowhois_on : autowhois_off);
+
+  if (new_member && autowhois == autowhois_on) {
+    // FIXME: This will fail for some UTF-8 nicknames.
+    gchar *joiner_nick = from_utf8(rname);
+    cmd_room_whois(room_elt->data, joiner_nick, FALSE);
+    g_free(joiner_nick);
+  }
+
+  scr_DrawRoster();
+}
+
+void roompresence(gpointer room, void *presencedata)
+{
+  const char *bjid;
+  const char *nickname;
+  char *to;
+  struct T_presence *pres = presencedata;
+
+  if (!buddy_getinsideroom(room))
+    return;
+
+  bjid = buddy_getjid(room);
+  if (!bjid) return;
+  nickname = buddy_getnickname(room);
+  if (!nickname) return;
+
+  to = g_strdup_printf("%s/%s", bjid, nickname);
+  xmpp_setstatus(pres->st, to, pres->msg, TRUE);
+  g_free(to);
+}
+
+//  got_invite(from, to, reason, passwd)
+// This function should be called when receiving an invitation from user
+// "from", to enter the room "to".  Optional reason and room password can
+// be provided.
+static void got_invite(const char* from, const char *to, const char* reason,
+                       const char* passwd)
+{
+  eviqs *evn;
+  event_muc_invitation *invitation;
+  GString *sbuf;
+  char *barejid;
+  GSList *room_elt;
+
+  sbuf = g_string_new("");
+  if (reason) {
+    g_string_printf(sbuf,
+                    "Received an invitation to <%s>, from <%s>, reason: %s",
+                    to, from, reason);
+  } else {
+    g_string_printf(sbuf, "Received an invitation to <%s>, from <%s>",
+                    to, from);
+  }
+
+  barejid = jidtodisp(from);
+  scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str);
+
+  evn = evs_new(EVS_TYPE_INVITATION, EVS_MAX_TIMEOUT);
+  if (evn) {
+    evn->callback = &evscallback_invitation;
+    invitation = g_new(event_muc_invitation, 1);
+    invitation->to = g_strdup(to);
+    invitation->from = g_strdup(from);
+    invitation->passwd = g_strdup(passwd);
+    invitation->reason = g_strdup(reason);
+    evn->data = invitation;
+    evn->desc = g_strdup_printf("<%s> invites you to %s ", from, to);
+    g_string_printf(sbuf, "Please use /event %s accept|reject", evn->id);
+  } else {
+    g_string_printf(sbuf, "Unable to create a new event!");
+  }
+  scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str);
+  g_string_free(sbuf, TRUE);
+  g_free(barejid);
+
+  // Make sure the MUC room barejid is a room in the roster
+  barejid = jidtodisp(to);
+  room_elt = roster_find(barejid, jidsearch, 0);
+  if (room_elt)
+    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
+
+  g_free(barejid);
+}
+
+
+// Specific MUC message handling (for example invitation processing)
+void got_muc_message(const char *from, LmMessageNode *x)
+{
+  LmMessageNode *invite = lm_message_node_get_child(x, "invite");
+  if (invite)
+  {
+    const char *invite_from;
+    const char *reason = NULL;
+    const char *password = NULL;
+
+    invite_from = lm_message_node_get_attribute(invite, "from");
+    reason = lm_message_node_get_child_value(invite, "reason");
+    password = lm_message_node_get_child_value(invite, "password");
+    if (invite_from)
+      got_invite(invite_from, from, reason, password);
+  }
+  // TODO
+  // handle status code = 100 ( not anonymous )
+  // handle status code = 170 ( changement de config )
+  // 10.2.1 Notification of Configuration Changes
+  // declined invitation
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_muc.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,13 @@
+#ifndef __MCABBER_XMPP_MUC_H__
+#define __MCABBER_XMPP_MUC_H__ 1
+
+void roompresence(gpointer room, void *presencedata);
+void got_muc_message(const char *from, LmMessageNode *x);
+void handle_muc_presence(const char *from, LmMessageNode * xmldata,
+                         const char *roomjid, const char *rname,
+                         enum imstatus ust, const char *ustmsg,
+                         time_t usttime, char bpprio);
+
+#endif /* __MCABBER_XMPP_MUC_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_s10n.c	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,90 @@
+/*
+ * xmpp_s10n.c  -- Jabber presence subscription handling
+ *
+ * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include "xmpp_helper.h"
+#include "events.h"
+#include "screen.h"
+#include "hbuf.h"
+#include "settings.h"
+
+//  xmpp_send_s10n(jid, subtype)
+// Send a s10n message with the passed subtype
+void xmpp_send_s10n(const char *bjid, LmMessageSubType type)
+{
+  LmMessage *x = lm_message_new_with_sub_type(bjid,
+                                              LM_MESSAGE_TYPE_PRESENCE,
+                                              type);
+  lm_connection_send(lconnection, x, NULL);
+  lm_message_unref(x);
+}
+
+int evscallback_subscription(eviqs *evp, guint evcontext)
+{
+  char *barejid;
+  char *buf;
+
+  if (evcontext == EVS_CONTEXT_TIMEOUT) {
+    scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.",
+                 evp->id);
+    return 0;
+  }
+  if (evcontext == EVS_CONTEXT_CANCEL) {
+    scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id);
+    return 0;
+  }
+  if (!(evcontext & EVS_CONTEXT_USER))
+    return 0;
+
+  // Sanity check
+  if (!evp->data) {
+    // Shouldn't happen, data should be set to the barejid.
+    scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback.");
+    return 0;
+  }
+
+  // Ok, let's work now.
+  // evcontext: 0, 1 == reject, accept
+
+  barejid = evp->data;
+
+  if (evcontext & ~EVS_CONTEXT_USER) {
+    // Accept subscription request
+    xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_SUBSCRIBED);
+    buf = g_strdup_printf("<%s> is allowed to receive your presence updates",
+                          barejid);
+  } else {
+    // Reject subscription request
+    xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);
+    buf = g_strdup_printf("<%s> won't receive your presence updates", barejid);
+    if (settings_opt_get_int("delete_on_reject")) {
+      // Remove the buddy from the roster if there is no current subscription
+      if (roster_getsubscription(barejid) == sub_none)
+        xmpp_delbuddy(barejid);
+    }
+  }
+  scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0);
+  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
+  g_free(buf);
+  return 0;
+}
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/mcabber/xmpp_s10n.h	Mon Jan 18 15:36:19 2010 +0200
@@ -0,0 +1,10 @@
+#ifndef __MCABBER_XMPP_S10N_H__
+#define __MCABBER_XMPP_S10N_H__ 1
+
+#include <mcabber/events.h>
+
+int evscallback_subscription(eviqs *evp, guint evcontext);
+
+#endif /* __MCABBER_XMPP_S10N_H__ */
+
+/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/COPYING	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,344 +0,0 @@
-Specific permission is granted for the GPLed code in this distribution
-to be linked to OpenSSL without invoking GPL clause 2(b).
-----------------------------------------------------------------------
-
-		    GNU GENERAL PUBLIC LICENSE
-		       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  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 Library 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 Library General
-Public License instead of this License.
--- a/mcabber/src/Makefile.am	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-bin_PROGRAMS = mcabber
-mcabber_SOURCES = main.c main.h roster.c roster.h events.c events.h \
-		  commands.c commands.h compl.c compl.h \
-		  hbuf.c hbuf.h screen.c screen.h logprint.h \
-		  settings.c settings.h hooks.c hooks.h utf8.c utf8.h \
-		  histolog.c histolog.h utils.c utils.h pgp.c pgp.h \
-		  xmpp.c xmpp.h xmpp_helper.c xmpp_helper.h xmpp_defines.h \
-		  xmpp_iq.c xmpp_iq.h xmpp_iqrequest.c xmpp_iqrequest.h \
-		  xmpp_muc.c xmpp_muc.h xmpp_s10n.c xmpp_s10n.h \
-		  caps.c caps.h fifo.c fifo.h help.c help.h
-
-if OTR
-mcabber_SOURCES += otr.c otr.h nohtml.c nohtml.h
-endif
-
-LDADD = $(GLIB_LIBS) $(LOUDMOUTH_LIBS) $(GPGME_LIBS) $(LIBOTR_LIBS) \
-				$(ENCHANT_LIBS)
-
-AM_CPPFLAGS = $(GLIB_CFLAGS) $(LOUDMOUTH_CFLAGS) $(GPGME_CFLAGS) \
-				$(LIBOTR_CFLAGS) $(ENCHANT_CFLAGS)
-
-CLEANFILES = hgcset.h
-
-if HGCSET
-BUILT_SOURCES = hgcset.h
-
-hgcset.h:
-	../hgcset.sh
-
-.PHONY: hgcset.h
-endif
-
-#SUBDIRS =
--- a/mcabber/src/caps.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-/*
- * caps.c       -- Entity Capabilities Cache for mcabber
- *
- * Copyright (C) 2008 Frank Zschockelt <mcabber@freakysoft.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <glib.h>
-
-typedef struct {
-  char *category;
-  char *name;
-  char *type;
-  GHashTable *features;
-} caps;
-
-static GHashTable *caps_cache = NULL;
-
-void caps_destroy(gpointer data)
-{
-  caps *c = data;
-  g_free(c->category);
-  g_free(c->name);
-  g_free(c->type);
-  g_hash_table_destroy(c->features);
-  g_free(c);
-}
-
-void caps_init(void)
-{
-  if (!caps_cache)
-    caps_cache = g_hash_table_new_full(g_str_hash, g_str_equal,
-                                       g_free, caps_destroy);
-}
-
-void caps_free(void)
-{
-  if (caps_cache) {
-    g_hash_table_destroy(caps_cache);
-    caps_cache = NULL;
-  }
-}
-
-void caps_add(char *hash)
-{
-  if (!hash)
-    return;
-  caps *c = g_new0(caps, 1);
-  c->features = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
-  g_hash_table_insert(caps_cache, g_strdup(hash), c);
-}
-
-int caps_has_hash(const char *hash)
-{
-  return (hash != NULL && (g_hash_table_lookup(caps_cache, hash) != NULL));
-}
-
-void caps_set_identity(char *hash,
-                       const char *category,
-                       const char *name,
-                       const char *type)
-{
-  caps *c;
-  if (!hash)
-    return;
-
-  c = g_hash_table_lookup(caps_cache, hash);
-  if (c) {
-    c->category = g_strdup(category);
-    c->name = g_strdup(name);
-    c->type = g_strdup(type);
-  }
-}
-
-void caps_add_feature(char *hash, const char *feature)
-{
-  caps *c;
-  if (!hash)
-    return;
-  c = g_hash_table_lookup(caps_cache, hash);
-  if (c) {
-    char *f = g_strdup(feature);
-    g_hash_table_replace(c->features, f, f);
-  }
-}
-
-int caps_has_feature(char *hash, char *feature)
-{
-  caps *c;
-  if (!hash)
-    return 0;
-  c = g_hash_table_lookup(caps_cache, hash);
-  if (c)
-    return (g_hash_table_lookup(c->features, feature) != NULL);
-  return 0;
-}
-
-static GFunc _foreach_function;
-
-void _caps_foreach_helper(gpointer key, gpointer value, gpointer user_data)
-{
-  // GFunc func = (GFunc)user_data;
-  _foreach_function(value, user_data);
-}
-
-void caps_foreach_feature(const char *hash, GFunc func, gpointer user_data)
-{
-  caps *c;
-  if (!hash)
-    return;
-  c = g_hash_table_lookup(caps_cache, hash);
-  if (!c)
-    return;
-  _foreach_function = func;
-  g_hash_table_foreach(c->features, _caps_foreach_helper, user_data);
-}
-
-gint _strcmp_sort(gconstpointer a, gconstpointer b)
-{
-  return g_strcmp0(a, b);
-}
-
-//generates the sha1 hash for the special capability "" and returns it
-const char *caps_generate(void)
-{
-  char *identity;
-  GList *features;
-  GChecksum *sha1;
-  guint8 digest[20];
-  gsize digest_size = 20;
-  gchar *hash, *old_hash = NULL;
-  caps *old_caps;
-  caps *c = g_hash_table_lookup(caps_cache, "");
-
-  g_hash_table_steal(caps_cache, "");
-  sha1 = g_checksum_new(G_CHECKSUM_SHA1);
-  identity = g_strdup_printf("%s/%s//%s<", c->category, c->type, c->name);
-  g_checksum_update(sha1, (guchar*)identity, -1);
-  g_free(identity);
-
-  features = g_hash_table_get_values(c->features);
-  features = g_list_sort(features, _strcmp_sort);
-  {
-    GList *feature;
-    for (feature=features; feature; feature=feature->next) {
-      g_checksum_update(sha1, feature->data, -1);
-      g_checksum_update(sha1, (guchar *)"<", -1);
-    }
-  }
-  g_list_free(features);
-
-  g_checksum_get_digest(sha1, digest, &digest_size);
-  hash = g_base64_encode(digest, digest_size);
-  g_checksum_free(sha1);
-  g_hash_table_lookup_extended(caps_cache, hash,
-                               (gpointer *)&old_hash, (gpointer *)&old_caps);
-  g_hash_table_insert(caps_cache, hash, c);
-  if (old_hash)
-    return old_hash;
-  else
-    return hash;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/caps.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-#ifndef __CAPS_H__
-#define __CAPS_H__ 1
-
-#include <glib.h>
-
-void  caps_init(void);
-void  caps_free(void);
-void  caps_add(char *hash);
-int   caps_has_hash(const char *hash);
-void  caps_set_identity(char *hash,
-                        const char *category,
-                        const char *name,
-                        const char *type);
-void  caps_add_feature(char *hash, const char *feature);
-int   caps_has_feature(char *hash, char *feature);
-void  caps_foreach_feature(const char *hash, GFunc func, gpointer user_data);
-
-char *caps_generate(void);
-
-#endif /* __CAPS_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/commands.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3828 +0,0 @@
-/*
- * commands.c   -- user commands handling
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <string.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "commands.h"
-#include "help.h"
-#include "roster.h"
-#include "screen.h"
-#include "compl.h"
-#include "hooks.h"
-#include "hbuf.h"
-#include "utils.h"
-#include "settings.h"
-#include "events.h"
-#include "otr.h"
-#include "utf8.h"
-#include "xmpp.h"
-#include "main.h"
-
-#define IMSTATUS_AWAY           "away"
-#define IMSTATUS_ONLINE         "online"
-#define IMSTATUS_OFFLINE        "offline"
-#define IMSTATUS_FREE4CHAT      "free"
-#define IMSTATUS_INVISIBLE      "invisible"
-#define IMSTATUS_AVAILABLE      "avail"
-#define IMSTATUS_NOTAVAILABLE   "notavail"
-#define IMSTATUS_DONOTDISTURB   "dnd"
-
-// Return value container for the following functions
-static int retval_for_cmds;
-
-// Commands callbacks
-static void do_roster(char *arg);
-static void do_status(char *arg);
-static void do_status_to(char *arg);
-static void do_add(char *arg);
-static void do_del(char *arg);
-static void do_group(char *arg);
-static void do_say(char *arg);
-static void do_msay(char *arg);
-static void do_say_to(char *arg);
-static void do_buffer(char *arg);
-static void do_clear(char *arg);
-static void do_info(char *arg);
-static void do_rename(char *arg);
-static void do_move(char *arg);
-static void do_set(char *arg);
-static void do_alias(char *arg);
-static void do_bind(char *arg);
-static void do_connect(char *arg);
-static void do_disconnect(char *arg);
-static void do_rawxml(char *arg);
-static void do_room(char *arg);
-static void do_authorization(char *arg);
-static void do_version(char *arg);
-static void do_request(char *arg);
-static void do_event(char *arg);
-static void do_help(char *arg);
-static void do_pgp(char *arg);
-static void do_iline(char *arg);
-static void do_screen_refresh(char *arg);
-static void do_chat_disable(char *arg);
-static void do_source(char *arg);
-static void do_color(char *arg);
-static void do_otr(char *arg);
-static void do_otrpolicy(char *arg);
-static void do_echo(char *arg);
-
-static void do_say_internal(char *arg, int parse_flags);
-
-// Global variable for the commands list
-static GSList *Commands;
-
-#ifdef MODULES_ENABLE
-#include <gmodule.h>
-
-static void do_load(char *arg);
-static void do_unload(char *arg);
-
-typedef struct {
-  char *name;
-  GModule *module;
-} loaded_module_t;
-
-GSList *loaded_modules = NULL;
-
-gpointer cmd_del(const char *name)
-{
-  GSList *sl_cmd;
-  for (sl_cmd = Commands; sl_cmd; sl_cmd = sl_cmd->next) {
-    cmd *command = (cmd *) sl_cmd->data;
-    if (!strcmp (command->name, name)) {
-      gpointer userdata = command->userdata;
-      Commands = g_slist_delete_link(Commands, sl_cmd);
-      compl_del_category_word(COMPL_CMD, command->name);
-      g_free(command);
-      return userdata;
-    }
-  }
-  return NULL;
-}
-
-//  cmd_add()
-// Adds a command to the commands list and to the CMD completion list
-void cmd_add(const char *name, const char *help, guint flags_row1,
-             guint flags_row2, void (*f)(char*), gpointer userdata)
-#define cmd_add(A, B, C, D, E) cmd_add (A, B, C, D, E, NULL);
-#else
-static void cmd_add(const char *name, const char *help,
-        guint flags_row1, guint flags_row2, void (*f)(char*))
-#endif
-{
-  cmd *n_cmd = g_new0(cmd, 1);
-  strncpy(n_cmd->name, name, 32-1);
-  n_cmd->help = help;
-  n_cmd->completion_flags[0] = flags_row1;
-  n_cmd->completion_flags[1] = flags_row2;
-  n_cmd->func = f;
-#ifdef MODULES_ENABLE
-  n_cmd->userdata = userdata;
-#endif
-  Commands = g_slist_prepend(Commands, n_cmd);
-  // Add to completion CMD category
-  compl_add_category_word(COMPL_CMD, name);
-}
-
-//  cmd_init()
-// Commands table initialization
-// !!!
-// After changing commands names and it arguments names here, you must change
-// ones in init_bindings()!
-//
-void cmd_init(void)
-{
-  cmd_add("add", "Add a jabber user", COMPL_JID, 0, &do_add);
-  cmd_add("alias", "Add an alias", 0, 0, &do_alias);
-  cmd_add("authorization", "Manage subscription authorizations",
-          COMPL_AUTH, COMPL_JID, &do_authorization);
-  cmd_add("bind", "Add an key binding", 0, 0, &do_bind);
-  cmd_add("buffer", "Manipulate current buddy's buffer (chat window)",
-          COMPL_BUFFER, 0, &do_buffer);
-  cmd_add("chat_disable", "Disable chat mode", 0, 0, &do_chat_disable);
-  cmd_add("clear", "Clear the dialog window", 0, 0, &do_clear);
-  cmd_add("color", "Set coloring options", COMPL_COLOR, 0, &do_color);
-  cmd_add("connect", "Connect to the server", 0, 0, &do_connect);
-  cmd_add("del", "Delete the current buddy", 0, 0, &do_del);
-  cmd_add("disconnect", "Disconnect from server", 0, 0, &do_disconnect);
-  cmd_add("echo", "Display a string in the log window", 0, 0, &do_echo);
-  cmd_add("event", "Process an event", COMPL_EVENTSID, COMPL_EVENTS, &do_event);
-  cmd_add("group", "Change group display settings",
-          COMPL_GROUP, COMPL_GROUPNAME, &do_group);
-  cmd_add("help", "Display some help", COMPL_CMD, 0, &do_help);
-  cmd_add("iline", "Manipulate input buffer", 0, 0, &do_iline);
-  cmd_add("info", "Show basic info on current buddy", 0, 0, &do_info);
-  cmd_add("move", "Move the current buddy to another group", COMPL_GROUPNAME,
-          0, &do_move);
-  cmd_add("msay", "Send a multi-lines message to the selected buddy",
-          COMPL_MULTILINE, 0, &do_msay);
-  cmd_add("otr", "Manage OTR settings", COMPL_OTR, COMPL_JID, &do_otr);
-  cmd_add("otrpolicy", "Manage OTR policies", COMPL_JID, COMPL_OTRPOLICY,
-          &do_otrpolicy);
-  cmd_add("pgp", "Manage PGP settings", COMPL_PGP, COMPL_JID, &do_pgp);
-  cmd_add("quit", "Exit the software", 0, 0, NULL);
-  cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml);
-  cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename);
-  cmd_add("request", "Send a Jabber IQ request", COMPL_REQUEST, COMPL_JID,
-          &do_request);
-  cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room);
-  cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0,
-          &do_roster);
-  cmd_add("say", "Say something to the selected buddy", 0, 0, &do_say);
-  cmd_add("say_to", "Say something to a specific buddy", COMPL_JID, 0,
-          &do_say_to);
-  cmd_add("screen_refresh", "Redraw mcabber screen", 0, 0, &do_screen_refresh);
-  //cmd_add("search");
-  cmd_add("set", "Set/query an option value", 0, 0, &do_set);
-  cmd_add("source", "Read a configuration file", 0, 0, &do_source);
-  cmd_add("status", "Show or set your status", COMPL_STATUS, 0, &do_status);
-  cmd_add("status_to", "Show or set your status for one recipient",
-          COMPL_JID, COMPL_STATUS, &do_status_to);
-  cmd_add("version", "Show mcabber version", 0, 0, &do_version);
-#ifdef MODULES_ENABLE
-  cmd_add("load", "Load module", 0, 0, &do_load);
-  cmd_add("unload", "Unload module", 0, 0, &do_unload);
-#endif
-
-  // Status category
-  compl_add_category_word(COMPL_STATUS, "online");
-  compl_add_category_word(COMPL_STATUS, "avail");
-  compl_add_category_word(COMPL_STATUS, "invisible");
-  compl_add_category_word(COMPL_STATUS, "free");
-  compl_add_category_word(COMPL_STATUS, "dnd");
-  compl_add_category_word(COMPL_STATUS, "notavail");
-  compl_add_category_word(COMPL_STATUS, "away");
-  compl_add_category_word(COMPL_STATUS, "offline");
-  compl_add_category_word(COMPL_STATUS, "message");
-
-  // Roster category
-  compl_add_category_word(COMPL_ROSTER, "bottom");
-  compl_add_category_word(COMPL_ROSTER, "top");
-  compl_add_category_word(COMPL_ROSTER, "up");
-  compl_add_category_word(COMPL_ROSTER, "down");
-  compl_add_category_word(COMPL_ROSTER, "group_prev");
-  compl_add_category_word(COMPL_ROSTER, "group_next");
-  compl_add_category_word(COMPL_ROSTER, "hide");
-  compl_add_category_word(COMPL_ROSTER, "show");
-  compl_add_category_word(COMPL_ROSTER, "toggle");
-  compl_add_category_word(COMPL_ROSTER, "display");
-  compl_add_category_word(COMPL_ROSTER, "hide_offline");
-  compl_add_category_word(COMPL_ROSTER, "show_offline");
-  compl_add_category_word(COMPL_ROSTER, "toggle_offline");
-  compl_add_category_word(COMPL_ROSTER, "item_lock");
-  compl_add_category_word(COMPL_ROSTER, "item_unlock");
-  compl_add_category_word(COMPL_ROSTER, "item_toggle_lock");
-  compl_add_category_word(COMPL_ROSTER, "alternate");
-  compl_add_category_word(COMPL_ROSTER, "search");
-  compl_add_category_word(COMPL_ROSTER, "unread_first");
-  compl_add_category_word(COMPL_ROSTER, "unread_next");
-  compl_add_category_word(COMPL_ROSTER, "note");
-
-  // Buffer category
-  compl_add_category_word(COMPL_BUFFER, "clear");
-  compl_add_category_word(COMPL_BUFFER, "bottom");
-  compl_add_category_word(COMPL_BUFFER, "top");
-  compl_add_category_word(COMPL_BUFFER, "up");
-  compl_add_category_word(COMPL_BUFFER, "down");
-  compl_add_category_word(COMPL_BUFFER, "search_backward");
-  compl_add_category_word(COMPL_BUFFER, "search_forward");
-  compl_add_category_word(COMPL_BUFFER, "date");
-  compl_add_category_word(COMPL_BUFFER, "%");
-  compl_add_category_word(COMPL_BUFFER, "purge");
-  compl_add_category_word(COMPL_BUFFER, "close");
-  compl_add_category_word(COMPL_BUFFER, "close_all");
-  compl_add_category_word(COMPL_BUFFER, "scroll_lock");
-  compl_add_category_word(COMPL_BUFFER, "scroll_unlock");
-  compl_add_category_word(COMPL_BUFFER, "scroll_toggle");
-  compl_add_category_word(COMPL_BUFFER, "list");
-  compl_add_category_word(COMPL_BUFFER, "save");
-
-  // Group category
-  compl_add_category_word(COMPL_GROUP, "fold");
-  compl_add_category_word(COMPL_GROUP, "unfold");
-  compl_add_category_word(COMPL_GROUP, "toggle");
-
-  // Multi-line (msay) category
-  compl_add_category_word(COMPL_MULTILINE, "abort");
-  compl_add_category_word(COMPL_MULTILINE, "begin");
-  compl_add_category_word(COMPL_MULTILINE, "send");
-  compl_add_category_word(COMPL_MULTILINE, "send_to");
-  compl_add_category_word(COMPL_MULTILINE, "toggle");
-  compl_add_category_word(COMPL_MULTILINE, "toggle_verbatim");
-  compl_add_category_word(COMPL_MULTILINE, "verbatim");
-
-  // Room category
-  compl_add_category_word(COMPL_ROOM, "affil");
-  compl_add_category_word(COMPL_ROOM, "ban");
-  compl_add_category_word(COMPL_ROOM, "bookmark");
-  compl_add_category_word(COMPL_ROOM, "destroy");
-  compl_add_category_word(COMPL_ROOM, "invite");
-  compl_add_category_word(COMPL_ROOM, "join");
-  compl_add_category_word(COMPL_ROOM, "kick");
-  compl_add_category_word(COMPL_ROOM, "leave");
-  compl_add_category_word(COMPL_ROOM, "names");
-  compl_add_category_word(COMPL_ROOM, "nick");
-  compl_add_category_word(COMPL_ROOM, "privmsg");
-  compl_add_category_word(COMPL_ROOM, "remove");
-  compl_add_category_word(COMPL_ROOM, "role");
-  compl_add_category_word(COMPL_ROOM, "setopt");
-  compl_add_category_word(COMPL_ROOM, "topic");
-  compl_add_category_word(COMPL_ROOM, "unban");
-  compl_add_category_word(COMPL_ROOM, "unlock");
-  compl_add_category_word(COMPL_ROOM, "whois");
-
-  // Authorization category
-  compl_add_category_word(COMPL_AUTH, "allow");
-  compl_add_category_word(COMPL_AUTH, "cancel");
-  compl_add_category_word(COMPL_AUTH, "request");
-  compl_add_category_word(COMPL_AUTH, "request_unsubscribe");
-
-  // Request (query) category
-  compl_add_category_word(COMPL_REQUEST, "last");
-  compl_add_category_word(COMPL_REQUEST, "time");
-  compl_add_category_word(COMPL_REQUEST, "vcard");
-  compl_add_category_word(COMPL_REQUEST, "version");
-
-  // Events category
-  compl_add_category_word(COMPL_EVENTS, "accept");
-  compl_add_category_word(COMPL_EVENTS, "ignore");
-  compl_add_category_word(COMPL_EVENTS, "reject");
-
-  // PGP category
-  compl_add_category_word(COMPL_PGP, "disable");
-  compl_add_category_word(COMPL_PGP, "enable");
-  compl_add_category_word(COMPL_PGP, "force");
-  compl_add_category_word(COMPL_PGP, "info");
-  compl_add_category_word(COMPL_PGP, "setkey");
-
-  // OTR category
-  compl_add_category_word(COMPL_OTR, "start");
-  compl_add_category_word(COMPL_OTR, "stop");
-  compl_add_category_word(COMPL_OTR, "fingerprint");
-  compl_add_category_word(COMPL_OTR, "smpq");
-  compl_add_category_word(COMPL_OTR, "smpr");
-  compl_add_category_word(COMPL_OTR, "smpa");
-  compl_add_category_word(COMPL_OTR, "info");
-  compl_add_category_word(COMPL_OTR, "key");
-
-  // OTR Policy category
-  compl_add_category_word(COMPL_OTRPOLICY, "plain");
-  compl_add_category_word(COMPL_OTRPOLICY, "manual");
-  compl_add_category_word(COMPL_OTRPOLICY, "opportunistic");
-  compl_add_category_word(COMPL_OTRPOLICY, "always");
-
-  // Color category
-  compl_add_category_word(COMPL_COLOR, "roster");
-  compl_add_category_word(COMPL_COLOR, "muc");
-  compl_add_category_word(COMPL_COLOR, "mucnick");
-}
-
-#ifdef MODULES_ENABLE
-void cmd_deinit ()
-{
-  GSList *el = loaded_modules;
-  while (el) {
-    loaded_module_t *module = el->data;
-    if (!g_module_close ((GModule *) module->module))
-      scr_LogPrint (LPRINT_LOGNORM, "* Module unloading failed: %s",
-                    g_module_error ());
-    g_free (module->name);
-    g_free (module);
-    el = g_slist_next (el);
-  }
-  g_slist_free (loaded_modules);
-}
-#endif
-
-//  expandalias(line)
-// If there is one, expand the alias in line and returns a new allocated line
-// If no alias is found, returns line
-// Note: if the returned pointer is different from line, the caller should
-//       g_free() the pointer after use
-char *expandalias(const char *line)
-{
-  const char *p1, *p2;
-  char *word;
-  const gchar *value;
-  char *newline = (char*)line;
-
-  // Ignore leading COMMAND_CHAR
-  for (p1 = line ; *p1 == COMMAND_CHAR ; p1++)
-    ;
-  // Locate the end of the word
-  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
-    ;
-  // Extract the word and look for an alias in the list
-  word = g_strndup(p1, p2-p1);
-  value = settings_get(SETTINGS_TYPE_ALIAS, (const char*)word);
-  g_free(word);
-
-  if (value)
-    newline = g_strdup_printf("%c%s%s", COMMAND_CHAR, value, p2);
-
-  return newline;
-}
-
-//  cmd_get
-// Finds command in the command list structure.
-// Returns a pointer to the cmd entry, or NULL if command not found.
-cmd *cmd_get(const char *command)
-{
-  const char *p1, *p2;
-  char *com;
-  GSList *sl_com;
-
-  // Ignore leading COMMAND_CHAR
-  for (p1 = command ; *p1 == COMMAND_CHAR ; p1++)
-    ;
-  // Locate the end of the command
-  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
-    ;
-  // Copy the clean command
-  com = g_strndup(p1, p2-p1);
-
-  // Look for command in the list
-  for (sl_com=Commands; sl_com; sl_com = g_slist_next(sl_com)) {
-    if (!strcasecmp(com, ((cmd*)sl_com->data)->name))
-      break;
-  }
-  g_free(com);
-
-  if (sl_com)       // Command has been found.
-    return (cmd*)sl_com->data;
-  return NULL;
-}
-
-//  process_command(line, iscmd)
-// Process a command line.
-// If iscmd is TRUE, process the command even if verbatim mmode is set;
-// it is intended to be used for key bindings.
-// Return 255 if this is the /quit command, and 0 for the other commands.
-int process_command(const char *line, guint iscmd)
-{
-  char *p;
-  char *xpline;
-  cmd *curcmd;
-
-  if (!line)
-    return 0;
-
-  // We do alias expansion here
-  if (iscmd || scr_get_multimode() != 2)
-    xpline = expandalias(line);
-  else
-    xpline = (char*)line; // No expansion in verbatim multi-line mode
-
-  // We want to use a copy
-  if (xpline == line)
-    xpline = g_strdup(line);
-
-  // Remove trailing spaces:
-  for (p=xpline ; *p ; p++)
-    ;
-  for (p-- ; p>xpline && (*p == ' ') ; p--)
-    *p = 0;
-
-  // Command "quit"?
-  if ((iscmd || scr_get_multimode() != 2)
-      && (!strncasecmp(xpline, mkcmdstr("quit"), strlen(mkcmdstr("quit"))))) {
-    if (!xpline[5] || xpline[5] == ' ') {
-      g_free(xpline);
-      return 255;
-    }
-  } else if (iscmd && !strncasecmp(xpline, "quit", 4) &&
-             (!xpline[4] || xpline[4] == ' ')) {
-    // If iscmd is true we can have the command without the command prefix
-    // character (usually '/').
-    g_free(xpline);
-    return 255;
-  }
-
-  // If verbatim multi-line mode, we check if another /msay command is typed
-  if (!iscmd && scr_get_multimode() == 2
-      && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
-    // It isn't an /msay command
-    scr_append_multiline(xpline);
-    g_free(xpline);
-    return 0;
-  }
-
-  // Commands handling
-  curcmd = cmd_get(xpline);
-
-  if (!curcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized command.  "
-                 "Please see the manual for a list of known commands.");
-    g_free(xpline);
-    return 0;
-  }
-  if (!curcmd->func) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "This functionality is not yet implemented, sorry.");
-    g_free(xpline);
-    return 0;
-  }
-  // Lets go to the command parameters
-  for (p = xpline+1; *p && (*p != ' ') ; p++)
-    ;
-  // Skip spaces
-  while (*p && (*p == ' '))
-    p++;
-  // Call command-specific function
-  retval_for_cmds = 0;
-#ifdef MODULES_ENABLE
-  if (curcmd->userdata)
-    (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata);
-  else
-    (*curcmd->func)(p);
-#else
-  (*curcmd->func)(p);
-#endif
-  g_free(xpline);
-  return retval_for_cmds;
-}
-
-//  process_line(line)
-// Process a command/message line.
-// If this isn't a command, this is a message and it is sent to the
-// currently selected buddy.
-// Return 255 if the line is the /quit command, or 0.
-int process_line(const char *line)
-{
-  if (!*line) { // User only pressed enter
-    if (scr_get_multimode()) {
-      scr_append_multiline("");
-      return 0;
-    }
-    if (current_buddy) {
-      if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP)
-	do_group("toggle");
-      else {
-        // Enter chat mode
-        scr_set_chatmode(TRUE);
-        scr_ShowBuddyWindow();
-      }
-    }
-    return 0;
-  }
-
-  if (*line != COMMAND_CHAR) {
-    // This isn't a command
-    if (scr_get_multimode())
-      scr_append_multiline(line);
-    else
-      do_say_internal((char*)line, 0);
-    return 0;
-  }
-
-  /* It is _probably_ a command -- except for verbatim multi-line mode */
-  return process_command(line, FALSE);
-}
-
-// Helper routine for buffer item_{lock,unlock,toggle_lock}
-// "lock" values: 1=lock 0=unlock -1=invert
-static void roster_buddylock(char *bjid, int lock)
-{
-  gpointer bud = NULL;
-  bool may_need_refresh = FALSE;
-
-  // Allow special jid "" or "." (current buddy)
-  if (bjid && (!*bjid || !strcmp(bjid, ".")))
-    bjid = NULL;
-
-  if (bjid) {
-    // The JID has been specified.  Quick check...
-    if (check_jid_syntax(bjid)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", bjid);
-    } else {
-      // Find the buddy
-      GSList *roster_elt;
-      roster_elt = roster_find(bjid, jidsearch,
-                               ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
-      if (roster_elt)
-        bud = roster_elt->data;
-      else
-        scr_LogPrint(LPRINT_NORMAL, "This jid isn't in the roster.");
-      may_need_refresh = TRUE;
-    }
-  } else {
-    // Use the current buddy
-    if (current_buddy)
-      bud = BUDDATA(current_buddy);
-  }
-
-  // Update the ROSTER_FLAG_USRLOCK flag
-  if (bud) {
-    if (lock == -1)
-      lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
-    buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
-    if (may_need_refresh) {
-      buddylist_build();
-      update_roster = TRUE;
-    }
-  }
-}
-
-//  display_and_free_note(note, winId)
-// Display the note information in the winId buffer, and free note
-// (winId is a bare jid or NULL for the status window, in which case we
-// display the note jid too)
-static void display_and_free_note(struct annotation *note, const char *winId)
-{
-  gchar tbuf[128];
-  GString *sbuf;
-  guint msg_flag = HBB_PREFIX_INFO;
-  /* We use the flag prefix_info for the first line, and prefix_cont
-     for the other lines, for better readability */
-
-  if (!note)
-    return;
-
-  sbuf = g_string_new("");
-
-  if (!winId) {
-    // We're writing to the status window, so let's show the jid too.
-    g_string_printf(sbuf, "Annotation on <%s>", note->jid);
-    scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
-    msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT;
-  }
-
-  // If we have the creation date, display it
-  if (note->cdate) {
-    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S",
-             localtime(&note->cdate));
-    g_string_printf(sbuf, "Note created  %s", tbuf);
-    scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
-    msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT;
-  }
-  // If we have the modification date, display it
-  // unless it's the same as the creation date
-  if (note->mdate && note->mdate != note->cdate) {
-    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S",
-             localtime(&note->mdate));
-    g_string_printf(sbuf, "Note modified %s", tbuf);
-    scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
-    msg_flag = HBB_PREFIX_INFO | HBB_PREFIX_CONT;
-  }
-  // Note text
-  g_string_printf(sbuf, "Note: %s", note->text);
-  scr_WriteIncomingMessage(winId, sbuf->str, 0, msg_flag, 0);
-
-  g_string_free(sbuf, TRUE);
-  g_free(note->text);
-  g_free(note->jid);
-  g_free(note);
-}
-
-static void display_all_annotations(void)
-{
-  GSList *notes;
-  notes = xmpp_get_all_storage_rosternotes();
-
-  if (!notes)
-    return;
-
-  // Call display_and_free_note() for each note,
-  // with winId = NULL (special window)
-  g_slist_foreach(notes, (GFunc)&display_and_free_note, NULL);
-  scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
-  update_roster = TRUE;
-  g_slist_free(notes);
-}
-
-static void roster_note(char *arg)
-{
-  const char *bjid;
-  guint type;
-
-  if (!current_buddy)
-    return;
-
-  bjid = buddy_getjid(BUDDATA(current_buddy));
-  type = buddy_gettype(BUDDATA(current_buddy));
-
-  if (!bjid && type == ROSTER_TYPE_SPECIAL && !arg) {
-    // We're in the status window (the only special buffer currently)
-    // Let's display all server notes
-    display_all_annotations();
-    return;
-  }
-
-  if (!bjid || (type != ROSTER_TYPE_USER &&
-               type != ROSTER_TYPE_ROOM &&
-               type != ROSTER_TYPE_AGENT)) {
-    scr_LogPrint(LPRINT_NORMAL, "This item can't have a note.");
-    return;
-  }
-
-  if (arg && *arg) {  // Set a note
-    gchar *msg, *notetxt;
-    msg = to_utf8(arg);
-    if (!strcmp(msg, "-"))
-      notetxt = NULL; // delete note
-    else
-      notetxt = msg;
-    xmpp_set_storage_rosternotes(bjid, notetxt);
-    g_free(msg);
-  } else {      // Display a note
-    struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
-    if (note) {
-      display_and_free_note(note, bjid);
-    } else {
-      scr_WriteIncomingMessage(bjid, "This item doesn't have a note.", 0,
-                               HBB_PREFIX_INFO, 0);
-    }
-  }
-}
-
-//  roster_updown(updown, nitems)
-// updown: -1=up, +1=down
-inline static void roster_updown(int updown, char *nitems)
-{
-  int nbitems;
-
-  if (!nitems || !*nitems)
-    nbitems = 1;
-  else
-    nbitems = strtol(nitems, NULL, 10);
-
-  if (nbitems > 0)
-    scr_RosterUpDown(updown, nbitems);
-}
-
-/* Commands callback functions */
-/* All these do_*() functions will be called with a "arg" parameter */
-/* (with arg not null)                                              */
-
-static void do_roster(char *arg)
-{
-  char **paramlst;
-  char *subcmd;
-
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(subcmd, "top")) {
-    scr_RosterTop();
-    update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "bottom")) {
-    scr_RosterBottom();
-    update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "hide")) {
-    scr_RosterVisibility(0);
-  } else if (!strcasecmp(subcmd, "show")) {
-    scr_RosterVisibility(1);
-  } else if (!strcasecmp(subcmd, "toggle")) {
-    scr_RosterVisibility(-1);
-  } else if (!strcasecmp(subcmd, "hide_offline")) {
-    buddylist_set_hide_offline_buddies(TRUE);
-    if (current_buddy)
-      buddylist_build();
-    update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "show_offline")) {
-    buddylist_set_hide_offline_buddies(FALSE);
-    buddylist_build();
-    update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "toggle_offline")) {
-    buddylist_set_hide_offline_buddies(-1);
-    buddylist_build();
-    update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "display")) {
-    scr_RosterDisplay(arg);
-  } else if (!strcasecmp(subcmd, "item_lock")) {
-    roster_buddylock(arg, 1);
-  } else if (!strcasecmp(subcmd, "item_unlock")) {
-    roster_buddylock(arg, 0);
-  } else if (!strcasecmp(subcmd, "item_toggle_lock")) {
-    roster_buddylock(arg, -1);
-  } else if (!strcasecmp(subcmd, "unread_first")) {
-    scr_RosterUnreadMessage(0);
-  } else if (!strcasecmp(subcmd, "unread_next")) {
-    scr_RosterUnreadMessage(1);
-  } else if (!strcasecmp(subcmd, "alternate")) {
-    scr_RosterJumpAlternate();
-  } else if (!strncasecmp(subcmd, "search", 6)) {
-    strip_arg_special_chars(arg);
-    if (!arg || !*arg) {
-      scr_LogPrint(LPRINT_NORMAL, "What name or JID are you looking for?");
-      free_arg_lst(paramlst);
-      return;
-    }
-    scr_RosterSearch(arg);
-    update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "up")) {
-    roster_updown(-1, arg);
-  } else if (!strcasecmp(subcmd, "down")) {
-    roster_updown(1, arg);
-  } else if (!strcasecmp(subcmd, "group_prev")) {
-    scr_RosterPrevGroup();
-  } else if (!strcasecmp(subcmd, "group_next")) {
-    scr_RosterNextGroup();
-  } else if (!strcasecmp(subcmd, "note")) {
-    roster_note(arg);
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  free_arg_lst(paramlst);
-}
-
-void do_color(char *arg)
-{
-  char **paramlst;
-  char *subcmd;
-
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(subcmd, "roster")) {
-    char *status, *wildcard, *color;
-    char **arglist = split_arg(arg, 3, 0);
-
-    status = *arglist;
-    wildcard = to_utf8(arglist[1]);
-    color = arglist[2];
-
-    if (status && !strcmp(status, "clear")) { // Not a color command, clear all
-      scr_RosterClearColor();
-      update_roster = TRUE;
-    } else {
-      if (!status || !*status || !wildcard || !*wildcard || !color || !*color) {
-        scr_LogPrint(LPRINT_NORMAL, "Missing argument");
-      } else {
-        update_roster = scr_RosterColor(status, wildcard, color)
-                        || update_roster;
-      }
-    }
-    free_arg_lst(arglist);
-    g_free(wildcard);
-  } else if (!strcasecmp(subcmd, "muc")) {
-    char **arglist = split_arg(arg, 2, 0);
-    char *free_muc = to_utf8(*arglist);
-    const char *muc = free_muc, *mode = arglist[1];
-    if (!muc || !*muc)
-      scr_LogPrint(LPRINT_NORMAL, "What MUC?");
-    else {
-      if (!strcmp(muc, "."))
-        if (!(muc = CURRENT_JID))
-          scr_LogPrint(LPRINT_NORMAL, "No JID selected");
-      if (muc) {
-        if (check_jid_syntax(muc) && strcmp(muc, "*"))
-          scr_LogPrint(LPRINT_NORMAL, "Not a JID");
-        else {
-          if (!mode || !*mode || !strcasecmp(mode, "on"))
-            scr_MucColor(muc, MC_ALL);
-          else if (!strcasecmp(mode, "preset"))
-            scr_MucColor(muc, MC_PRESET);
-          else if (!strcasecmp(mode, "off"))
-            scr_MucColor(muc, MC_OFF);
-          else if (!strcmp(mode, "-"))
-            scr_MucColor(muc, MC_REMOVE);
-          else
-            scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
-        }
-      }
-    }
-    free_arg_lst(arglist);
-    g_free(free_muc);
-  } else if (!strcasecmp(subcmd, "mucnick")) {
-    char **arglist = split_arg(arg, 2, 0);
-    const char *nick = *arglist, *color = arglist[1];
-    if (!nick || !*nick || !color || !*color)
-      scr_LogPrint(LPRINT_NORMAL, "Missing argument");
-    else
-      scr_MucNickColor(nick, color);
-    free_arg_lst(arglist);
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  free_arg_lst(paramlst);
-}
-
-//  cmd_setstatus(recipient, arg)
-// Set your Jabber status.
-// - if recipient is not NULL, the status is sent to this contact only
-// - arg must be "status message" (message is optional)
-void cmd_setstatus(const char *recipient, const char *arg)
-{
-  char **paramlst;
-  char *status;
-  char *msg;
-  enum imstatus st;
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    return;
-  }
-
-  // It makes sense to reset autoaway before changing the status
-  // (esp. for FIFO or remote commands) or the behaviour could be
-  // unexpected...
-  if (!recipient)
-    scr_CheckAutoAway(TRUE);
-
-  paramlst = split_arg(arg, 2, 1); // status, message
-  status = *paramlst;
-  msg = *(paramlst+1);
-
-  if (!status) {
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if      (!strcasecmp(status, IMSTATUS_OFFLINE))       st = offline;
-  else if (!strcasecmp(status, IMSTATUS_ONLINE))        st = available;
-  else if (!strcasecmp(status, IMSTATUS_AVAILABLE))     st = available;
-  else if (!strcasecmp(status, IMSTATUS_AWAY))          st = away;
-  else if (!strcasecmp(status, IMSTATUS_INVISIBLE))     st = invisible;
-  else if (!strcasecmp(status, IMSTATUS_DONOTDISTURB))  st = dontdisturb;
-  else if (!strcasecmp(status, IMSTATUS_NOTAVAILABLE))  st = notavail;
-  else if (!strcasecmp(status, IMSTATUS_FREE4CHAT))     st = freeforchat;
-  else if (!strcasecmp(status, "message")) {
-    if (!msg || !*msg) {
-      // We want a message.  If there's none, we give up.
-      scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-      free_arg_lst(paramlst);
-      return;
-    }
-    st = xmpp_getstatus();  // Preserve current status
-  } else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized status!");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  // Use provided message
-  if (msg && !*msg) {
-    msg = NULL;
-  }
-
-  // If a recipient is specified, let's don't use default status messages
-  if (recipient && !msg)
-    msg = "";
-
-  xmpp_setstatus(st, recipient, msg, FALSE);
-
-  free_arg_lst(paramlst);
-}
-
-static void do_status(char *arg)
-{
-  if (!*arg) {
-    const char *sm = xmpp_getstatusmsg();
-    scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s",
-                 imstatus2char[xmpp_getstatus()],
-                 (sm ? sm : ""));
-    return;
-  }
-  arg = to_utf8(arg);
-  cmd_setstatus(NULL, arg);
-  g_free(arg);
-}
-
-static void do_status_to(char *arg)
-{
-  char **paramlst;
-  char *fjid, *st, *msg;
-  char *jid_utf8 = NULL;
-
-  paramlst = split_arg(arg, 3, 1); // jid, status, [message]
-  fjid = *paramlst;
-  st = *(paramlst+1);
-  msg = *(paramlst+2);
-
-  if (!fjid || !st) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Please specify both a Jabber ID and a status.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  // Allow things like /status_to "" away
-  if (!*fjid || !strcmp(fjid, "."))
-    fjid = NULL;
-
-  if (fjid) {
-    // The JID has been specified.  Quick check...
-    if (check_jid_syntax(fjid)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", fjid);
-      fjid = NULL;
-    } else {
-      // Convert jid to lowercase
-      char *p = fjid;
-      for ( ; *p && *p != JID_RESOURCE_SEPARATOR; p++)
-        *p = tolower(*p);
-      fjid = jid_utf8 = to_utf8(fjid);
-    }
-  } else {
-    // Add the current buddy
-    if (current_buddy)
-      fjid = (char*)buddy_getjid(BUDDATA(current_buddy));
-    if (!fjid)
-      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-  }
-
-  if (fjid) {
-    char *cmdline;
-    if (!msg)
-      msg = "";
-    msg = to_utf8(msg);
-    cmdline = g_strdup_printf("%s %s", st, msg);
-    scr_LogPrint(LPRINT_LOGNORM, "Sending to <%s> /status %s", fjid, cmdline);
-    cmd_setstatus(fjid, cmdline);
-    g_free(msg);
-    g_free(cmdline);
-    g_free(jid_utf8);
-  }
-  free_arg_lst(paramlst);
-}
-
-static void do_add(char *arg)
-{
-  char **paramlst;
-  char *id, *nick;
-  char *jid_utf8 = NULL;
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    return;
-  }
-
-  paramlst = split_arg(arg, 2, 0); // jid, [nickname]
-  id = *paramlst;
-  nick = *(paramlst+1);
-
-  if (!id)
-    nick = NULL; // Allow things like: /add "" nick
-  else if (!*id || !strcmp(id, "."))
-    id = NULL;
-
-  if (id) {
-    // The JID has been specified.  Quick check...
-    if (check_jid_syntax(id)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", id);
-      id = NULL;
-    } else {
-      mc_strtolower(id);
-      // Actually an UTF-8 id isn't needed because only the bare jid will
-      // be used.
-      id = jid_utf8 = to_utf8(id);
-    }
-  } else {
-    // Add the current buddy
-    if (current_buddy)
-      id = (char*)buddy_getjid(BUDDATA(current_buddy));
-    if (!id)
-      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-  }
-
-  if (nick)
-    nick = to_utf8(nick);
-
-  if (id) {
-    // 2nd parameter = optional nickname
-    xmpp_addbuddy(id, nick, NULL);
-    scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.",
-                 id);
-  }
-
-  g_free(jid_utf8);
-  g_free(nick);
-  free_arg_lst(paramlst);
-}
-
-static void do_del(char *arg)
-{
-  const char *bjid;
-
-  if (*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; "
-                 "the currently-selected buddy will be deleted.");
-    return;
-  }
-
-  if (!current_buddy)
-    return;
-  bjid = buddy_getjid(BUDDATA(current_buddy));
-  if (!bjid)
-    return;
-
-  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM) {
-    // This is a chatroom
-    if (buddy_getinsideroom(BUDDATA(current_buddy))) {
-      scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!");
-      return;
-    }
-  }
-
-  // Close the buffer
-  scr_BufferPurge(1, NULL);
-
-  scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", bjid);
-  xmpp_delbuddy(bjid);
-  scr_UpdateBuddyWindow();
-}
-
-static void do_group(char *arg)
-{
-  gpointer group = NULL;
-  guint leave_buddywindow;
-  char **paramlst;
-  char *subcmd;
-  enum { group_toggle = -1, group_unfold = 0, group_fold = 1 } group_state = 0;
-
-  if (!*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    return;
-  }
-
-  if (!current_buddy)
-    return;
-
-  paramlst = split_arg(arg, 2, 0); // subcmd, [arg]
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd)
-    goto do_group_return;   // Should not happen anyway
-
-  if (arg && *arg) {
-    GSList *roster_elt;
-    char *group_utf8 = to_utf8(arg);
-    roster_elt = roster_find(group_utf8, namesearch, ROSTER_TYPE_GROUP);
-    g_free(group_utf8);
-    if (roster_elt)
-      group = buddy_getgroup(roster_elt->data);
-  } else {
-    group = buddy_getgroup(BUDDATA(current_buddy));
-  }
-  if (!group)
-    goto do_group_return;
-
-  // We'll have to redraw the chat window if we're not currently on the group
-  // entry itself, because it means we'll have to leave the current buddy
-  // chat window.
-  leave_buddywindow = (group != BUDDATA(current_buddy) &&
-                       group == buddy_getgroup(BUDDATA(current_buddy)));
-
-
-  if (!(buddy_gettype(group) & ROSTER_TYPE_GROUP)) {
-    scr_LogPrint(LPRINT_NORMAL, "You need to select a group.");
-    goto do_group_return;
-  }
-
-  if (!strcasecmp(subcmd, "expand") || !strcasecmp(subcmd, "unfold"))
-    group_state = group_unfold;
-  else if (!strcasecmp(subcmd, "shrink") || !strcasecmp(subcmd, "fold"))
-    group_state = group_fold;
-  else if (!strcasecmp(subcmd, "toggle"))
-    group_state = group_toggle;
-  else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-    goto do_group_return;
-  }
-
-  if (group_state != group_unfold && leave_buddywindow)
-    scr_RosterPrevGroup();
-
-  buddy_hide_group(group, group_state);
-
-  buddylist_build();
-  update_roster = TRUE;
-
-do_group_return:
-  free_arg_lst(paramlst);
-}
-
-static int send_message_to(const char *fjid, const char *msg, const char *subj,
-                           LmMessageSubType type_overwrite, bool quiet)
-{
-  char *bare_jid, *rp;
-  char *hmsg;
-  gint crypted;
-  gint retval = 0;
-  int isroom;
-  gpointer xep184 = NULL;
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    return 1;
-  }
-  if (!fjid || !*fjid) {
-    scr_LogPrint(LPRINT_NORMAL, "You must specify a Jabber ID.");
-    return 1;
-  }
-  if (!msg || !*msg) {
-    scr_LogPrint(LPRINT_NORMAL, "You must specify a message.");
-    return 1;
-  }
-  if (check_jid_syntax((char*)fjid)) {
-    scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                 "<%s> is not a valid Jabber ID.", fjid);
-    return 1;
-  }
-
-  // We must use the bare jid in hk_message_out()
-  rp = strchr(fjid, JID_RESOURCE_SEPARATOR);
-  if (rp)
-    bare_jid = g_strndup(fjid, rp - fjid);
-  else
-    bare_jid = (char*)fjid;
-
-  if (!quiet) {
-    // Jump to window, create one if needed
-    scr_RosterJumpJid(bare_jid);
-  }
-
-  // Check if we're sending a message to a conference room
-  // If not, we must make sure rp is NULL, for hk_message_out()
-  isroom = !!roster_find(bare_jid, jidsearch, ROSTER_TYPE_ROOM);
-  if (rp) {
-    if (isroom) rp++;
-    else rp = NULL;
-  }
-  isroom = isroom && (!rp || !*rp);
-
-  // local part (UI, logging, etc.)
-  if (subj)
-    hmsg = g_strdup_printf("[%s]\n%s", subj, msg);
-  else
-    hmsg = (char*)msg;
-
-  // Network part
-  xmpp_send_msg(fjid, msg, (isroom ? ROSTER_TYPE_ROOM : ROSTER_TYPE_USER),
-                subj, FALSE, &crypted, type_overwrite, &xep184);
-
-  if (crypted == -1) {
-    scr_LogPrint(LPRINT_LOGNORM, "Encryption error.  Message was not sent.");
-    retval = 1;
-    goto send_message_to_return;
-  }
-
-  // Hook
-  if (!isroom)
-    hk_message_out(bare_jid, rp, 0, hmsg, crypted, xep184);
-
-send_message_to_return:
-  if (hmsg != msg) g_free(hmsg);
-  if (rp) g_free(bare_jid);
-  return retval;
-}
-
-//  send_message(msg, subj, type_overwrite)
-// Write the message in the buddy's window and send the message on
-// the network.
-static void send_message(const char *msg, const char *subj,
-                         LmMessageSubType type_overwrite)
-{
-  const char *bjid;
-
-  if (!current_buddy) {
-    scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected.");
-    return;
-  }
-
-  bjid = CURRENT_JID;
-  if (!bjid) {
-    scr_LogPrint(LPRINT_NORMAL, "No buddy is currently selected.");
-    return;
-  }
-
-  send_message_to(bjid, msg, subj, type_overwrite, FALSE);
-}
-
-static LmMessageSubType scan_mtype(char **arg)
-{
-  //Try splitting it
-  char **parlist = split_arg(*arg, 2, 1);
-  LmMessageSubType result = LM_MESSAGE_SUB_TYPE_NOT_SET;
-  //Is it any good parameter?
-  if (parlist && *parlist) {
-    if (!strcmp("-n", *parlist)) {
-      result = LM_MESSAGE_SUB_TYPE_NORMAL;
-    } else if (!strcmp("-h", *parlist)) {
-      result = LM_MESSAGE_SUB_TYPE_HEADLINE;
-    }
-    if (result != LM_MESSAGE_SUB_TYPE_NOT_SET || (!strcmp("--", *parlist)))
-      *arg += strlen(*arg) - (parlist[1] ? strlen(parlist[1]) : 0);
-  }
-  //Anything found? -> skip it
-  free_arg_lst(parlist);
-  return result;
-}
-
-static void do_say_internal(char *arg, int parse_flags)
-{
-  gpointer bud;
-  LmMessageSubType msgtype = LM_MESSAGE_SUB_TYPE_NOT_SET;
-
-  scr_set_chatmode(TRUE);
-  scr_ShowBuddyWindow();
-
-  if (!current_buddy) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Whom are you talking to?  Please select a buddy.");
-    return;
-  }
-
-  bud = BUDDATA(current_buddy);
-  if (!(buddy_gettype(bud) &
-        (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) {
-    scr_LogPrint(LPRINT_NORMAL, "This is not a user.");
-    return;
-  }
-
-  buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
-  if (parse_flags)
-    msgtype = scan_mtype(&arg);
-  arg = to_utf8(arg);
-  send_message(arg, NULL, msgtype);
-  g_free(arg);
-}
-
-static void do_say(char *arg) {
-  do_say_internal(arg, 1);
-}
-
-static void do_msay(char *arg)
-{
-  /* Parameters: begin verbatim abort send send_to */
-  char **paramlst;
-  char *subcmd;
-
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    scr_LogPrint(LPRINT_NORMAL, "Please read the manual before using "
-                 "the /msay command.");
-    scr_LogPrint(LPRINT_NORMAL, "(Use \"%s begin\" to enter "
-                 "multi-line mode...)", mkcmdstr("msay"));
-    goto do_msay_return;
-  }
-
-  if (!strcasecmp(subcmd, "toggle")) {
-    if (scr_get_multimode())
-      subcmd = "send";
-    else
-      subcmd = "begin";
-  } else if (!strcasecmp(subcmd, "toggle_verbatim")) {
-    if (scr_get_multimode())
-      subcmd = "send";
-    else
-      subcmd = "verbatim";
-  }
-
-  if (!strcasecmp(subcmd, "abort")) {
-    if (scr_get_multimode())
-      scr_LogPrint(LPRINT_NORMAL, "Leaving multi-line message mode.");
-    scr_set_multimode(FALSE, NULL);
-    goto do_msay_return;
-  } else if ((!strcasecmp(subcmd, "begin")) ||
-             (!strcasecmp(subcmd, "verbatim"))) {
-    bool verbat;
-    gchar *subj_utf8 = to_utf8(arg);
-    if (!strcasecmp(subcmd, "verbatim")) {
-      scr_set_multimode(2, subj_utf8);
-      verbat = TRUE;
-    } else {
-      scr_set_multimode(1, subj_utf8);
-      verbat = FALSE;
-    }
-
-    scr_LogPrint(LPRINT_NORMAL, "Entered %smulti-line message mode.",
-                 verbat ? "VERBATIM " : "");
-    scr_LogPrint(LPRINT_NORMAL, "Select a buddy and use \"%s send\" "
-                 "when your message is ready.", mkcmdstr("msay"));
-    if (verbat)
-      scr_LogPrint(LPRINT_NORMAL, "Use \"%s abort\" to abort this mode.",
-                   mkcmdstr("msay"));
-    g_free(subj_utf8);
-    goto do_msay_return;
-  } else if (strcasecmp(subcmd, "send") && strcasecmp(subcmd, "send_to")) {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-    goto do_msay_return;
-  }
-
-  /* send/send_to command */
-
-  if (!scr_get_multimode()) {
-    scr_LogPrint(LPRINT_NORMAL, "No message to send.  "
-                 "Use \"%s begin\" first.", mkcmdstr("msay"));
-    goto do_msay_return;
-  }
-
-  scr_set_chatmode(TRUE);
-  scr_ShowBuddyWindow();
-
-  if (!strcasecmp(subcmd, "send_to")) {
-    int err = FALSE;
-    gchar *msg_utf8;
-    LmMessageSubType msg_type = scan_mtype(&arg);
-    // Let's send to the specified JID.  We leave now if there
-    // has been an error (so we don't leave multi-line mode).
-    arg = to_utf8(arg);
-    msg_utf8 = to_utf8(scr_get_multiline());
-    if (msg_utf8) {
-      err = send_message_to(arg, msg_utf8, scr_get_multimode_subj(), msg_type,
-                            FALSE);
-      g_free(msg_utf8);
-    }
-    g_free(arg);
-    if (err)
-      goto do_msay_return;
-  } else { // Send to currently selected buddy
-    gpointer bud;
-    gchar *msg_utf8;
-
-    if (!current_buddy) {
-      scr_LogPrint(LPRINT_NORMAL, "Whom are you talking to?");
-      goto do_msay_return;
-    }
-
-    bud = BUDDATA(current_buddy);
-    if (!(buddy_gettype(bud) &
-          (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) {
-      scr_LogPrint(LPRINT_NORMAL, "This is not a user.");
-      goto do_msay_return;
-    }
-
-    buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
-    msg_utf8 = to_utf8(scr_get_multiline());
-    if (msg_utf8) {
-      send_message(msg_utf8, scr_get_multimode_subj(), scan_mtype(&arg));
-      g_free(msg_utf8);
-    }
-  }
-  scr_set_multimode(FALSE, NULL);
-  scr_LogPrint(LPRINT_NORMAL, "You have left multi-line message mode.");
-do_msay_return:
-  free_arg_lst(paramlst);
-}
-
-//  load_message_from_file(filename)
-// Read the whole content of a file.
-// The data are converted to UTF8, they should be freed by the caller after
-// use.
-char *load_message_from_file(const char *filename)
-{
-  FILE *fd;
-  struct stat buf;
-  char *msgbuf, *msgbuf_utf8;
-  char *p;
-  char *next_utf8_char;
-  size_t len;
-
-  fd = fopen(filename, "r");
-
-  if (!fd || fstat(fileno(fd), &buf)) {
-    scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename);
-    return NULL;
-  }
-  if (!buf.st_size || buf.st_size >= HBB_BLOCKSIZE) {
-    if (!buf.st_size)
-      scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename);
-    else
-      scr_LogPrint(LPRINT_LOGNORM, "Message file is too big (%s)", filename);
-    fclose(fd);
-    return NULL;
-  }
-
-  msgbuf = g_new0(char, HBB_BLOCKSIZE);
-  len = fread(msgbuf, 1, HBB_BLOCKSIZE-1, fd);
-  fclose(fd);
-
-  next_utf8_char = msgbuf;
-
-  // Check there is no binary data.  It must be a *message* file!
-  for (p = msgbuf ; *p ; p++) {
-    if (utf8_mode) {
-      if (p == next_utf8_char) {
-        if (!iswprint(get_char(p)) && *p != '\n' && *p != '\t')
-          break;
-        next_utf8_char = next_char(p);
-      }
-    } else {
-      unsigned char sc = *p;
-      if (!iswprint(sc) && sc != '\n' && sc != '\t')
-        break;
-    }
-  }
-
-  if (*p || (size_t)(p-msgbuf) != len) { // We're not at the End Of Line...
-    scr_LogPrint(LPRINT_LOGNORM, "Message file contains "
-                 "invalid characters (%s)", filename);
-    g_free(msgbuf);
-    return NULL;
-  }
-
-  // p is now at the EOL
-  // Let's strip trailing newlines
-  if (p > msgbuf)
-    p--;
-  while (p > msgbuf && *p == '\n')
-    *p-- = 0;
-
-  // It could be empty, once the trailing newlines are gone
-  if (p == msgbuf && *p == '\n') {
-    scr_LogPrint(LPRINT_LOGNORM, "Message file is empty (%s)", filename);
-    g_free(msgbuf);
-    return NULL;
-  }
-
-  msgbuf_utf8 = to_utf8(msgbuf);
-
-  if (!msgbuf_utf8 && msgbuf)
-    scr_LogPrint(LPRINT_LOGNORM, "Message file charset conversion error (%s)",
-                 filename);
-  g_free(msgbuf);
-  return msgbuf_utf8;
-}
-
-static void do_say_to(char *arg)
-{
-  char **paramlst;
-  char *fjid, *msg;
-  char *file = NULL;
-  LmMessageSubType msg_type = LM_MESSAGE_SUB_TYPE_NOT_SET;
-  bool quiet = FALSE;
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    return;
-  }
-
-  msg_type = scan_mtype(&arg);
-  paramlst = split_arg(arg, 2, 1); // jid, message (or option, jid, message)
-
-  if (!*paramlst) {  // No parameter?
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  // Check for an option parameter
-  while (*paramlst) {
-    if (!strcmp(*paramlst, "-q")) {
-      char **oldparamlst = paramlst;
-      paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, message
-      free_arg_lst(oldparamlst);
-      quiet = TRUE;
-    } else if (!strcmp(*paramlst, "-f")) {
-      char **oldparamlst = paramlst;
-      paramlst = split_arg(*(oldparamlst+1), 2, 1); // filename, jid
-      free_arg_lst(oldparamlst);
-      if (!*paramlst) {
-        scr_LogPrint(LPRINT_NORMAL, "Wrong usage.");
-        return;
-      }
-      file = g_strdup(*paramlst);
-      // One more parameter shift...
-      oldparamlst = paramlst;
-      paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, nothing
-      free_arg_lst(oldparamlst);
-    } else
-      break;
-  }
-
-  if (!*paramlst) {
-    scr_LogPrint(LPRINT_NORMAL, "Wrong usage.");
-    return;
-  }
-
-  fjid = *paramlst;
-  msg = *(paramlst+1);
-
-  if (!strcmp(fjid, ".")) {
-    // Send the message to the current buddy
-    if (current_buddy)
-      fjid = (char*)buddy_getjid(BUDDATA(current_buddy));
-    if (!fjid) {
-      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-      free_arg_lst(paramlst);
-      return;
-    }
-  } else if (check_jid_syntax(fjid)) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  fjid = to_utf8(fjid);
-  if (!file) {
-    msg = to_utf8(msg);
-  } else {
-    char *filename_xp;
-    if (msg)
-      scr_LogPrint(LPRINT_NORMAL, "say_to: extra parameter ignored.");
-    filename_xp = expand_filename(file);
-    msg = load_message_from_file(filename_xp);
-    g_free(filename_xp);
-    g_free(file);
-  }
-
-  send_message_to(fjid, msg, NULL, msg_type, quiet);
-
-  g_free(fjid);
-  g_free(msg);
-  free_arg_lst(paramlst);
-}
-
-//  buffer_updown(updown, nblines)
-// updown: -1=up, +1=down
-inline static void buffer_updown(int updown, char *nlines)
-{
-  int nblines;
-
-  if (!nlines || !*nlines)
-    nblines = 0;
-  else
-    nblines = strtol(nlines, NULL, 10);
-
-  if (nblines >= 0)
-    scr_BufferScrollUpDown(updown, nblines);
-}
-
-static void buffer_search(int direction, char *arg)
-{
-  if (!arg || !*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    return;
-  }
-
-  scr_BufferSearch(direction, arg);
-}
-
-static void buffer_date(char *date)
-{
-  time_t t;
-
-  if (!date || !*date) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    return;
-  }
-
-  strip_arg_special_chars(date);
-
-  t = from_iso8601(date, 0);
-  if (t)
-    scr_BufferDate(t);
-  else
-    scr_LogPrint(LPRINT_NORMAL, "The date you specified is "
-                 "not correctly formatted or invalid.");
-}
-
-static void buffer_percent(char *arg1, char *arg2)
-{
-  // Basically, user has typed "%arg1 arg2"
-  // "%50"  -> arg1 = 50, arg2 null pointer
-  // "% 50" -> arg1 = \0, arg2 = 50
-
-  if (!*arg1 && (!arg2 || !*arg2)) { // No value
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    return;
-  }
-
-  if (*arg1 && arg2 && *arg2) {     // Two values
-    scr_LogPrint(LPRINT_NORMAL, "Wrong parameters.");
-    return;
-  }
-
-  scr_BufferPercent(atoi((*arg1 ? arg1 : arg2)));
-}
-
-static void do_buffer(char *arg)
-{
-  char **paramlst;
-  char *subcmd;
-
-  if (!current_buddy)
-    return;
-
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP &&
-      strcasecmp(subcmd, "close_all")) {
-    scr_LogPrint(LPRINT_NORMAL, "Groups have no buffer.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(subcmd, "top")) {
-    scr_BufferTopBottom(-1);
-  } else if (!strcasecmp(subcmd, "bottom")) {
-    scr_BufferTopBottom(1);
-  } else if (!strcasecmp(subcmd, "clear")) {
-    scr_BufferClear();
-  } else if (!strcasecmp(subcmd, "close")) {
-    scr_BufferPurge(1, arg);
-  } else if (!strcasecmp(subcmd, "close_all")) {
-    scr_BufferPurgeAll(1);
-  } else if (!strcasecmp(subcmd, "purge")) {
-    scr_BufferPurge(0, arg);
-  } else if (!strcasecmp(subcmd, "scroll_lock")) {
-    scr_BufferScrollLock(1);
-  } else if (!strcasecmp(subcmd, "scroll_unlock")) {
-    scr_BufferScrollLock(0);
-  } else if (!strcasecmp(subcmd, "scroll_toggle")) {
-    scr_BufferScrollLock(-1);
-  } else if (!strcasecmp(subcmd, "up")) {
-    buffer_updown(-1, arg);
-  } else if (!strcasecmp(subcmd, "down")) {
-    buffer_updown(1, arg);
-  } else if (!strcasecmp(subcmd, "search_backward")) {
-    strip_arg_special_chars(arg);
-    buffer_search(-1, arg);
-  } else if (!strcasecmp(subcmd, "search_forward")) {
-    strip_arg_special_chars(arg);
-    buffer_search(1, arg);
-  } else if (!strcasecmp(subcmd, "date")) {
-    buffer_date(arg);
-  } else if (*subcmd == '%') {
-    buffer_percent(subcmd+1, arg);
-  } else if (!strcasecmp(subcmd, "save")) {
-    scr_BufferDump(arg);
-  } else if (!strcasecmp(subcmd, "list")) {
-    scr_BufferList();
-  } else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  }
-
-  free_arg_lst(paramlst);
-}
-
-static void do_clear(char *arg)    // Alias for "buffer clear"
-{
-  do_buffer("clear");
-}
-
-static void do_info(char *arg)
-{
-  gpointer bud;
-  const char *bjid, *name;
-  guint type, on_srv;
-  char *buffer;
-  enum subscr esub;
-
-  if (!current_buddy)
-    return;
-  bud = BUDDATA(current_buddy);
-
-  bjid   = buddy_getjid(bud);
-  name   = buddy_getname(bud);
-  type   = buddy_gettype(bud);
-  esub   = buddy_getsubscription(bud);
-  on_srv = buddy_getonserverflag(bud);
-
-  buffer = g_new(char, 4096);
-
-  if (bjid) {
-    GSList *resources, *p_res;
-    char *bstr = "unknown";
-
-    // Enter chat mode
-    scr_set_chatmode(TRUE);
-    scr_ShowBuddyWindow();
-
-    snprintf(buffer, 4095, "jid:  <%s>", bjid);
-    scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-    if (name) {
-      snprintf(buffer, 4095, "Name: %s", name);
-      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-    }
-
-    if (type == ROSTER_TYPE_USER)       bstr = "user";
-    else if (type == ROSTER_TYPE_ROOM)  bstr = "chatroom";
-    else if (type == ROSTER_TYPE_AGENT) bstr = "agent";
-    snprintf(buffer, 127, "Type: %s", bstr);
-    scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-
-    if (!on_srv) {
-      scr_WriteIncomingMessage(bjid, "(Local item, not on the server)",
-                               0, HBB_PREFIX_INFO, 0);
-    }
-
-    if (esub == sub_both)     bstr = "both";
-    else if (esub & sub_from) bstr = "from";
-    else if (esub & sub_to)   bstr = "to";
-    else bstr = "none";
-    snprintf(buffer, 64, "Subscription: %s", bstr);
-    if (esub & sub_pending)
-      strcat(buffer, " (pending)");
-    scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-
-    resources = buddy_getresources(bud);
-    if (!resources && type == ROSTER_TYPE_USER) {
-      // No resource; display last status message, if any.
-      const char *rst_msg = buddy_getstatusmsg(bud, "");
-      if (rst_msg) {
-        snprintf(buffer, 4095, "Last status message: %s", rst_msg);
-        scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-      }
-    }
-    for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
-      gchar rprio;
-      enum imstatus rstatus;
-      const char *rst_msg;
-      time_t rst_time;
-      struct pgp_data *rpgp;
-
-      rprio   = buddy_getresourceprio(bud, p_res->data);
-      rstatus = buddy_getstatus(bud, p_res->data);
-      rst_msg = buddy_getstatusmsg(bud, p_res->data);
-      rst_time = buddy_getstatustime(bud, p_res->data);
-      rpgp = buddy_resource_pgp(bud, p_res->data);
-
-      snprintf(buffer, 4095, "Resource: [%c] (%d) %s", imstatus2char[rstatus],
-               rprio, (char*)p_res->data);
-      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-      if (rst_msg) {
-        snprintf(buffer, 4095, "Status message: %s", rst_msg);
-        scr_WriteIncomingMessage(bjid, buffer,
-                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-      }
-      if (rst_time) {
-        char tbuf[128];
-
-        strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time));
-        snprintf(buffer, 127, "Status timestamp: %s", tbuf);
-        scr_WriteIncomingMessage(bjid, buffer,
-                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-      }
-#ifdef HAVE_GPGME
-      if (rpgp && rpgp->sign_keyid) {
-        snprintf(buffer, 4095, "PGP key id: %s", rpgp->sign_keyid);
-        scr_WriteIncomingMessage(bjid, buffer,
-                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-        if (rpgp->last_sigsum) {
-          gpgme_sigsum_t ss = rpgp->last_sigsum;
-          snprintf(buffer, 4095, "Last PGP signature: %s",
-                  (ss & GPGME_SIGSUM_GREEN ? "good":
-                   (ss & GPGME_SIGSUM_RED ? "bad" : "unknown")));
-          scr_WriteIncomingMessage(bjid, buffer,
-                                   0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-        }
-      }
-#endif
-      g_free(p_res->data);
-    }
-    g_slist_free(resources);
-  } else {  /* Item has no jid */
-    if (name) scr_LogPrint(LPRINT_NORMAL, "Name: %s", name);
-    scr_LogPrint(LPRINT_NORMAL, "Type: %s",
-                 type == ROSTER_TYPE_GROUP ? "group" :
-                 (type == ROSTER_TYPE_SPECIAL ? "special" : "unknown"));
-  }
-  g_free(buffer);
-
-  // Tell the user if this item has an annotation.
-  if (type == ROSTER_TYPE_USER ||
-      type == ROSTER_TYPE_ROOM ||
-      type == ROSTER_TYPE_AGENT) {
-    struct annotation *note = xmpp_get_storage_rosternotes(bjid, TRUE);
-    if (note) {
-      // We do not display the note, we just tell the user.
-      g_free(note->text);
-      g_free(note->jid);
-      g_free(note);
-      scr_WriteIncomingMessage(bjid, "(This item has an annotation)", 0,
-                               HBB_PREFIX_INFO, 0);
-    }
-  }
-}
-
-// room_names() is a variation of do_info(), for chatrooms only
-static void room_names(gpointer bud, char *arg)
-{
-  const char *bjid;
-  char *buffer;
-  GSList *resources, *p_res;
-  enum { style_normal = 0, style_detail, style_short,
-         style_quiet, style_compact } style = 0;
-
-  if (*arg) {
-    if (!strcasecmp(arg, "--short"))
-      style = style_short;
-    else if (!strcasecmp(arg, "--quiet"))
-      style = style_quiet;
-    else if (!strcasecmp(arg, "--detail"))
-      style = style_detail;
-    else if (!strcasecmp(arg, "--compact"))
-      style = style_compact;
-    else {
-      scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-      return;
-    }
-  }
-
-  // Enter chat mode
-  scr_set_chatmode(TRUE);
-  scr_ShowBuddyWindow();
-
-  bjid = buddy_getjid(bud);
-
-  buffer = g_new(char, 4096);
-  strncpy(buffer, "Room members:", 127);
-  scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-
-  resources = buddy_getresources(bud);
-  for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
-    enum imstatus rstatus;
-    const char *rst_msg;
-
-    rstatus = buddy_getstatus(bud, p_res->data);
-    rst_msg = buddy_getstatusmsg(bud, p_res->data);
-
-    if (style == style_short) {
-      snprintf(buffer, 4095, "[%c] %s%s%s", imstatus2char[rstatus],
-               (char*)p_res->data,
-               rst_msg ? " -- " : "", rst_msg ? rst_msg : "");
-      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-    } else if (style == style_compact) {
-        enum imrole role = buddy_getrole(bud, p_res->data);
-        enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
-        bool showaffil = (affil != affil_none);
-
-        snprintf(buffer, 4095, "[%c] %s (%s%s%s)",
-                 imstatus2char[rstatus], (char*)p_res->data,
-                 showaffil ? straffil[affil] : "\0",
-                 showaffil ? "/" : "\0",
-                 strrole[role]);
-        scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-      } else {
-      // (Style "normal", "detail" or "quiet")
-      snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus],
-               (char*)p_res->data);
-      scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
-      if (rst_msg && style != style_quiet) {
-        snprintf(buffer, 4095, "Status message: %s", rst_msg);
-        scr_WriteIncomingMessage(bjid, buffer,
-                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-      }
-      if (style == style_detail) {
-        enum imrole role = buddy_getrole(bud, p_res->data);
-        enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
-
-        snprintf(buffer, 4095, "Role: %s", strrole[role]);
-        scr_WriteIncomingMessage(bjid, buffer,
-                                 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-        if (affil != affil_none) {
-          snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]);
-          scr_WriteIncomingMessage(bjid, buffer,
-                                   0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-        }
-      }
-    }
-    g_free(p_res->data);
-  }
-  g_slist_free(resources);
-  g_free(buffer);
-}
-
-static void move_group_member(gpointer bud, void *groupnamedata)
-{
-  const char *bjid, *name, *groupname;
-
-  groupname = (char *)groupnamedata;
-
-  bjid = buddy_getjid(bud);
-  name = buddy_getname(bud);
-
-  xmpp_updatebuddy(bjid, name, *groupname ? groupname : NULL);
-}
-
-static void do_rename(char *arg)
-{
-  gpointer bud;
-  const char *bjid, *group;
-  guint type, on_srv;
-  char *newname, *p;
-  char *name_utf8;
-
-  if (!current_buddy)
-    return;
-  bud = BUDDATA(current_buddy);
-
-  bjid   = buddy_getjid(bud);
-  group  = buddy_getgroupname(bud);
-  type   = buddy_gettype(bud);
-  on_srv = buddy_getonserverflag(bud);
-
-  if (type & ROSTER_TYPE_SPECIAL) {
-    scr_LogPrint(LPRINT_NORMAL, "You can't rename this item.");
-    return;
-  }
-
-  if (!*arg && !(type & ROSTER_TYPE_GROUP)) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a new name.");
-    return;
-  }
-
-  if (!(type & ROSTER_TYPE_GROUP) && !on_srv) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Note: this item will be added to your server roster.");
-    // If this is a MUC room w/o bookmark, let's give a small hint...
-    if ((type & ROSTER_TYPE_ROOM) && !xmpp_is_bookmarked(bjid)) {
-      scr_LogPrint(LPRINT_NORMAL,
-                   "You should add a room bookmark or it will not be "
-                   "recognized as a MUC room next time you run mcabber.");
-    }
-  }
-
-  newname = g_strdup(arg);
-  // Remove trailing space
-  for (p = newname; *p; p++) ;
-  while (p > newname && *p == ' ') *p = 0;
-
-  strip_arg_special_chars(newname);
-
-  name_utf8 = to_utf8(newname);
-
-  if (type & ROSTER_TYPE_GROUP) {
-    // Rename a whole group
-    foreach_group_member(bud, &move_group_member, name_utf8);
-    // Let's jump to the previous buddy, because this group name should
-    // disappear when we receive the server answer.
-    scr_RosterUpDown(-1, 1);
-  } else {
-    // Rename a single buddy
-    guint del_name = 0;
-    if (!*newname || !strcmp(arg, "-"))
-      del_name = TRUE;
-    buddy_setname(bud, (del_name ? (char*)bjid : name_utf8));
-    xmpp_updatebuddy(bjid, (del_name ? NULL : name_utf8), group);
-  }
-
-  g_free(name_utf8);
-  g_free(newname);
-  update_roster = TRUE;
-}
-
-static void do_move(char *arg)
-{
-  gpointer bud;
-  const char *bjid, *name, *oldgroupname;
-  guint type;
-  char *newgroupname, *p;
-  char *group_utf8;
-
-  if (!current_buddy)
-    return;
-  bud = BUDDATA(current_buddy);
-
-  bjid = buddy_getjid(bud);
-  name = buddy_getname(bud);
-  type = buddy_gettype(bud);
-
-  oldgroupname = buddy_getgroupname(bud);
-
-  if (type & ROSTER_TYPE_GROUP) {
-    scr_LogPrint(LPRINT_NORMAL, "You can't move groups!");
-    return;
-  }
-  if (type & ROSTER_TYPE_SPECIAL) {
-    scr_LogPrint(LPRINT_NORMAL, "You can't move this item.");
-    return;
-  }
-
-  newgroupname = g_strdup(arg);
-  // Remove trailing space
-  for (p = newgroupname; *p; p++) ;
-  while (p > newgroupname && *p == ' ') *p-- = 0;
-
-  strip_arg_special_chars(newgroupname);
-
-  group_utf8 = to_utf8(newgroupname);
-  if (strcmp(oldgroupname, group_utf8)) {
-    guint msgflag;
-
-    xmpp_updatebuddy(bjid, name, *group_utf8 ? group_utf8 : NULL);
-    scr_RosterUpDown(-1, 1);
-
-    // If the buddy has a pending message flag,
-    // we remove it temporarily in order to reset the global group
-    // flag.  We set it back once the buddy is in the new group,
-    // which will update the new group's flag.
-    msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG;
-    if (msgflag)
-      roster_msg_setflag(bjid, FALSE, FALSE);
-    buddy_setgroup(bud, group_utf8);
-    if (msgflag)
-      roster_msg_setflag(bjid, FALSE, TRUE);
-  }
-
-  g_free(group_utf8);
-  g_free(newgroupname);
-  update_roster = TRUE;
-}
-
-static void print_option_cb(char *k, char *v, void *f)
-{
-  char *format = (char *)f;
-  scr_LogPrint (LPRINT_NORMAL, format, k, v);
-}
-
-static void do_set(char *arg)
-{
-  guint assign;
-  gchar *option, *value;
-  gchar *option_utf8;
-
-  if (!*arg) {
-    // list all set options
-    settings_foreach(SETTINGS_TYPE_OPTION, print_option_cb, "%s = [%s]");
-    return;
-  }
-
-  assign = parse_assigment(arg, &option, &value);
-  if (!option) {
-    scr_LogPrint(LPRINT_NORMAL, "Set what option?");
-    return;
-  }
-  option_utf8 = to_utf8(option);
-  g_free(option);
-  if (!assign) {  // This is a query
-    const char *val = settings_opt_get(option_utf8);
-    if (val)
-      scr_LogPrint(LPRINT_NORMAL, "%s = [%s]", option_utf8, val);
-    else
-      scr_LogPrint(LPRINT_NORMAL, "Option %s is not set", option_utf8);
-    g_free(option_utf8);
-    return;
-  }
-  // Update the option
-  // Maybe some options should be protected when user is connected (server,
-  // username, etc.).  And we should catch some options here, too
-  // (hide_offline_buddies for ex.)
-  if (!value) {
-    settings_del(SETTINGS_TYPE_OPTION, option_utf8);
-  } else {
-    gchar *value_utf8 = to_utf8(value);
-    settings_set(SETTINGS_TYPE_OPTION, option_utf8, value_utf8);
-    g_free(value_utf8);
-    g_free(value);
-  }
-  g_free(option_utf8);
-}
-
-static void dump_alias(char *k, char *v, void *param)
-{
-  scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "Alias %s = %s", k, v);
-}
-
-static void do_alias(char *arg)
-{
-  guint assign;
-  gchar *alias, *value;
-
-  assign = parse_assigment(arg, &alias, &value);
-  if (!alias) {
-    settings_foreach(SETTINGS_TYPE_ALIAS, &dump_alias, NULL);
-    return;
-  }
-  if (!assign) {  // This is a query
-    const char *val = settings_get(SETTINGS_TYPE_ALIAS, alias);
-    // NOTE: LPRINT_NOTUTF8 here, see below why it isn't encoded...
-    if (val)
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "%s = %s", alias, val);
-    else
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "Alias '%s' does not exist", alias);
-    goto do_alias_return;
-  }
-  // Check the alias does not conflict with a registered command
-  if (cmd_get(alias)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "'%s' is a reserved word!", alias);
-      goto do_alias_return;
-  }
-  // Update the alias
-  if (!value) {
-    if (settings_get(SETTINGS_TYPE_ALIAS, alias)) {
-      settings_del(SETTINGS_TYPE_ALIAS, alias);
-      // Remove alias from the completion list
-      compl_del_category_word(COMPL_CMD, alias);
-    }
-  } else {
-    /* Add alias to the completion list, if not already in.
-       NOTE: We're not UTF8-encoding "alias" and "value" here because UTF-8 is
-       not yet supported in the UI... (and we use the values in the completion
-       system)
-    */
-    if (!settings_get(SETTINGS_TYPE_ALIAS, alias))
-      compl_add_category_word(COMPL_CMD, alias);
-    settings_set(SETTINGS_TYPE_ALIAS, alias, value);
-    g_free(value);
-  }
-do_alias_return:
-  g_free(alias);
-}
-
-static void dump_bind(char *k, char *v, void *param)
-{
-  scr_LogPrint(LPRINT_NORMAL, "Key %4s is bound to: %s", k, v);
-}
-
-static void do_bind(char *arg)
-{
-  guint assign;
-  gchar *k_code, *value;
-
-  assign = parse_assigment(arg, &k_code, &value);
-  if (!k_code) {
-    settings_foreach(SETTINGS_TYPE_BINDING, &dump_bind, NULL);
-    return;
-  }
-  if (!assign) {  // This is a query
-    const char *val = settings_get(SETTINGS_TYPE_BINDING, k_code);
-    if (val)
-      scr_LogPrint(LPRINT_NORMAL, "Key %s is bound to: %s", k_code, val);
-    else
-      scr_LogPrint(LPRINT_NORMAL, "Key %s is not bound.", k_code);
-    g_free(k_code);
-    return;
-  }
-  // Update the key binding
-  if (!value) {
-    settings_del(SETTINGS_TYPE_BINDING, k_code);
-  } else {
-    gchar *value_utf8 = to_utf8(value);
-    settings_set(SETTINGS_TYPE_BINDING, k_code, value_utf8);
-    g_free(value_utf8);
-    g_free(value);
-  }
-  g_free(k_code);
-}
-
-static void do_rawxml(char *arg)
-{
-  char **paramlst;
-  char *subcmd;
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    return;
-  }
-
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Please read the manual page"
-                 " before using /rawxml :-)");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(subcmd, "send"))  {
-    gchar *buffer;
-
-    if (!subcmd || !*subcmd) {
-      scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-      free_arg_lst(paramlst);
-      return;
-    }
-
-    // We don't strip_arg_special_chars() here, because it would be a pain for
-    // the user to escape quotes in a XML stream...
-
-    buffer = to_utf8(arg);
-    if (buffer) {
-      scr_LogPrint(LPRINT_NORMAL, "Sending XML string");
-      lm_connection_send_raw(lconnection, buffer, NULL);
-      g_free(buffer);
-    } else {
-      scr_LogPrint(LPRINT_NORMAL, "Conversion error in XML string.");
-    }
-  } else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  }
-
-  free_arg_lst(paramlst);
-}
-
-//  check_room_subcommand(arg, param_needed, buddy_must_be_a_room)
-// - Check if this is a room, if buddy_must_be_a_room is not null
-// - Check there is at least 1 parameter, if param_needed is true
-// - Return null if one of the checks fails, or a pointer to the first
-//   non-space character.
-static char *check_room_subcommand(char *arg, bool param_needed,
-                                   gpointer buddy_must_be_a_room)
-{
-  if (buddy_must_be_a_room &&
-      !(buddy_gettype(buddy_must_be_a_room) & ROSTER_TYPE_ROOM)) {
-    scr_LogPrint(LPRINT_NORMAL, "This isn't a conference room.");
-    return NULL;
-  }
-
-  if (param_needed) {
-    if (!arg) {
-      scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-      return NULL;
-    }
-  }
-
-  if (arg)
-    return arg;
-  else
-    return "";
-}
-
-static void room_join(gpointer bud, char *arg)
-{
-  char **paramlst;
-  char *roomname, *nick, *pass;
-  char *roomname_tmp = NULL;
-  char *pass_utf8;
-
-  paramlst = split_arg(arg, 3, 0); // roomid, nickname, password
-  roomname = *paramlst;
-  nick = *(paramlst+1);
-  pass = *(paramlst+2);
-
-  if (!roomname)
-    nick = NULL;
-  if (!nick)
-    pass = NULL;
-
-  if (!roomname || !strcmp(roomname, ".")) {
-    // If the current_buddy is recognized as a room, the room name
-    // can be omitted (or "." can be used).
-    if (!bud || !(buddy_gettype(bud) & ROSTER_TYPE_ROOM)) {
-      scr_LogPrint(LPRINT_NORMAL, "Please specify a room name.");
-      free_arg_lst(paramlst);
-      return;
-    }
-    roomname = (char*)buddy_getjid(bud);
-  } else if (strchr(roomname, '/')) {
-    scr_LogPrint(LPRINT_NORMAL, "Invalid room name.");
-    free_arg_lst(paramlst);
-    return;
-  } else {
-    // The room id has been specified.  Let's convert it and use it.
-    mc_strtolower(roomname);
-    roomname = roomname_tmp = to_utf8(roomname);
-  }
-
-  // If no nickname is provided with the /join command,
-  // we try to get a default nickname.
-  if (!nick || !*nick)
-    nick = default_muc_nickname(roomname);
-  else
-    nick = to_utf8(nick);
-  // If we still have no nickname, give up
-  if (!nick || !*nick) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname.");
-    g_free(nick);
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  pass_utf8 = to_utf8(pass);
-
-  xmpp_room_join(roomname, nick, pass_utf8);
-
-  scr_LogPrint(LPRINT_LOGNORM, "Sent a join request to <%s>...", roomname);
-
-  g_free(roomname_tmp);
-  g_free(nick);
-  g_free(pass_utf8);
-  buddylist_build();
-  update_roster = TRUE;
-  free_arg_lst(paramlst);
-}
-
-static void room_invite(gpointer bud, char *arg)
-{
-  char **paramlst;
-  const gchar *roomname;
-  char* fjid;
-  gchar *reason_utf8;
-
-  paramlst = split_arg(arg, 2, 1); // jid, [reason]
-  fjid = *paramlst;
-  arg = *(paramlst+1);
-  // An empty reason is no reason...
-  if (arg && !*arg)
-    arg = NULL;
-
-  if (!fjid || !*fjid) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing or incorrect Jabber ID.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  roomname = buddy_getjid(bud);
-  reason_utf8 = to_utf8(arg);
-  xmpp_room_invite(roomname, fjid, reason_utf8);
-  scr_LogPrint(LPRINT_LOGNORM, "Invitation sent to <%s>.", fjid);
-  g_free(reason_utf8);
-  free_arg_lst(paramlst);
-}
-
-static void room_affil(gpointer bud, char *arg)
-{
-  char **paramlst;
-  gchar *fjid, *rolename;
-  struct role_affil ra;
-  const char *roomid = buddy_getjid(bud);
-
-  paramlst = split_arg(arg, 3, 1); // jid, new_affil, [reason]
-  fjid = *paramlst;
-  rolename = *(paramlst+1);
-  arg = *(paramlst+2);
-
-  if (!fjid || !*fjid || !rolename || !*rolename) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  ra.type = type_affil;
-  ra.val.affil = affil_none;
-  for (; ra.val.affil < imaffiliation_size; ra.val.affil++)
-    if (!strcasecmp(rolename, straffil[ra.val.affil]))
-      break;
-
-  if (ra.val.affil < imaffiliation_size) {
-    gchar *jid_utf8, *reason_utf8;
-    jid_utf8 = to_utf8(fjid);
-    reason_utf8 = to_utf8(arg);
-    xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8);
-    g_free(jid_utf8);
-    g_free(reason_utf8);
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Wrong affiliation parameter.");
-
-  free_arg_lst(paramlst);
-}
-
-static void room_role(gpointer bud, char *arg)
-{
-  char **paramlst;
-  gchar *fjid, *rolename;
-  struct role_affil ra;
-  const char *roomid = buddy_getjid(bud);
-
-  paramlst = split_arg(arg, 3, 1); // jid, new_role, [reason]
-  fjid = *paramlst;
-  rolename = *(paramlst+1);
-  arg = *(paramlst+2);
-
-  if (!fjid || !*fjid || !rolename || !*rolename) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify both a Jabber ID and a role.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  ra.type = type_role;
-  ra.val.role = role_none;
-  for (; ra.val.role < imrole_size; ra.val.role++)
-    if (!strcasecmp(rolename, strrole[ra.val.role]))
-      break;
-
-  if (ra.val.role < imrole_size) {
-    gchar *jid_utf8, *reason_utf8;
-    jid_utf8 = to_utf8(fjid);
-    reason_utf8 = to_utf8(arg);
-    xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, reason_utf8);
-    g_free(jid_utf8);
-    g_free(reason_utf8);
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Wrong role parameter.");
-
-  free_arg_lst(paramlst);
-}
-
-
-// The expected argument is a Jabber id
-static void room_ban(gpointer bud, char *arg)
-{
-  char **paramlst;
-  gchar *fjid, *bjid;
-  const gchar *banjid;
-  gchar *jid_utf8, *reason_utf8;
-  struct role_affil ra;
-  const char *roomid = buddy_getjid(bud);
-
-  paramlst = split_arg(arg, 2, 1); // jid, [reason]
-  fjid = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!fjid || !*fjid) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  ra.type = type_affil;
-  ra.val.affil = affil_outcast;
-
-  bjid = jidtodisp(fjid);
-  jid_utf8 = to_utf8(bjid);
-
-  // If the argument doesn't look like a jid, we'll try to find a matching
-  // nickname.
-  if (!strchr(bjid, JID_DOMAIN_SEPARATOR) || check_jid_syntax(bjid)) {
-    const gchar *tmp;
-    // We want the initial argument, so the fjid variable, because
-    // we don't want to strip a resource-like string from the nickname!
-    g_free(jid_utf8);
-    jid_utf8 = to_utf8(fjid);
-    tmp = buddy_getrjid(bud, jid_utf8);
-    if (!tmp) {
-      scr_LogPrint(LPRINT_NORMAL, "Wrong JID or nickname");
-      goto room_ban_return;
-    }
-    banjid = jidtodisp(tmp);
-  } else
-    banjid = jid_utf8;
-
-  scr_LogPrint(LPRINT_NORMAL, "Requesting a ban for %s", banjid);
-
-  reason_utf8 = to_utf8(arg);
-  xmpp_room_setattrib(roomid, banjid, NULL, ra, reason_utf8);
-  g_free(reason_utf8);
-
-room_ban_return:
-  g_free(bjid);
-  g_free(jid_utf8);
-  free_arg_lst(paramlst);
-}
-
-// The expected argument is a Jabber id
-static void room_unban(gpointer bud, char *arg)
-{
-  gchar *fjid = arg;
-  gchar *jid_utf8;
-  struct role_affil ra;
-  const char *roomid = buddy_getjid(bud);
-
-  if (!fjid || !*fjid) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-    return;
-  }
-
-  ra.type = type_affil;
-  ra.val.affil = affil_none;
-
-  jid_utf8 = to_utf8(fjid);
-  xmpp_room_setattrib(roomid, jid_utf8, NULL, ra, NULL);
-  g_free(jid_utf8);
-}
-
-// The expected argument is a nickname
-static void room_kick(gpointer bud, char *arg)
-{
-  char **paramlst;
-  gchar *nick;
-  gchar *nick_utf8, *reason_utf8;
-  struct role_affil ra;
-  const char *roomid = buddy_getjid(bud);
-
-  paramlst = split_arg(arg, 2, 1); // nickname, [reason]
-  nick = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!nick || !*nick) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  ra.type = type_role;
-  ra.val.affil = role_none;
-
-  nick_utf8 = to_utf8(nick);
-  reason_utf8 = to_utf8(arg);
-  xmpp_room_setattrib(roomid, NULL, nick_utf8, ra, reason_utf8);
-  g_free(nick_utf8);
-  g_free(reason_utf8);
-
-  free_arg_lst(paramlst);
-}
-
-void cmd_room_leave(gpointer bud, char *arg)
-{
-  gchar *roomid, *desc;
-  const char *nickname;
-
-  nickname = buddy_getnickname(bud);
-  if (!nickname) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not in this room.");
-    return;
-  }
-
-  roomid = g_strdup_printf("%s/%s", buddy_getjid(bud), nickname);
-  desc = to_utf8(arg);
-  xmpp_setstatus(offline, roomid, desc, TRUE);
-  g_free(desc);
-  g_free(roomid);
-}
-
-static void room_nick(gpointer bud, char *arg)
-{
-  if (!buddy_getinsideroom(bud)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not in this room.");
-    return;
-  }
-
-  if (!arg || !*arg) {
-    const char *nick = buddy_getnickname(bud);
-    if (nick)
-      scr_LogPrint(LPRINT_NORMAL, "Your nickname is: %s", nick);
-    else
-      scr_LogPrint(LPRINT_NORMAL, "You have no nickname in this room.");
-  } else {
-    gchar *nick = to_utf8(arg);
-    strip_arg_special_chars(nick);
-    xmpp_room_join(buddy_getjid(bud), nick, NULL);
-    g_free(nick);
-  }
-}
-
-static void room_privmsg(gpointer bud, char *arg)
-{
-  char **paramlst;
-  gchar *fjid_utf8, *nick, *nick_utf8, *msg;
-
-  paramlst = split_arg(arg, 2, 1); // nickname, message
-  nick = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!nick || !*nick || !arg || !*arg) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Please specify both a Jabber ID and a message.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  nick_utf8 = to_utf8(nick);
-  fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8);
-  g_free (nick_utf8);
-  msg = to_utf8(arg);
-  send_message_to(fjid_utf8, msg, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, FALSE);
-  g_free(fjid_utf8);
-  g_free(msg);
-  free_arg_lst(paramlst);
-}
-
-static void room_remove(gpointer bud, char *arg)
-{
-  if (*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; "
-                 "the currently-selected room will be removed.");
-    return;
-  }
-
-  // Quick check: if there are resources, we haven't left
-  if (buddy_getinsideroom(bud)) {
-    scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!");
-    return;
-  }
-  // Delete the room
-  roster_del_user(buddy_getjid(bud));
-  scr_UpdateBuddyWindow();
-  buddylist_build();
-  update_roster = TRUE;
-}
-
-static void room_topic(gpointer bud, char *arg)
-{
-  if (!buddy_getinsideroom(bud)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not in this room.");
-    return;
-  }
-
-  // If no parameter is given, display the current topic
-  if (!*arg) {
-    const char *topic = buddy_gettopic(bud);
-    if (topic)
-      scr_LogPrint(LPRINT_NORMAL, "Topic: %s", topic);
-    else
-      scr_LogPrint(LPRINT_NORMAL, "No topic has been set.");
-    return;
-  }
-
-  // If arg is "-", let's clear the topic
-  if (!strcmp(arg, "-"))
-    arg = NULL;
-
-  arg = to_utf8(arg);
-  // Set the topic
-  xmpp_send_msg(buddy_getjid(bud), NULL, ROSTER_TYPE_ROOM, arg ? arg : "",
-                FALSE, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, NULL);
-  g_free(arg);
-}
-
-static void room_destroy(gpointer bud, char *arg)
-{
-  gchar *msg;
-
-  if (arg && *arg)
-    msg = to_utf8(arg);
-  else
-    msg = NULL;
-
-  xmpp_room_destroy(buddy_getjid(bud), NULL, msg);
-  g_free(msg);
-}
-
-static void room_unlock(gpointer bud, char *arg)
-{
-  if (*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "Unknown parameter.");
-    return;
-  }
-
-  xmpp_room_unlock(buddy_getjid(bud));
-}
-
-static void room_setopt(gpointer bud, char *arg)
-{
-  char **paramlst;
-  char *param, *value;
-  enum { opt_none = 0, opt_printstatus, opt_autowhois } option = 0;
-
-  paramlst = split_arg(arg, 2, 1); // param, value
-  param = *paramlst;
-  value = *(paramlst+1);
-  if (!param) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a room option.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(param, "print_status"))
-    option = opt_printstatus;
-  else if (!strcasecmp(param, "auto_whois"))
-    option = opt_autowhois;
-  else {
-    scr_LogPrint(LPRINT_NORMAL, "Wrong option!");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  // If no value is given, display the current value
-  if (!value) {
-    const char *strval;
-    if (option == opt_printstatus)
-      strval = strprintstatus[buddy_getprintstatus(bud)];
-    else
-      strval = strautowhois[buddy_getautowhois(bud)];
-    scr_LogPrint(LPRINT_NORMAL, "%s is set to: %s", param, strval);
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (option == opt_printstatus) {
-    enum room_printstatus eval;
-    if (!strcasecmp(value, "none"))
-      eval = status_none;
-    else if (!strcasecmp(value, "in_and_out"))
-      eval = status_in_and_out;
-    else if (!strcasecmp(value, "all"))
-      eval = status_all;
-    else {
-      eval = status_default;
-      if (strcasecmp(value, "default") != 0)
-        scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default...");
-    }
-    buddy_setprintstatus(bud, eval);
-  } else if (option == opt_autowhois) {
-    enum room_autowhois eval;
-    if (!strcasecmp(value, "on"))
-      eval = autowhois_on;
-    else if (!strcasecmp(value, "off"))
-      eval = autowhois_off;
-    else {
-      eval = autowhois_default;
-      if (strcasecmp(value, "default") != 0)
-        scr_LogPrint(LPRINT_NORMAL, "Unrecognized value, assuming default...");
-    }
-    buddy_setautowhois(bud, eval);
-  }
-
-  free_arg_lst(paramlst);
-}
-
-//  cmd_room_whois(..)
-// If interactive is TRUE, chatmode can be enabled.
-void cmd_room_whois(gpointer bud, char *arg, guint interactive)
-{
-  char **paramlst;
-  gchar *nick, *buffer;
-  const char *bjid, *realjid;
-  const char *rst_msg;
-  gchar rprio;
-  enum imstatus rstatus;
-  enum imrole role;
-  enum imaffiliation affil;
-  time_t rst_time;
-  guint msg_flag = HBB_PREFIX_INFO;
-
-  paramlst = split_arg(arg, 1, 0); // nickname
-  nick = *paramlst;
-
-  if (!nick || !*nick) {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a nickname.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  nick = to_utf8(nick);
-
-  if (interactive) {
-    // Enter chat mode
-    scr_set_chatmode(TRUE);
-    scr_ShowBuddyWindow();
-  } else
-    msg_flag |= HBB_PREFIX_NOFLAG;
-
-  bjid = buddy_getjid(bud);
-  rstatus = buddy_getstatus(bud, nick);
-
-  if (rstatus == offline) {
-    scr_LogPrint(LPRINT_NORMAL, "No such member: %s", nick);
-    free_arg_lst(paramlst);
-    g_free(nick);
-    return;
-  }
-
-  rst_time = buddy_getstatustime(bud, nick);
-  rprio   = buddy_getresourceprio(bud, nick);
-  rst_msg = buddy_getstatusmsg(bud, nick);
-  if (!rst_msg) rst_msg = "";
-
-  role = buddy_getrole(bud, nick);
-  affil = buddy_getaffil(bud, nick);
-  realjid = buddy_getrjid(bud, nick);
-
-  buffer = g_new(char, 4096);
-
-  snprintf(buffer, 4095, "Whois [%s]", nick);
-  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag, 0);
-  snprintf(buffer, 4095, "Status   : [%c] %s", imstatus2char[rstatus],
-           rst_msg);
-  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
-
-  if (rst_time) {
-    char tbuf[128];
-
-    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", localtime(&rst_time));
-    snprintf(buffer, 127, "Timestamp: %s", tbuf);
-    scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
-  }
-
-  if (realjid) {
-    snprintf(buffer, 4095, "JID      : <%s>", realjid);
-    scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
-  }
-
-  snprintf(buffer, 4095, "Role     : %s", strrole[role]);
-  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
-  snprintf(buffer, 4095, "Affiliat.: %s", straffil[affil]);
-  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
-  snprintf(buffer, 4095, "Priority : %d", rprio);
-  scr_WriteIncomingMessage(bjid, buffer, 0, msg_flag | HBB_PREFIX_CONT, 0);
-
-  scr_WriteIncomingMessage(bjid, "End of WHOIS", 0, msg_flag, 0);
-
-  g_free(buffer);
-  g_free(nick);
-  free_arg_lst(paramlst);
-}
-
-static void room_bookmark(gpointer bud, char *arg)
-{
-  const char *roomid;
-  const char *name = NULL, *nick = NULL;
-  char *tmpnick = NULL;
-  enum room_autowhois autowhois = 0;
-  enum room_printstatus printstatus = 0;
-  enum { bm_add = 0, bm_del = 1 } action = 0;
-  int autojoin = 0;
-  int nick_set = 0;
-
-  if (arg && *arg) {
-    // /room bookmark [add|del] [[+|-]autojoin] [-|nick]
-    char **paramlst;
-    char **pp;
-
-    paramlst = split_arg(arg, 3, 0); // At most 3 parameters
-    for (pp = paramlst; *pp; pp++) {
-      if (!strcasecmp(*pp, "add"))
-        action = bm_add;
-      else if (!strcasecmp(*pp, "del"))
-        action = bm_del;
-      else if (!strcasecmp(*pp, "-autojoin"))
-        autojoin = 0;
-      else if (!strcasecmp(*pp, "+autojoin") || !strcasecmp(*pp, "autojoin"))
-        autojoin = 1;
-      else if (!strcmp(*pp, "-"))
-        nick_set = 1;
-      else {
-        nick_set = 1;
-        nick = tmpnick = to_utf8 (*pp);
-      }
-    }
-    free_arg_lst(paramlst);
-  }
-
-  roomid = buddy_getjid(bud);
-
-  if (action == bm_add) {
-    name = buddy_getname(bud);
-    if (!nick_set)
-      nick = buddy_getnickname(bud);
-    printstatus = buddy_getprintstatus(bud);
-    autowhois   = buddy_getautowhois(bud);
-  }
-
-  xmpp_set_storage_bookmark(roomid, name, nick, NULL, autojoin,
-                            printstatus, autowhois);
-  g_free (tmpnick);
-}
-
-static void display_all_bookmarks(void)
-{
-  GSList *bm, *bmp;
-  GString *sbuf;
-  struct bookmark *bm_elt;
-
-  bm = xmpp_get_all_storage_bookmarks();
-
-  if (!bm)
-    return;
-
-  sbuf = g_string_new("");
-
-  scr_WriteIncomingMessage(NULL, "List of MUC bookmarks:",
-                           0, HBB_PREFIX_INFO, 0);
-
-  for (bmp = bm; bmp; bmp = g_slist_next(bmp)) {
-    bm_elt = bmp->data;
-    g_string_printf(sbuf, "%c <%s>",
-                    (bm_elt->autojoin ? '*' : ' '), bm_elt->roomjid);
-    if (bm_elt->nick)
-      g_string_append_printf(sbuf, " (%s)", bm_elt->nick);
-    if (bm_elt->name)
-      g_string_append_printf(sbuf, " %s", bm_elt->name);
-    g_free(bm_elt->roomjid);
-    g_free(bm_elt->name);
-    g_free(bm_elt->nick);
-    g_free(bm_elt);
-    scr_WriteIncomingMessage(NULL, sbuf->str,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-  }
-
-  scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
-  update_roster = TRUE;
-  g_string_free(sbuf, TRUE);
-  g_slist_free(bm);
-}
-
-#ifdef MODULES_ENABLE
-static gint module_list_comparator(gconstpointer arg1, gconstpointer arg2)
-{
-  const loaded_module_t *module = arg1;
-  const char *name = arg2;
-  return g_strcmp0(module->name, name);
-}
-
-static void do_load(char *arg)
-{
-  GModule *mod;
-  GSList *lmod;
-  char *mdir, *path;
-  if (!arg || !*arg) {
-    scr_LogPrint(LPRINT_LOGNORM, "Missing modulename.");
-    return;
-  }
-  lmod = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
-  if (lmod) {
-    scr_LogPrint(LPRINT_LOGNORM, "Module %s is already loaded.", arg);
-    return;
-  }
-  mdir = expand_filename(settings_opt_get("modules_dir"));
-  path = g_module_build_path(mdir, arg);
-  mod  = g_module_open(path, G_MODULE_BIND_LAZY);
-  if (!mod)
-    scr_LogPrint(LPRINT_LOGNORM, "Module loading failed: %s",
-                 g_module_error());
-  else {
-    loaded_module_t *module = g_new(loaded_module_t, 1);
-    module->name   = g_strdup(arg);
-    module->module = mod;
-    loaded_modules = g_slist_prepend(loaded_modules, module);
-    scr_LogPrint(LPRINT_LOGNORM, "Loaded module %s.", arg);
-  }
-  g_free(path);
-  g_free(mdir);
-}
-
-static void do_unload(char *arg)
-{
-  GSList *module;
-  if (!arg || !*arg) {
-    scr_LogPrint(LPRINT_LOGNORM, "Missing modulename.");
-    return;
-  }
-  module = g_slist_find_custom(loaded_modules, arg, module_list_comparator);
-  if (module) {
-    loaded_module_t *mod = module->data;
-    if (!g_module_close(mod->module))
-      scr_LogPrint(LPRINT_LOGNORM, "Module unloading failed: %s",
-                   g_module_error());
-    else {
-      g_free(mod->name);
-      g_free(mod);
-      loaded_modules = g_slist_delete_link(loaded_modules, module);
-      scr_LogPrint(LPRINT_LOGNORM, "Unloaded module %s.", arg);
-    }
-  } else
-    scr_LogPrint(LPRINT_LOGNORM, "Module %s not loaded.", arg);
-}
-#endif
-
-static void do_room(char *arg)
-{
-  char **paramlst;
-  char *subcmd;
-  gpointer bud;
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    return;
-  }
-
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (current_buddy) {
-    bud = BUDDATA(current_buddy);
-  } else {
-    if (strcasecmp(subcmd, "join")) {
-      free_arg_lst(paramlst);
-      return;
-    }
-    // "room join" is a special case, we don't need to have a valid
-    // current_buddy.
-    bud = NULL;
-  }
-
-  if (!strcasecmp(subcmd, "join"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, NULL)) != NULL)
-      room_join(bud, arg);
-  } else if (!strcasecmp(subcmd, "invite"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      room_invite(bud, arg);
-  } else if (!strcasecmp(subcmd, "affil"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      room_affil(bud, arg);
-  } else if (!strcasecmp(subcmd, "role"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      room_role(bud, arg);
-  } else if (!strcasecmp(subcmd, "ban"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      room_ban(bud, arg);
-  } else if (!strcasecmp(subcmd, "unban"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      room_unban(bud, arg);
-  } else if (!strcasecmp(subcmd, "kick"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      room_kick(bud, arg);
-  } else if (!strcasecmp(subcmd, "leave"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      cmd_room_leave(bud, arg);
-  } else if (!strcasecmp(subcmd, "names"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_names(bud, arg);
-  } else if (!strcasecmp(subcmd, "nick"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_nick(bud, arg);
-  } else if (!strcasecmp(subcmd, "privmsg"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      room_privmsg(bud, arg);
-  } else if (!strcasecmp(subcmd, "remove"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_remove(bud, arg);
-  } else if (!strcasecmp(subcmd, "destroy"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_destroy(bud, arg);
-  } else if (!strcasecmp(subcmd, "unlock"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_unlock(bud, arg);
-  } else if (!strcasecmp(subcmd, "setopt"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_setopt(bud, arg);
-  } else if (!strcasecmp(subcmd, "topic"))  {
-    if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_topic(bud, arg);
-  } else if (!strcasecmp(subcmd, "whois"))  {
-    if ((arg = check_room_subcommand(arg, TRUE, bud)) != NULL)
-      cmd_room_whois(bud, arg, TRUE);
-  } else if (!strcasecmp(subcmd, "bookmark"))  {
-    if (!arg && !buddy_getjid(BUDDATA(current_buddy)) &&
-        buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_SPECIAL)
-      display_all_bookmarks();
-    else if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
-      room_bookmark(bud, arg);
-  } else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  }
-
-  free_arg_lst(paramlst);
-}
-
-static void do_authorization(char *arg)
-{
-  char **paramlst;
-  char *subcmd;
-  char *jid_utf8;
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    return;
-  }
-
-  paramlst = split_arg(arg, 2, 0); // subcmd, [jid]
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    goto do_authorization_return;
-  }
-
-  // Use the provided jid, if it looks valid
-  if (arg) {
-    if (!*arg) {
-      // If no jid is provided, we use the current selected buddy
-      arg = NULL;
-    } else {
-      if (check_jid_syntax(arg)) {
-        scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                     "<%s> is not a valid Jabber ID.", arg);
-        goto do_authorization_return;
-      }
-    }
-  }
-
-  if (!arg) {       // Use the current selected buddy's jid
-    gpointer bud;
-    guint type;
-
-    if (!current_buddy)
-      goto do_authorization_return;
-    bud = BUDDATA(current_buddy);
-
-    jid_utf8 = arg  = (char*)buddy_getjid(bud);
-    type = buddy_gettype(bud);
-
-    if (!(type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT))) {
-      scr_LogPrint(LPRINT_NORMAL, "Invalid buddy.");
-      goto do_authorization_return;
-    }
-  } else {
-    jid_utf8 = to_utf8(arg);
-  }
-
-  if (!strcasecmp(subcmd, "allow"))  {
-    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBED);
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Sent presence subscription approval to <%s>.",
-                 jid_utf8);
-  } else if (!strcasecmp(subcmd, "cancel"))  {
-    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "<%s> will no longer receive your presence updates.",
-                 jid_utf8);
-  } else if (!strcasecmp(subcmd, "request"))  {
-    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_SUBSCRIBE);
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Sent presence notification request to <%s>.", jid_utf8);
-  } else if (!strcasecmp(subcmd, "request_unsubscribe"))  {
-    xmpp_send_s10n(jid_utf8, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE);
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Sent presence notification unsubscription request to <%s>.",
-                 jid_utf8);
-  } else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  }
-
-  // Only free jid_utf8 if it has been allocated, i.e. if != arg.
-  if (jid_utf8 && jid_utf8 != arg)
-    g_free(jid_utf8);
-do_authorization_return:
-  free_arg_lst(paramlst);
-}
-
-static void do_version(char *arg)
-{
-  gchar *ver = mcabber_version();
-  scr_LogPrint(LPRINT_NORMAL, "This is mcabber version %s.", ver);
-  g_free(ver);
-}
-
-static void do_request(char *arg)
-{
-  char **paramlst;
-  char *fjid, *type;
-  enum iqreq_type numtype = iqreq_none;
-  char *jid_utf8 = NULL;
-
-  paramlst = split_arg(arg, 2, 0); // type, jid
-  type = *paramlst;
-  fjid = *(paramlst+1);
-
-  if (type) {
-    // Quick check...
-    if (!strcasecmp(type, "version"))
-      numtype = iqreq_version;
-    else if (!strcasecmp(type, "time"))
-      numtype = iqreq_time;
-    else if (!strcasecmp(type, "last"))
-      numtype = iqreq_last;
-    else if (!strcasecmp(type, "vcard"))
-      numtype = iqreq_vcard;
-  }
-
-  if (!type || !numtype) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Please specify a query type (version, time...).");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  // Allow special jid "" or "." (current buddy)
-  if (fjid && (!*fjid || !strcmp(fjid, ".")))
-    fjid = NULL;
-
-  if (fjid) {
-    // The JID has been specified.  Quick check...
-    if (check_jid_syntax(fjid)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", fjid);
-      fjid = NULL;
-    } else {
-      // Convert jid to lowercase
-      char *p;
-      for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
-        *p = tolower(*p);
-      fjid = jid_utf8 = to_utf8(fjid);
-    }
-  } else {
-    // Add the current buddy
-    if (current_buddy)
-      fjid = (char*)buddy_getjid(BUDDATA(current_buddy));
-    if (!fjid)
-      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-  }
-
-  if (fjid) {
-    switch (numtype) {
-      case iqreq_version:
-      case iqreq_time:
-      case iqreq_last:
-      case iqreq_vcard:
-          xmpp_request(fjid, numtype);
-          break;
-      default:
-          break;
-    }
-  }
-  g_free(jid_utf8);
-  free_arg_lst(paramlst);
-}
-
-static void do_event(char *arg)
-{
-  char **paramlst;
-  char *evid, *subcmd;
-  int action = -1;
-  GSList *evidlst;
-
-  paramlst = split_arg(arg, 2, 0); // id, subcmd
-  evid = *paramlst;
-  subcmd = *(paramlst+1);
-
-  if (!evid || !subcmd) {
-    // Special case: /event list
-    if (evid && !strcasecmp(evid, "list"))
-      evs_display_list();
-    else
-      scr_LogPrint(LPRINT_NORMAL,
-                   "Missing parameter.  Usage: /event num action");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(subcmd, "reject"))
-    action = 0;
-  else if (!strcasecmp(subcmd, "accept"))
-    action = 1;
-  else if (!strcasecmp(subcmd, "ignore"))
-    action = 2;
-
-  if (action == -1) {
-    scr_LogPrint(LPRINT_NORMAL, "Wrong action parameter.");
-  } else if (action >= 0 && action <= 2) {
-    GSList *p;
-
-    if (action == 2) {
-      action = EVS_CONTEXT_CANCEL;
-    } else {
-      action += EVS_CONTEXT_USER;
-    }
-
-    if (!strcmp(evid, "*")) {
-      // Use completion list
-      evidlst = evs_geteventslist(FALSE);
-    } else {
-      // Let's create a slist with the provided event id
-      evidlst = g_slist_append(NULL, g_strdup(evid));
-    }
-    for (p = evidlst; p; p = g_slist_next(p)) {
-      if (evs_callback(p->data, action) == -1) {
-        scr_LogPrint(LPRINT_NORMAL, "Event %s not found.", p->data);
-      }
-      g_free(p->data);
-    }
-    g_slist_free(evidlst);
-  }
-
-  free_arg_lst(paramlst);
-}
-
-static void do_pgp(char *arg)
-{
-  char **paramlst;
-  char *fjid, *subcmd, *keyid;
-  enum {
-    pgp_none,
-    pgp_enable,
-    pgp_disable,
-    pgp_setkey,
-    pgp_force,
-    pgp_info
-  } op = 0;
-  int force = FALSE;
-
-  paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key]
-  subcmd = *paramlst;
-  fjid = *(paramlst+1);
-  keyid = *(paramlst+2);
-
-  if (!subcmd)
-    fjid = NULL;
-  if (!fjid)
-    keyid = NULL;
-
-  if (subcmd) {
-    if (!strcasecmp(subcmd, "enable"))
-      op = pgp_enable;
-    else if (!strcasecmp(subcmd, "disable"))
-      op = pgp_disable;
-    else if (!strcasecmp(subcmd, "setkey"))
-      op = pgp_setkey;
-    else if ((!strcasecmp(subcmd, "force")) ||
-             (!strcasecmp(subcmd, "+force"))) {
-      op = pgp_force;
-      force = TRUE;
-    } else if (!strcasecmp(subcmd, "-force"))
-      op = pgp_force;
-    else if (!strcasecmp(subcmd, "info"))
-      op = pgp_info;
-  }
-
-  if (!op) {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  // Allow special jid "" or "." (current buddy)
-  if (fjid && (!*fjid || !strcmp(fjid, ".")))
-    fjid = NULL;
-
-  if (fjid) {
-    // The JID has been specified.  Quick check...
-    if (check_jid_syntax(fjid) || !strchr(fjid, '@')) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", fjid);
-      fjid = NULL;
-    } else {
-      // Convert jid to lowercase and strip resource
-      char *p;
-      for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
-        *p = tolower(*p);
-      if (*p == JID_RESOURCE_SEPARATOR)
-        *p = '\0';
-    }
-  } else {
-    gpointer bud = NULL;
-    if (current_buddy)
-      bud = BUDDATA(current_buddy);
-    if (bud) {
-      guint type = buddy_gettype(bud);
-      if (type & ROSTER_TYPE_USER)  // Is it a user?
-        fjid = (char*)buddy_getjid(bud);
-      else
-        scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user.");
-    }
-  }
-
-  if (fjid) { // fjid is actually a bare jid...
-    guint disabled;
-    GString *sbuf;
-    switch (op) {
-      case pgp_enable:
-      case pgp_disable:
-          settings_pgp_setdisabled(fjid, (op == pgp_disable ? TRUE : FALSE));
-          break;
-      case pgp_force:
-          settings_pgp_setforce(fjid, force);
-          break;
-      case pgp_setkey:
-          settings_pgp_setkeyid(fjid, keyid);
-          break;
-      case pgp_info:
-          sbuf = g_string_new("");
-          if (settings_pgp_getkeyid(fjid)) {
-            g_string_printf(sbuf, "PGP Encryption key id: %s",
-                            settings_pgp_getkeyid(fjid));
-            scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
-          }
-          disabled = settings_pgp_getdisabled(fjid);
-          g_string_printf(sbuf, "PGP encryption is %s",
-                          (disabled ?  "disabled" : "enabled"));
-          scr_WriteIncomingMessage(fjid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
-          if (!disabled && settings_pgp_getforce(fjid)) {
-            scr_WriteIncomingMessage(fjid,
-                                     "Encryption enforced (no negotiation)",
-                                     0, HBB_PREFIX_INFO, 0);
-          }
-          g_string_free(sbuf, TRUE);
-          break;
-      default:
-          break;
-    }
-  } else {
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
-  }
-
-  free_arg_lst(paramlst);
-}
-
-static void do_otr(char *arg)
-{
-#ifdef HAVE_LIBOTR
-  char **paramlst;
-  char *fjid, *subcmd, *keyid;
-  enum {
-    otr_none,
-    otr_start,
-    otr_stop,
-    otr_fpr,
-    otr_smpq,
-    otr_smpr,
-    otr_smpa,
-    otr_k,
-    otr_info
-  } op = 0;
-
-  if (!otr_enabled()) {
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Warning: OTR hasn't been enabled -- command ignored.");
-    return;
-  }
-
-  paramlst = split_arg(arg, 3, 0); // subcmd, jid, [key]
-  subcmd = *paramlst;
-  fjid = *(paramlst+1);
-  keyid = *(paramlst+2);
-
-  if (!subcmd)
-    fjid = NULL;
-  if (!fjid)
-    keyid = NULL;
-
-  if (subcmd) {
-    if (!strcasecmp(subcmd, "start"))
-      op = otr_start;
-    else if (!strcasecmp(subcmd, "stop"))
-      op = otr_stop;
-    else if (!strcasecmp(subcmd, "fingerprint"))
-      op = otr_fpr;
-    else if (!strcasecmp(subcmd, "smpq"))
-      op = otr_smpq;
-    else if (!strcasecmp(subcmd, "smpr"))
-      op = otr_smpr;
-    else if (!strcasecmp(subcmd, "smpa"))
-      op = otr_smpa;
-    else if (!strcasecmp(subcmd, "key"))
-      op = otr_k;
-    else if (!strcasecmp(subcmd, "info"))
-      op = otr_info;
-  }
-
-  if (!op) {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized or missing parameter!");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (op == otr_k)
-    otr_key();
-  else {
-    // Allow special jid "" or "." (current buddy)
-    if (fjid && (!*fjid || !strcmp(fjid, ".")))
-      fjid = NULL;
-
-    if (fjid) {
-      // The JID has been specified.  Quick check...
-      if (check_jid_syntax(fjid) || !strchr(fjid, '@')) {
-        scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                     "<%s> is not a valid Jabber ID.", fjid);
-        fjid = NULL;
-      } else {
-        // Convert jid to lowercase and strip resource
-        char *p;
-        for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
-          *p = tolower(*p);
-        if (*p == JID_RESOURCE_SEPARATOR)
-          *p = '\0';
-      }
-    } else {
-      gpointer bud = NULL;
-      if (current_buddy)
-        bud = BUDDATA(current_buddy);
-      if (bud) {
-        guint type = buddy_gettype(bud);
-        if (type & ROSTER_TYPE_USER)  // Is it a user?
-          fjid = (char*)buddy_getjid(bud);
-        else
-          scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user.");
-      }
-    }
-
-    if (fjid) { // fjid is actually a bare jid...
-      switch (op) {
-        case otr_start:
-          otr_establish(fjid);          break;
-        case otr_stop:
-          otr_disconnect(fjid);         break;
-        case otr_fpr:
-          otr_fingerprint(fjid, keyid); break;
-        case otr_smpq:
-          otr_smp_query(fjid, keyid);   break;
-        case otr_smpr:
-          otr_smp_respond(fjid, keyid); break;
-        case otr_smpa:
-          otr_smp_abort(fjid);          break;
-        case otr_info:
-          otr_print_info(fjid);         break;
-        default:
-          break;
-      }
-    } else
-      scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
-  }
-  free_arg_lst(paramlst);
-
-#else
-  scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled.");
-#endif /* HAVE_LIBOTR */
-}
-
-#ifdef HAVE_LIBOTR
-static char *string_for_otrpolicy(enum otr_policy p)
-{
-  switch (p) {
-    case plain:         return "plain";
-    case opportunistic: return "opportunistic";
-    case manual:        return "manual";
-    case always:        return "always";
-    default:            return "unknown";
-  }
-}
-
-static void dump_otrpolicy(char *k, char *v, void *nothing)
-{
-  scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, "otrpolicy for %s: %s", k,
-               string_for_otrpolicy(*(enum otr_policy*)v));
-}
-#endif
-
-static void do_otrpolicy(char *arg)
-{
-#ifdef HAVE_LIBOTR
-  char **paramlst;
-  char *fjid, *policy;
-  enum otr_policy p;
-
-  paramlst = split_arg(arg, 2, 0); // [jid|default] policy
-  fjid = *paramlst;
-  policy = *(paramlst+1);
-
-  if (!fjid && !policy) {
-    scr_LogPrint(LPRINT_NORMAL, "default otrpolicy: %s",
-                 string_for_otrpolicy(settings_otr_getpolicy(NULL)));
-    settings_foreach(SETTINGS_TYPE_OTR, &dump_otrpolicy, NULL);
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!policy) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Please call otrpolicy correctly: /otrpolicy (default|jid) "
-                 "(plain|manual|opportunistic|always)");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(policy, "plain"))
-    p = plain;
-  else if (!strcasecmp(policy, "manual"))
-    p = manual;
-  else if (!strcasecmp(policy, "opportunistic"))
-    p = opportunistic;
-  else if (!strcasecmp(policy, "always"))
-    p = always;
-  else {
-    /* Fail, we don't know _this_ policy*/
-    scr_LogPrint(LPRINT_NORMAL, "mcabber doesn't support _this_ policy!");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  if (!strcasecmp(fjid, "default") || !strcasecmp(fjid, "*")) {
-    /*set default policy*/
-    settings_otr_setpolicy(NULL, p);
-    free_arg_lst(paramlst);
-    return;
-  }
-  // Allow special jid "" or "." (current buddy)
-  if (fjid && (!*fjid || !strcmp(fjid, ".")))
-    fjid = NULL;
-
-  if (fjid) {
-    // The JID has been specified.  Quick check...
-    if (check_jid_syntax(fjid) || !strchr(fjid, '@')) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", fjid);
-      fjid = NULL;
-    } else {
-      // Convert jid to lowercase and strip resource
-      char *p;
-      for (p = fjid; *p && *p != JID_RESOURCE_SEPARATOR; p++)
-        *p = tolower(*p);
-      if (*p == JID_RESOURCE_SEPARATOR)
-        *p = '\0';
-    }
-  } else {
-    gpointer bud = NULL;
-    if (current_buddy)
-      bud = BUDDATA(current_buddy);
-    if (bud) {
-      guint type = buddy_gettype(bud);
-      if (type & ROSTER_TYPE_USER)  // Is it a user?
-        fjid = (char*)buddy_getjid(bud);
-      else
-        scr_LogPrint(LPRINT_NORMAL, "The selected item should be a user.");
-    }
-  }
-
-  if (fjid)
-    settings_otr_setpolicy(fjid, p);
-  else
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
-
-  free_arg_lst(paramlst);
-#else
-  scr_LogPrint(LPRINT_NORMAL, "Please recompile mcabber with libotr enabled.");
-#endif /* HAVE_LIBOTR */
-}
-
-/* !!!
-  After changing the /iline arguments names here, you must change ones
-  in init_bindings().
-*/
-static void do_iline(char *arg)
-{
-  if (!strcasecmp(arg, "fword")) {
-    readline_forward_word();
-  } else if (!strcasecmp(arg, "bword")) {
-    readline_backward_word();
-  } else if (!strcasecmp(arg, "word_fdel")) {
-    readline_forward_kill_word();
-  } else if (!strcasecmp(arg, "word_bdel")) {
-    readline_backward_kill_word();
-  } else if (!strcasecmp(arg, "word_upcase")) {
-    readline_updowncase_word(1);
-  } else if (!strcasecmp(arg, "word_downcase")) {
-    readline_updowncase_word(0);
-  } else if (!strcasecmp(arg, "word_capit")) {
-    readline_capitalize_word();
-  } else if (!strcasecmp(arg, "fchar")) {
-    readline_forward_char();
-  } else if (!strcasecmp(arg, "bchar")) {
-    readline_backward_char();
-  } else if (!strcasecmp(arg, "char_fdel")) {
-    readline_forward_kill_char();
-  } else if (!strcasecmp(arg, "char_bdel")) {
-    readline_backward_kill_char();
-  } else if (!strcasecmp(arg, "char_swap")) {
-    readline_transpose_chars();
-  } else if (!strcasecmp(arg, "hist_beginning_search_bwd")) {
-    readline_hist_beginning_search_bwd();
-  } else if (!strcasecmp(arg, "hist_beginning_search_fwd")) {
-    readline_hist_beginning_search_fwd();
-  } else if (!strcasecmp(arg, "hist_prev")) {
-    readline_hist_prev();
-  } else if (!strcasecmp(arg, "hist_next")) {
-    readline_hist_next();
-  } else if (!strcasecmp(arg, "iline_start")) {
-    readline_iline_start();
-  } else if (!strcasecmp(arg, "iline_end")) {
-    readline_iline_end();
-  } else if (!strcasecmp(arg, "iline_fdel")) {
-    readline_forward_kill_iline();
-  } else if (!strcasecmp(arg, "iline_bdel")) {
-    readline_backward_kill_iline();
-  } else if (!strcasecmp(arg, "send_multiline")) {
-    readline_send_multiline();
-  } else if (!strcasecmp(arg, "iline_accept")) {
-    retval_for_cmds = readline_accept_line(FALSE);
-  } else if (!strcasecmp(arg, "iline_accept_down_hist")) {
-    retval_for_cmds = readline_accept_line(TRUE);
-  } else if (!strcasecmp(arg, "compl_cancel")) {
-    readline_cancel_completion();
-  } else if (!strcasecmp(arg, "compl_do")) {
-    readline_do_completion();
-  }
-}
-
-static void do_screen_refresh(char *arg)
-{
-  readline_refresh_screen();
-}
-
-static void do_chat_disable(char *arg)
-{
-  guint show_roster;
-
-  if (arg && !strcasecmp(arg, "--show-roster"))
-    show_roster = 1;
-  else
-    show_roster = 0;
-
-  readline_disable_chat_mode(show_roster);
-}
-
-static void do_source(char *arg)
-{
-  static int recur_level;
-  gchar *filename, *expfname;
-  if (!*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing filename.");
-    return;
-  }
-  if (recur_level > 20) {
-    scr_LogPrint(LPRINT_LOGNORM, "** Too many source commands!");
-    return;
-  }
-  filename = g_strdup(arg);
-  strip_arg_special_chars(filename);
-  expfname = expand_filename(filename);
-  recur_level++;
-  cfg_read_file(expfname, FALSE);
-  recur_level--;
-  g_free(filename);
-  g_free(expfname);
-}
-
-static void do_connect(char *arg)
-{
-  xmpp_connect();
-}
-
-static void do_disconnect(char *arg)
-{
-  xmpp_disconnect();
-}
-
-static void do_help(char *arg)
-{
-  help_process(arg);
-}
-
-static void do_echo(char *arg)
-{
-  if (arg)
-    scr_print_logwindow(arg);
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/commands.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#ifndef __COMMANDS_H__
-#define __COMMANDS_H__ 1
-
-#include <glib.h>
-
-#include "config.h"
-
-// Command structure
-typedef struct {
-  char name[32];
-  const char *help;
-  guint completion_flags[2];
-  void (*func)(char *);
-#ifdef MODULES_ENABLE
-  gpointer userdata;
-#endif
-} cmd;
-
-void cmd_init(void);
-cmd *cmd_get(const char *command);
-int  process_line(const char *line);
-int  process_command(const char *line, guint iscmd);
-char *expandalias(const char *line);
-#ifdef MODULES_ENABLE
-void cmd_deinit(void);
-gpointer cmd_del(const char *name);
-void cmd_add(const char *name, const char *help, guint flags1, guint flags2, void (*f)(char*), gpointer userdata);
-#endif
-
-void cmd_room_whois(gpointer bud, char *nick_locale, guint interactive);
-void cmd_room_leave(gpointer bud, char *arg);
-void cmd_setstatus(const char *recipient, const char *arg);
-
-#endif /* __COMMANDS_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/compl.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,286 +0,0 @@
-/*
- * compl.c      -- Completion system
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-/*  Usage, basically:
- * - new_completion();      // 1.   Initialization
- * - complete();            // 2.   1st completion
- * - cancel_completion();   // 3a.  2nd completion / cancel previous
- * - complete();            // 3b.  2nd completion / complete
- *   ...
- * - done_completion();     // n.   finished -- free allocated areas
- *
- */
-
-#include <string.h>
-
-#include "compl.h"
-#include "utf8.h"
-#include "roster.h"
-#include "events.h"
-
-// Completion structure
-typedef struct {
-  GSList *list;         // list of matches
-  guint len_prefix;     // length of text already typed by the user
-  guint len_compl;      // length of the last completion
-  GSList *next;         // pointer to next completion to try
-} compl;
-
-// Category structure
-typedef struct {
-  guint flag;
-  GSList *words;
-} category;
-
-static GSList *Categories;
-static compl *InputCompl;
-
-#ifdef MODULES_ENABLE
-guint registered_cats = COMPL_CMD|COMPL_JID|COMPL_URLJID|COMPL_NAME| \
-                        COMPL_STATUS|COMPL_FILENAME|COMPL_ROSTER|COMPL_BUFFER| \
-                        COMPL_GROUP|COMPL_GROUPNAME|COMPL_MULTILINE|COMPL_ROOM| \
-                        COMPL_RESOURCE|COMPL_AUTH|COMPL_REQUEST|COMPL_EVENTS| \
-                        COMPL_EVENTSID|COMPL_PGP|COMPL_COLOR| \
-                        COMPL_OTR|COMPL_OTRPOLICY| \
-                        0;
-
-//  compl_new_category()
-// Reserves id for new completion category.
-// Returns 0, if no more categories can be allocated.
-// Note, that user should not make any assumptions about id nature,
-// as it is likely to change in future.
-guint compl_new_category (void)
-{
-  guint i = 0;
-  while ((registered_cats >> i) & 1)
-    i++;
-  if (i >= sizeof (guint)*8)
-    return 0;
-  else {
-    guint id = 1 << i;
-    registered_cats |= id;
-    return id;
-  }
-}
-
-//  compl_del_category (id)
-// Frees reserved id for category.
-// Note, that for now it not validates its input, so, be careful
-// and specify exactly what you get from compl_new_category.
-void compl_del_category (guint id)
-{
-  registered_cats &= ~id;
-}
-#endif
-
-//  new_completion(prefix, compl_cat)
-// . prefix    = beginning of the word, typed by the user
-// . compl_cat = pointer to a completion category list (list of *char)
-// Set the InputCompl pointer to an allocated compl structure.
-// done_completion() must be called when finished.
-// Returns the number of possible completions.
-guint new_completion(char *prefix, GSList *compl_cat)
-{
-  compl *c;
-  GSList *sl_cat;
-  size_t len = strlen(prefix);
-
-  if (InputCompl) { // This should not happen, but hey...
-    cancel_completion();
-  }
-
-  c = g_new0(compl, 1);
-  // Build the list of matches
-  for (sl_cat = compl_cat; sl_cat; sl_cat = g_slist_next(sl_cat)) {
-    char *word = sl_cat->data;
-    if (!strncasecmp(prefix, word, len)) {
-      if (strlen(word) != len)
-        c->list = g_slist_append(c->list, g_strdup(word+len)); // TODO sort
-    }
-  }
-  c->next = c->list;
-  InputCompl = c;
-  return g_slist_length(c->list);
-}
-
-//  done_completion();
-void done_completion(void)
-{
-  GSList *clp;
-
-  if (!InputCompl)  return;
-
-  // Free the current completion list
-  for (clp = InputCompl->list; clp; clp = g_slist_next(clp))
-    g_free(clp->data);
-  g_slist_free(InputCompl->list);
-  g_free(InputCompl);
-  InputCompl = NULL;
-}
-
-//  cancel_completion()
-// Returns the number of chars to delete to cancel the completion
-//guint cancel_completion(compl *c)
-guint cancel_completion(void)
-{
-  if (!InputCompl)  return 0;
-  return InputCompl->len_compl;
-}
-
-// Returns pointer to text to insert, NULL if no completion.
-const char *complete()
-{
-  compl* c = InputCompl;
-  char *r;
-
-  if (!InputCompl)  return NULL;
-
-  if (!c->next) {
-    c->next = c->list;  // back to the beginning
-    c->len_compl = 0;
-    return NULL;
-  }
-  r = (char*)c->next->data;
-  c->next = g_slist_next(c->next);
-  if (!utf8_mode) {
-    c->len_compl = strlen(r);
-  } else {
-    char *wc;
-    c->len_compl = 0;
-    for (wc = r; *wc; wc = next_char(wc))
-      c->len_compl++;
-  }
-  return r;
-}
-
-
-/* Categories functions */
-
-//  compl_add_category_word(categ, command)
-// Adds a keyword as a possible completion in category categ.
-void compl_add_category_word(guint categ, const char *word)
-{
-  GSList *sl_cat;
-  category *cat;
-  char *nword;
-  // Look for category
-  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
-    if (categ == ((category*)sl_cat->data)->flag)
-      break;
-  }
-  if (!sl_cat) {   // Category not found, let's create it
-    cat = g_new0(category, 1);
-    cat->flag = categ;
-    Categories = g_slist_append(Categories, cat);
-  } else
-    cat = (category*)sl_cat->data;
-
-  // If word is not space-terminated, we add one trailing space
-  for (nword = (char*)word; *nword; nword++)
-    ;
-  if (nword > word) nword--;
-  if (*nword != ' ') {  // Add a space
-    nword = g_strdup_printf("%s ", word);
-  } else {              // word is fine
-    nword = g_strdup(word);
-  }
-
-  // TODO Check word does not already exist
-  cat->words = g_slist_append(cat->words, nword); // TODO sort
-}
-
-//  compl_del_category_word(categ, command)
-// Removes a keyword from category categ in completion list.
-void compl_del_category_word(guint categ, const char *word)
-{
-  GSList *sl_cat, *sl_elt;
-  category *cat;
-  char *nword;
-  // Look for category
-  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
-    if (categ == ((category*)sl_cat->data)->flag)
-      break;
-  }
-  if (!sl_cat) return;   // Category not found, finished!
-
-  cat = (category*)sl_cat->data;
-
-  // If word is not space-terminated, we add one trailing space
-  for (nword = (char*)word; *nword; nword++)
-    ;
-  if (nword > word) nword--;
-  if (*nword != ' ') {  // Add a space
-    nword = g_strdup_printf("%s ", word);
-  } else {              // word is fine
-    nword = g_strdup(word);
-  }
-
-  sl_elt = cat->words;
-  while (sl_elt) {
-    if (!strcasecmp((char*)sl_elt->data, nword)) {
-      g_free(sl_elt->data);
-      cat->words = g_slist_delete_link(cat->words, sl_elt);
-      break; // Only remove first occurence
-    }
-    sl_elt = g_slist_next(sl_elt);
-  }
-}
-
-//  compl_get_category_list()
-// Returns a slist of all words in the categories specified by the given flags
-// Iff this function sets *dynlist to TRUE, then the caller must free the
-// whole list after use.
-GSList *compl_get_category_list(guint cat_flags, guint *dynlist)
-{
-  GSList *sl_cat;
-
-  *dynlist = FALSE;
-
-  // Look for category
-  // XXX Actually that's not that simple... cat_flags can be a combination
-  // of several flags!
-  for (sl_cat=Categories; sl_cat; sl_cat = g_slist_next(sl_cat)) {
-    if (cat_flags == ((category*)sl_cat->data)->flag)
-      break;
-  }
-  if (sl_cat)       // Category was found, easy...
-    return ((category*)sl_cat->data)->words;
-
-  // Handle dynamic SLists
-  *dynlist = TRUE;
-  if (cat_flags == COMPL_GROUPNAME) {
-    return compl_list(ROSTER_TYPE_GROUP);
-  }
-  if (cat_flags == COMPL_JID) {
-    return compl_list(ROSTER_TYPE_USER);
-  }
-  if (cat_flags == COMPL_RESOURCE) {
-    return buddy_getresources_locale(NULL);
-  }
-  if (cat_flags == COMPL_EVENTSID) {
-    return evs_geteventslist(TRUE);
-  }
-
-  *dynlist = FALSE;
-  return NULL;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/compl.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-#ifndef __COMPL_H__
-#define __COMPL_H__ 1
-
-#include <glib.h>
-
-#include "config.h"
-
-#define COMPL_CMD         (1U<<0)
-#define COMPL_JID         (1U<<1)
-#define COMPL_URLJID      (1U<<2)   // Not implemented yet
-#define COMPL_NAME        (1U<<3)   // Not implemented yet
-#define COMPL_STATUS      (1U<<4)
-#define COMPL_FILENAME    (1U<<5)   // Not implemented yet
-#define COMPL_ROSTER      (1U<<6)
-#define COMPL_BUFFER      (1U<<7)
-#define COMPL_GROUP       (1U<<8)
-#define COMPL_GROUPNAME   (1U<<9)
-#define COMPL_MULTILINE   (1U<<10)
-#define COMPL_ROOM        (1U<<11)
-#define COMPL_RESOURCE    (1U<<12)
-#define COMPL_AUTH        (1U<<13)
-#define COMPL_REQUEST     (1U<<14)
-#define COMPL_EVENTS      (1U<<15)
-#define COMPL_EVENTSID    (1U<<16)
-#define COMPL_PGP         (1U<<17)
-#define COMPL_COLOR       (1U<<18)
-#define COMPL_OTR         (1U<<19)
-#define COMPL_OTRPOLICY   (1U<<20)
-#ifdef MODULES_ENABLE
-#define COMPL_MAX_BUILTIN (1U<<20)
-
-guint compl_new_category (void);
-void  compl_del_category (guint id);
-#endif
-
-void    compl_add_category_word(guint, const char *command);
-void    compl_del_category_word(guint categ, const char *word);
-GSList *compl_get_category_list(guint cat_flags, guint *dynlist);
-
-guint   new_completion(char *prefix, GSList *compl_cat);
-void    done_completion(void);
-guint   cancel_completion(void);
-const char *complete(void);
-
-#endif /* __COMPL_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/events.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,180 +0,0 @@
-/*
- * events.c     -- Events fonctions
- *
- * Copyright (C) 2006-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <glib.h>
-#include <string.h>
-#include "events.h"
-#include "logprint.h"
-
-static GSList *evs_list; // Events list
-
-static eviqs *evs_find(const char *evid);
-
-//  evs_new(type, timeout)
-// Create an events structure.
-eviqs *evs_new(guint8 type, time_t timeout)
-{
-  static guint evs_idn;
-  eviqs *new_evs;
-  time_t now_t;
-  char *stridn;
-
-  if (!++evs_idn)
-    evs_idn = 1;
-  /* Check for wrapping, we shouldn't reuse ids */
-  stridn = g_strdup_printf("%d", evs_idn);
-  if (evs_find(stridn))  {
-    g_free(stridn);
-    // We could try another id but for now giving up should be fine...
-    return NULL;
-  }
-
-  new_evs = g_new0(eviqs, 1);
-  time(&now_t);
-  new_evs->ts_create = now_t;
-  if (timeout)
-    new_evs->ts_expire = now_t + timeout;
-  new_evs->type = type;
-  new_evs->id = stridn;
-
-  if(!g_slist_length(evs_list))
-    g_timeout_add_seconds(20, evs_check_timeout, NULL);
-  evs_list = g_slist_append(evs_list, new_evs);
-  return new_evs;
-}
-
-int evs_del(const char *evid)
-{
-  GSList *p;
-  eviqs *i;
-
-  if (!evid) return 1;
-
-  for (p = evs_list; p; p = g_slist_next(p)) {
-    i = p->data;
-    if (!strcmp(evid, i->id))
-      break;
-  }
-  if (p) {
-    g_free(i->id);
-    g_free(i->data);
-    g_free(i->desc);
-    g_free(i);
-    evs_list = g_slist_remove(evs_list, p->data);
-    return 0; // Ok, deleted
-  }
-  return -1;  // Not found
-}
-
-static eviqs *evs_find(const char *evid)
-{
-  GSList *p;
-  eviqs *i;
-
-  if (!evid) return NULL;
-
-  for (p = evs_list; p; p = g_slist_next(p)) {
-    i = p->data;
-    if (!strcmp(evid, i->id))
-      return i;
-  }
-  return NULL;
-}
-
-//  evs_callback(evid, evcontext)
-// Callback processing for the specified event.
-// Return 0 in case of success, -1 if the evid hasn't been found.
-int evs_callback(const char *evid, guint evcontext)
-{
-  eviqs *i;
-
-  i = evs_find(evid);
-  if (!i) return -1;
-
-  // IQ processing
-  // Note: If xml_result is NULL, this is a timeout
-  if (i->callback)
-    (void)(*i->callback)(i, evcontext);
-
-  evs_del(evid);
-  return 0;
-}
-
-gboolean evs_check_timeout()
-{
-  time_t now_t;
-  GSList *p;
-  eviqs *i;
-
-  time(&now_t);
-  p = evs_list;
-  if (!p)
-    return FALSE;
-  while (p) {
-    i = p->data;
-    // We must get next IQ eviqs element now because the current one
-    // could be freed.
-    p = g_slist_next(p);
-
-    if ((!i->ts_expire && now_t > i->ts_create + EVS_MAX_TIMEOUT) ||
-        (i->ts_expire && now_t > i->ts_expire)) {
-      evs_callback(i->id, EVS_CONTEXT_TIMEOUT);
-    }
-  }
-  return TRUE;
-}
-
-void evs_display_list(void)
-{
-  GSList *p;
-  eviqs *i;
-
-  scr_LogPrint(LPRINT_LOGNORM, "Events list:");
-  for (p = evs_list; p; p = g_slist_next(p)) {
-    i = p->data;
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Id: %-3s %s", i->id, (i->desc ? i->desc : ""));
-  }
-  scr_LogPrint(LPRINT_LOGNORM, "End of events list.");
-}
-
-//  evs_geteventslist(bool comp)
-// Return a singly-linked-list of events ids, for the completion system.
-// If comp is true, the string "list" is added (it's a completion argument).
-// Note: the caller should free the list (and data) after use.
-GSList *evs_geteventslist(int compl)
-{
-  GSList *evidlist = NULL, *p;
-  eviqs *i;
-
-  for (p = evs_list; p; p = g_slist_next(p)) {
-    i = p->data;
-    evidlist = g_slist_append(evidlist, g_strdup(i->id));
-  }
-
-  if (compl) {
-    // Last item is the "list" subcommand.
-    evidlist = g_slist_append(evidlist, g_strdup("list"));
-  }
-  return evidlist;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/events.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-#ifndef __EVENTS_H__
-#define __EVENTS_H__ 1
-
-#include "config.h" // MODULES_ENABLE
-
-#define EVS_DEFAULT_TIMEOUT 90
-#define EVS_MAX_TIMEOUT     432000
-
-#define EVS_CONTEXT_TIMEOUT 0U
-#define EVS_CONTEXT_CANCEL  1U
-#define EVS_CONTEXT_USER    2U
-
-typedef enum {
-  EVS_TYPE_SUBSCRIPTION = 1,
-  EVS_TYPE_INVITATION = 2,
-#ifdef MODULES_ENABLE
-  EVS_TYPE_USER = 3,
-#endif
-} evs_type;
-
-/* Common structure for events (evs) and IQ requests (iqs) */
-typedef struct {
-  char *id;
-  time_t ts_create;
-  time_t ts_expire;
-  guint8 type;
-  gpointer data;
-  int (*callback)();
-  char *desc;
-} eviqs;
-
-typedef struct {
-  char* to;
-  char* from;
-  char* passwd;
-  char* reason;
-} event_muc_invitation;
-
-eviqs   *evs_new(guint8 type, time_t timeout);
-int      evs_del(const char *evid);
-int      evs_callback(const char *evid, guint evcontext);
-gboolean evs_check_timeout();
-void     evs_display_list(void);
-GSList  *evs_geteventslist(int forcompl);
-
-#endif /* __EVENTS_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/fifo.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,178 +0,0 @@
-/*
- * fifo.c       -- Read commands from a named pipe
- *
- * Copyright (C) 2008,2009 Mikael Berthe <mikael@lilotux.net>
- * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <stdlib.h>
-#include <glib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-
-#include "commands.h"
-#include "logprint.h"
-#include "utils.h"
-#include "settings.h"
-#include "main.h"
-
-static char *fifo_name = NULL;
-static GIOChannel *fifo_channel = NULL;
-
-static const char *FIFO_ENV_NAME = "MCABBER_FIFO";
-
-static gboolean attach_fifo(const char *name);
-
-static guint fifo_callback(GIOChannel *channel,
-                           GIOCondition condition,
-                           gpointer data)
-{
-  if (condition & (G_IO_IN|G_IO_PRI)) {
-    GIOStatus  chstat;
-    gchar     *buf;
-    gsize      endpos;
-
-    chstat = g_io_channel_read_line(channel, &buf, NULL, &endpos, NULL);
-    if (chstat == G_IO_STATUS_ERROR || chstat == G_IO_STATUS_EOF) {
-      if (!attach_fifo(fifo_name))
-        scr_LogPrint(LPRINT_LOGNORM,
-                     "Reopening fifo failed! Fifo will not work from now!");
-      return FALSE;
-    }
-    if (buf) {
-      guint logflag;
-      guint fifo_ignore = settings_opt_get_int("fifo_ignore");
-
-      if (endpos)
-        buf[endpos] = '\0';
-
-      if (settings_opt_get_int("fifo_hide_commands"))
-        logflag = LPRINT_LOG;
-      else
-        logflag = LPRINT_LOGNORM;
-      scr_LogPrint(logflag, "%s FIFO command: %s",
-                   (fifo_ignore ? "Ignoring" : "Executing"), buf);
-      if (!fifo_ignore) {
-        if (process_command(buf, TRUE) == 255)
-          mcabber_set_terminate_ui();
-      }
-
-      g_free(buf);
-    }
-  } else if (condition & (G_IO_ERR|G_IO_NVAL|G_IO_HUP)) {
-    if (!attach_fifo(fifo_name))
-      scr_LogPrint(LPRINT_LOGNORM,
-                   "Reopening fifo failed! Fifo will not work from now!");
-    return FALSE;
-  }
-  return TRUE;
-}
-
-static void fifo_destroy_callback(gpointer data)
-{
-  GIOChannel *channel = (GIOChannel *)data;
-  g_io_channel_unref(channel);
-}
-
-static gboolean check_fifo(const char *name)
-{
-  struct stat finfo;
-  if (stat(name, &finfo) == -1) {
-    /* some unknown error */
-    if (errno != ENOENT)
-      return FALSE;
-    /* fifo not yet exists */
-    if (mkfifo(name, S_IRUSR|S_IWUSR) != -1)
-      return check_fifo(name);
-    else
-      return FALSE;
-  }
-
-  /* file exists */
-  if (S_ISFIFO(finfo.st_mode))
-    return TRUE;
-  else
-    return FALSE;
-}
-
-static gboolean attach_fifo(const char *name)
-{
-  GSource *source;
-  int fd = open (name, O_RDONLY|O_NONBLOCK);
-  if (fd == -1)
-    return FALSE;
-
-  fifo_channel = g_io_channel_unix_new(fd);
-
-  g_io_channel_set_flags(fifo_channel, G_IO_FLAG_NONBLOCK, NULL);
-  g_io_channel_set_encoding(fifo_channel, NULL, NULL);
-  g_io_channel_set_close_on_unref(fifo_channel, TRUE);
-
-  source = g_io_create_watch(fifo_channel,
-                             G_IO_IN|G_IO_PRI|G_IO_ERR|G_IO_HUP|G_IO_NVAL);
-  g_source_set_callback(source, (GSourceFunc)fifo_callback,
-                        (gpointer)fifo_channel,
-                        (GDestroyNotify)fifo_destroy_callback);
-  g_source_attach(source, main_context);
-
-  return TRUE;
-}
-
-int fifo_init(const char *fifo_path)
-{
-  if (fifo_path) {
-    fifo_name = expand_filename(fifo_path);
-
-    if (!check_fifo(fifo_name)) {
-      scr_LogPrint(LPRINT_LOGNORM, "WARNING: Cannot create the FIFO. "
-                   "%s already exists and is not a pipe", fifo_name);
-      g_free(fifo_name);
-      return -1;
-    }
-  } else if (fifo_name)
-    g_source_remove_by_user_data(fifo_channel);
-  else
-    return -1;
-
-  if (!attach_fifo(fifo_name)) {
-    scr_LogPrint(LPRINT_LOGNORM, "Error: Cannot open fifo");
-    return -1;
-  }
-
-  setenv(FIFO_ENV_NAME, fifo_name, 1);
-
-  scr_LogPrint(LPRINT_LOGNORM, "FIFO initialized (%s)", fifo_path);
-  return 1;
-}
-
-void fifo_deinit(void)
-{
-  unsetenv(FIFO_ENV_NAME);
-
-  /* destroy open fifo */
-  unlink(fifo_name);
-  g_source_remove_by_user_data(fifo_channel);
-  /* channel itself should be destroyed by destruction callback */
-  g_free(fifo_name);
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/fifo.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-#ifndef __FIFO_H__
-#define __FIFO_H__ 1
-
-int  fifo_init(const char *fifo_path);
-void fifo_deinit(void);
-
-#endif /* __FIFO_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/hbuf.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,516 +0,0 @@
-/*
- * hbuf.c       -- History buffer implementation
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include "hbuf.h"
-#include "utils.h"
-#include "utf8.h"
-#include "screen.h"
-
-
-/* This is a private structure type */
-
-typedef struct {
-  char *ptr;
-  char *ptr_end;        // beginning of the block
-  char *ptr_end_alloc;  // end of the current persistent block
-  guchar flags;
-
-  // XXX This should certainly be a pointer, and be allocated only when needed
-  // (for ex. when HBB_FLAG_PERSISTENT is set).
-  struct { // hbuf_line_info
-    time_t timestamp;
-    unsigned mucnicklen;
-    guint  flags;
-    gpointer xep184;
-  } prefix;
-} hbuf_block;
-
-
-//  do_wrap(p_hbuf, first_hbuf_elt, width)
-// Wrap hbuf lines with the specified width.
-// '\n' are handled by this routine (they are removed and persistent lines
-// are created).
-// All hbuf elements are processed, starting from first_hbuf_elt.
-static inline void do_wrap(GList **p_hbuf, GList *first_hbuf_elt,
-                           unsigned int width)
-{
-  GList *curr_elt = first_hbuf_elt;
-
-  // Let's add non-persistent blocs if necessary
-  // - If there are '\n' in the string
-  // - If length > width (and width != 0)
-  while (curr_elt) {
-    hbuf_block *hbuf_b_curr, *hbuf_b_prev;
-    char *c, *end;
-    char *br = NULL; // break pointer
-    char *cr = NULL; // CR pointer
-    unsigned int cur_w = 0;
-
-    // We want to break where we can find a space char or a CR
-
-    hbuf_b_curr = (hbuf_block*)(curr_elt->data);
-    hbuf_b_prev = hbuf_b_curr;
-    c = hbuf_b_curr->ptr;
-
-    while (*c && (!width || cur_w <= width)) {
-      if (*c == '\n') {
-        br = cr = c;
-        *c = 0;
-        break;
-      }
-      if (iswblank(get_char(c)))
-        br = c;
-      cur_w += get_char_width(c);
-      c = next_char(c);
-    }
-
-    if (cr || (*c && cur_w > width)) {
-      if (!br || br == hbuf_b_curr->ptr)
-        br = c;
-      else
-        br = next_char(br);
-      end = hbuf_b_curr->ptr_end;
-      hbuf_b_curr->ptr_end = br;
-      // Create another block
-      hbuf_b_curr = g_new0(hbuf_block, 1);
-      // The block must be persistent after a CR
-      if (cr) {
-        hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end + 1; // == cr+1
-        hbuf_b_curr->flags  = HBB_FLAG_PERSISTENT;
-      } else {
-        hbuf_b_curr->ptr    = hbuf_b_prev->ptr_end; // == br
-        hbuf_b_curr->flags    = 0;
-      }
-      hbuf_b_curr->ptr_end  = end;
-      hbuf_b_curr->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
-      // This is OK because insert_before(NULL) == append():
-      *p_hbuf = g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
-    }
-    curr_elt = g_list_next(curr_elt);
-  }
-}
-
-//  hbuf_add_line(p_hbuf, text, prefix_flags, width, maxhbufblocks)
-// Add a line to the given buffer.  If width is not null, then lines are
-// wrapped at this length.
-// maxhbufblocks is the maximum number of hbuf blocks we can allocate.  If
-// null, there is no limit.  If non-null, it should be >= 2.
-//
-// Note 1: Splitting according to width won't work if there are tabs; they
-//         should be expanded before.
-// Note 2: width does not include the ending \0.
-void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp,
-        guint prefix_flags, guint width, guint maxhbufblocks,
-        unsigned mucnicklen, gpointer xep184)
-{
-  GList *curr_elt;
-  char *line;
-  guint hbb_blocksize, textlen;
-  hbuf_block *hbuf_block_elt;
-
-  if (!text) return;
-
-  prefix_flags |= (xep184 ? HBB_PREFIX_RECEIPT : 0);
-
-  textlen = strlen(text);
-  hbb_blocksize = MAX(textlen+1, HBB_BLOCKSIZE);
-
-  hbuf_block_elt = g_new0(hbuf_block, 1);
-  hbuf_block_elt->prefix.timestamp  = timestamp;
-  hbuf_block_elt->prefix.flags      = prefix_flags;
-  hbuf_block_elt->prefix.mucnicklen = mucnicklen;
-  hbuf_block_elt->prefix.xep184     = xep184;
-  if (!*p_hbuf) {
-    hbuf_block_elt->ptr  = g_new(char, hbb_blocksize);
-    if (!hbuf_block_elt->ptr) {
-      g_free(hbuf_block_elt);
-      return;
-    }
-    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
-    hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
-  } else {
-    hbuf_block *hbuf_b_prev;
-    // Set p_hbuf to the end of the list, to speed up history loading
-    // (or CPU time will be used by g_list_last() for each line)
-    *p_hbuf = g_list_last(*p_hbuf);
-    hbuf_b_prev = (*p_hbuf)->data;
-    hbuf_block_elt->ptr    = hbuf_b_prev->ptr_end;
-    hbuf_block_elt->flags  = HBB_FLAG_PERSISTENT;
-    hbuf_block_elt->ptr_end_alloc = hbuf_b_prev->ptr_end_alloc;
-  }
-  *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
-
-  if (hbuf_block_elt->ptr + textlen >= hbuf_block_elt->ptr_end_alloc) {
-    // Too long for the current allocated bloc, we need another one
-    if (!maxhbufblocks || textlen >= HBB_BLOCKSIZE) {
-      // No limit, let's allocate a new block
-      // If the message text is big, we won't bother to reuse an old block
-      // as well (it could be too small and cause a segfault).
-      hbuf_block_elt->ptr  = g_new0(char, hbb_blocksize);
-      hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
-      // XXX We should check the return value.
-    } else {
-      GList *hbuf_head, *hbuf_elt;
-      hbuf_block *hbuf_b_elt;
-      guint n = 0;
-      hbuf_head = g_list_first(*p_hbuf);
-      // We need at least 2 allocated blocks
-      if (maxhbufblocks == 1)
-        maxhbufblocks = 2;
-      // Let's count the number of allocated areas
-      for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
-        hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
-        if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
-          n++;
-      }
-      // If we can't allocate a new area, reuse the previous block(s)
-      if (n < maxhbufblocks) {
-        hbuf_block_elt->ptr  = g_new0(char, hbb_blocksize);
-        hbuf_block_elt->ptr_end_alloc = hbuf_block_elt->ptr + hbb_blocksize;
-      } else {
-        // Let's use an old block, and free the extra blocks if needed
-        char *allocated_block = NULL;
-        char *end_of_allocated_block = NULL;
-        while (n >= maxhbufblocks) {
-          int start_of_block = 1;
-          for (hbuf_elt = hbuf_head; hbuf_elt; hbuf_elt = hbuf_head) {
-            hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
-            if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
-              if (start_of_block-- == 0)
-                break;
-              if (n == maxhbufblocks) {
-                allocated_block = hbuf_b_elt->ptr;
-                end_of_allocated_block = hbuf_b_elt->ptr_end_alloc;
-              } else {
-                g_free(hbuf_b_elt->ptr);
-              }
-            }
-            g_free(hbuf_b_elt);
-            hbuf_head = *p_hbuf = g_list_delete_link(hbuf_head, hbuf_elt);
-          }
-          n--;
-        }
-        memset(allocated_block, 0, end_of_allocated_block-allocated_block);
-        hbuf_block_elt->ptr = allocated_block;
-        hbuf_block_elt->ptr_end_alloc = end_of_allocated_block;
-      }
-    }
-    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
-  }
-
-  line = hbuf_block_elt->ptr;
-  // Ok, now we can copy the text..
-  strcpy(line, text);
-  hbuf_block_elt->ptr_end = line + textlen + 1;
-
-  curr_elt = g_list_last(*p_hbuf);
-
-  // Wrap lines and handle CRs ('\n')
-  do_wrap(p_hbuf, curr_elt, width);
-}
-
-//  hbuf_free()
-// Destroys all hbuf list.
-void hbuf_free(GList **p_hbuf)
-{
-  hbuf_block *hbuf_b_elt;
-  GList *hbuf_elt;
-  GList *first_elt = g_list_first(*p_hbuf);
-
-  for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
-    hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
-    if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
-      g_free(hbuf_b_elt->ptr);
-    }
-    g_free(hbuf_b_elt);
-  }
-
-  g_list_free(first_elt);
-  *p_hbuf = NULL;
-}
-
-//  hbuf_rebuild()
-// Rebuild all hbuf list, with the new width.
-// If width == 0, lines are not wrapped.
-void hbuf_rebuild(GList **p_hbuf, unsigned int width)
-{
-  GList *first_elt, *curr_elt, *next_elt;
-  hbuf_block *hbuf_b_curr, *hbuf_b_next;
-
-  // *p_hbuf needs to be the head of the list
-  first_elt = *p_hbuf = g_list_first(*p_hbuf);
-
-  // #1 Remove non-persistent blocks (ptr_end should be updated!)
-  curr_elt = first_elt;
-  while (curr_elt) {
-    next_elt = g_list_next(curr_elt);
-    // Last element?
-    if (!next_elt)
-      break;
-    hbuf_b_curr = (hbuf_block*)(curr_elt->data);
-    hbuf_b_next = (hbuf_block*)(next_elt->data);
-    // Is next line not-persistent?
-    if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
-      hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
-      g_free(hbuf_b_next);
-      curr_elt = g_list_delete_link(curr_elt, next_elt);
-    } else
-      curr_elt = next_elt;
-  }
-  // #2 Go back to head and create non-persistent blocks when needed
-  if (width)
-    do_wrap(p_hbuf, first_elt, width);
-}
-
-//  hbuf_previous_persistent()
-// Returns the previous persistent block (line).  If the given line is
-// persistent, then it is returned.
-// This function is used for example when resizing a buffer.  If the top of the
-// screen is on a non-persistent block, then a screen resize could destroy this
-// line...
-GList *hbuf_previous_persistent(GList *l_line)
-{
-  hbuf_block *hbuf_b_elt;
-
-  while (l_line) {
-    hbuf_b_elt = (hbuf_block*)l_line->data;
-    if (hbuf_b_elt->flags & HBB_FLAG_PERSISTENT)
-      return l_line;
-    l_line = g_list_previous(l_line);
-  }
-
-  return NULL;
-}
-
-//  hbuf_get_lines(hbuf, n)
-// Returns an array of n hbb_line pointers
-// (The first line will be the line currently pointed by hbuf)
-// Note: The caller should free the array, the hbb_line pointers and the
-// text pointers after use.
-hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n)
-{
-  unsigned int i;
-  hbuf_block *blk;
-  guint last_persist_prefixflags = 0;
-  GList *last_persist;  // last persistent flags
-  hbb_line **array, **array_elt;
-
-  // To be able to correctly highlight multi-line messages,
-  // we need to look at the last non-null prefix, which should be the first
-  // line of the message.
-  last_persist = hbuf_previous_persistent(hbuf);
-  while (last_persist) {
-    blk = (hbuf_block*)last_persist->data;
-    if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
-      last_persist_prefixflags = blk->prefix.flags;
-      break;
-    }
-    last_persist = g_list_previous(last_persist);
-  }
-
-  array = g_new0(hbb_line*, n);
-  array_elt = array;
-
-  for (i = 0 ; i < n ; i++) {
-    if (hbuf) {
-      int maxlen;
-
-      blk = (hbuf_block*)(hbuf->data);
-      maxlen = blk->ptr_end - blk->ptr;
-      *array_elt = (hbb_line*)g_new(hbb_line, 1);
-      (*array_elt)->timestamp  = blk->prefix.timestamp;
-      (*array_elt)->flags      = blk->prefix.flags;
-      (*array_elt)->mucnicklen = blk->prefix.mucnicklen;
-      (*array_elt)->text       = g_strndup(blk->ptr, maxlen);
-
-      if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
-        last_persist_prefixflags = blk->prefix.flags;
-      } else {
-        // Propagate highlighting flags
-        (*array_elt)->flags |= last_persist_prefixflags &
-                               (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
-                                HBB_PREFIX_INFO | HBB_PREFIX_IN);
-        // Continuation of a message - omit the prefix
-        (*array_elt)->flags |= HBB_PREFIX_CONT;
-        (*array_elt)->mucnicklen = 0; // The nick is in the first one
-      }
-
-      hbuf = g_list_next(hbuf);
-    } else
-      break;
-
-    array_elt++;
-  }
-
-  return array;
-}
-
-//  hbuf_search(hbuf, direction, string)
-// Look backward/forward for a line containing string in the history buffer
-// Search starts at hbuf, and goes forward if direction == 1, backward if -1
-GList *hbuf_search(GList *hbuf, int direction, const char *string)
-{
-  hbuf_block *blk;
-
-  for (;;) {
-    if (direction > 0)
-      hbuf = g_list_next(hbuf);
-    else
-      hbuf = g_list_previous(hbuf);
-
-    if (!hbuf) break;
-
-    blk = (hbuf_block*)(hbuf->data);
-    // XXX blk->ptr is (maybe) not really correct, because the match should
-    // not be after ptr_end.  We should check that...
-    if (strcasestr(blk->ptr, string))
-      break;
-  }
-
-  return hbuf;
-}
-
-//  hbuf_jump_date(hbuf, t)
-// Return a pointer to the first line after date t in the history buffer
-GList *hbuf_jump_date(GList *hbuf, time_t t)
-{
-  hbuf_block *blk;
-
-  hbuf = g_list_first(hbuf);
-
-  for ( ; hbuf && g_list_next(hbuf); hbuf = g_list_next(hbuf)) {
-    blk = (hbuf_block*)(hbuf->data);
-    if (blk->prefix.timestamp >= t) break;
-  }
-
-  return hbuf;
-}
-
-//  hbuf_jump_percent(hbuf, pc)
-// Return a pointer to the line at % pc of the history buffer
-GList *hbuf_jump_percent(GList *hbuf, int pc)
-{
-  guint hlen;
-
-  hbuf = g_list_first(hbuf);
-  hlen = g_list_length(hbuf);
-
-  return g_list_nth(hbuf, pc*hlen/100);
-}
-
-//  hbuf_dump_to_file(hbuf, filename)
-// Save the buffer to a file.
-void hbuf_dump_to_file(GList *hbuf, const char *filename)
-{
-  hbuf_block *blk;
-  hbb_line line;
-  guint last_persist_prefixflags = 0;
-  guint prefixwidth;
-  char pref[96];
-  FILE *fp;
-  struct stat statbuf;
-
-  if (!stat(filename, &statbuf)) {
-    scr_LogPrint(LPRINT_NORMAL, "The file already exists.");
-    return;
-  }
-  fp = fopen(filename, "w");
-  if (!fp) {
-    scr_LogPrint(LPRINT_NORMAL, "Unable to open the file.");
-    return;
-  }
-
-  prefixwidth = scr_getprefixwidth();
-  prefixwidth = MIN(prefixwidth, sizeof pref);
-
-  for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
-    int maxlen;
-
-    blk = (hbuf_block*)(hbuf->data);
-    maxlen = blk->ptr_end - blk->ptr;
-
-    memset(&line, 0, sizeof(line));
-    line.timestamp  = blk->prefix.timestamp;
-    line.flags      = blk->prefix.flags;
-    line.mucnicklen = blk->prefix.mucnicklen;
-    line.text       = g_strndup(blk->ptr, maxlen);
-
-    if ((blk->flags & HBB_FLAG_PERSISTENT) && blk->prefix.flags) {
-      last_persist_prefixflags = blk->prefix.flags;
-    } else {
-      // Propagate highlighting flags
-      line.flags |= last_persist_prefixflags &
-                    (HBB_PREFIX_HLIGHT_OUT | HBB_PREFIX_HLIGHT |
-                     HBB_PREFIX_INFO | HBB_PREFIX_IN);
-      // Continuation of a message - omit the prefix
-      line.flags |= HBB_PREFIX_CONT;
-      line.mucnicklen = 0; // The nick is in the first one
-    }
-
-    scr_line_prefix(&line, pref, prefixwidth);
-    fprintf(fp, "%s%s\n", pref, line.text);
-  }
-
-  fclose(fp);
-  return;
-}
-
-//  hbuf_remove_receipt(hbuf, xep184)
-// Remove the Receipt Flag for the message with the given xep184 id
-// Returns TRUE if it was found and removed, otherwise FALSE
-gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184)
-{
-  hbuf_block *blk;
-
-  hbuf = g_list_first(hbuf);
-
-  for ( ; hbuf; hbuf = g_list_next(hbuf)) {
-    blk = (hbuf_block*)(hbuf->data);
-    if (blk->prefix.xep184 == xep184) {
-      blk->prefix.xep184 = NULL;
-      blk->prefix.flags ^= HBB_PREFIX_RECEIPT;
-      return TRUE;
-    }
-  }
-  return FALSE;
-}
-
-//  hbuf_get_blocks_number()
-// Returns the number of allocated hbuf_block's.
-guint hbuf_get_blocks_number(GList *hbuf)
-{
-  hbuf_block *hbuf_b_elt;
-  guint count = 0U;
-
-  for (hbuf = g_list_first(hbuf); hbuf; hbuf = g_list_next(hbuf)) {
-    hbuf_b_elt = (hbuf_block*)(hbuf->data);
-    if (hbuf_b_elt->flags & HBB_FLAG_ALLOC)
-      count++;
-  }
-  return count;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/hbuf.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-#ifndef __HBUF_H__
-#define __HBUF_H__ 1
-
-#include <time.h>
-#include <glib.h>
-
-// With current implementation a message must fit in a hbuf block,
-// so we shouldn't choose a too small size.
-#define HBB_BLOCKSIZE   8192    // > 20 please
-
-// Flags:
-// - ALLOC: the ptr data has been allocated, it can be freed
-// - PERSISTENT: this is a new history line
-#define HBB_FLAG_ALLOC      1
-#define HBB_FLAG_PERSISTENT 2
-
-#define HBB_PREFIX_IN         (1U<<0)
-#define HBB_PREFIX_OUT        (1U<<1)
-#define HBB_PREFIX_STATUS     (1U<<2)
-#define HBB_PREFIX_AUTH       (1U<<3)
-#define HBB_PREFIX_INFO       (1U<<4)
-#define HBB_PREFIX_ERR        (1U<<5)
-#define HBB_PREFIX_NOFLAG     (1U<<6)
-#define HBB_PREFIX_HLIGHT_OUT (1U<<7)
-#define HBB_PREFIX_HLIGHT     (1U<<8)
-#define HBB_PREFIX_NONE       (1U<<9)
-#define HBB_PREFIX_SPECIAL    (1U<<10)
-#define HBB_PREFIX_PGPCRYPT   (1U<<11)
-#define HBB_PREFIX_OTRCRYPT   (1U<<12)
-#define HBB_PREFIX_CONT       (1U<<13)
-#define HBB_PREFIX_RECEIPT    (1U<<14)
-
-typedef struct {
-  time_t timestamp;
-  guint flags;
-  unsigned mucnicklen;
-  char *text;
-} hbb_line;
-
-void hbuf_add_line(GList **p_hbuf, const char *text, time_t timestamp,
-        guint prefix_flags, guint width, guint maxhbufblocks,
-        unsigned mucnicklen, gpointer xep184);
-void hbuf_free(GList **p_hbuf);
-void hbuf_rebuild(GList **p_hbuf, unsigned int width);
-GList *hbuf_previous_persistent(GList *l_line);
-
-hbb_line **hbuf_get_lines(GList *hbuf, unsigned int n);
-GList *hbuf_search(GList *hbuf, int direction, const char *string);
-GList *hbuf_jump_date(GList *hbuf, time_t t);
-GList *hbuf_jump_percent(GList *hbuf, int pc);
-gboolean hbuf_remove_receipt(GList *hbuf, gpointer xep184);
-
-void hbuf_dump_to_file(GList *hbuf, const char *filename);
-
-guint hbuf_get_blocks_number(GList *p_hbuf);
-
-#endif /* __HBUF_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/help.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-/*
- * help.c       -- Help command
- *
- * Copyright (C) 2006-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <glib.h>
-
-#include "settings.h"
-#include "logprint.h"
-#include "utils.h"
-#include "screen.h"
-
-#define DEFAULT_LANG "en"
-
-//  get_lang()
-// Return the language code string (a 2-letters string).
-static const char *get_lang(void) {
-  static const char *lang_str = DEFAULT_LANG;
-#ifdef DATA_DIR
-  static char lang[3];
-  const char *opt_l;
-  opt_l = settings_opt_get("lang");
-  if (opt_l && strlen(opt_l) == 2 && isalpha(opt_l[0]) && isalpha(opt_l[1])) {
-    strncpy(lang, opt_l, sizeof(lang));
-    mc_strtolower(lang);
-    lang_str = lang;
-  }
-#endif /* DATA_DIR */
-  return lang_str;
-}
-
-//  help_process(string)
-// Display help about the "string" command.
-// If string is null, display general help.
-// Return 0 in case of success.
-int help_process(char *string)
-{
-#ifndef DATA_DIR
-  scr_LogPrint(LPRINT_NORMAL, "Help isn't available.");
-  return -1;
-#else
-  const char *lang;
-  FILE *fp;
-  char *helpfiles_dir, *filename;
-  char *data;
-  const int datasize = 4096;
-  int linecount = 0;
-  char *p;
-
-  // Check string is ok
-  for (p = string; p && *p; p++) {
-    if (!isalnum(*p) && *p != '_' && *p != '-') {
-      scr_LogPrint(LPRINT_NORMAL, "Cannot find help (invalid keyword).");
-      return 1;
-    }
-  }
-
-  // Look for help file
-  lang = get_lang();
-  helpfiles_dir = g_strdup_printf("%s/mcabber/help", DATA_DIR);
-  p = NULL;
-
-  if (string && *string) {
-    p = g_strdup(string);
-    mc_strtolower(p);
-    filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, lang, p);
-  } else
-    filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, lang);
-
-  fp = fopen(filename, "r");
-
-  if (!(fp) && (g_strcmp0(lang, DEFAULT_LANG)) ) {
-    g_free(filename);
-    if (p)
-      filename = g_strdup_printf("%s/%s/hlp_%s.txt", helpfiles_dir, DEFAULT_LANG, p);
-    else
-      filename = g_strdup_printf("%s/%s/hlp.txt", helpfiles_dir, DEFAULT_LANG);
-
-    fp = fopen(filename, "r");
-  }
-  g_free(p);
-  g_free(filename);
-  g_free(helpfiles_dir);
-
-  if (!fp) {
-    scr_LogPrint(LPRINT_NORMAL, "No help found.");
-    return -1;
-  }
-
-  data = g_new(char, datasize);
-  while (!feof(fp)) {
-    if (fgets(data, datasize, fp) == NULL) break;
-    // Strip trailing newline
-    for (p = data; *p; p++) ;
-    if (p > data)
-      p--;
-    if (*p == '\n' || *p == '\r')
-      *p = '\0';
-    // Displaty the help line
-    scr_LogPrint(LPRINT_NORMAL, "%s", data);
-    linecount++;
-  }
-  fclose(fp);
-  g_free(data);
-
-  if (linecount) {
-    scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
-    update_roster = TRUE;
-  }
-
-  return 0;
-#endif /* DATA_DIR */
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/help.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-#ifndef __HELP_H__
-#define __HELP_H__ 1
-
-int help_process(char *string);
-
-#endif /* __HELP_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/histolog.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,536 +0,0 @@
-/*
- * histolog.c   -- File history handling
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "histolog.h"
-#include "hbuf.h"
-#include "utils.h"
-#include "screen.h"
-#include "settings.h"
-#include "utils.h"
-#include "roster.h"
-#include "xmpp.h"
-
-static guint UseFileLogging;
-static guint FileLoadLogs;
-static char *RootDir;
-
-
-//  user_histo_file(jid)
-// Returns history filename for the given jid
-// Note: the caller *must* free the filename after use (if not null).
-static char *user_histo_file(const char *bjid)
-{
-  char *filename;
-  char *lowerid;
-
-  if (!(UseFileLogging || FileLoadLogs))
-    return NULL;
-
-  lowerid = g_strdup(bjid);
-  if (!lowerid)
-    return NULL;
-  mc_strtolower(lowerid);
-
-  filename = g_strdup_printf("%s%s", RootDir, lowerid);
-  g_free(lowerid);
-  return filename;
-}
-
-char *hlog_get_log_jid(const char *bjid)
-{
-  struct stat bufstat;
-  char *path;
-  char *log_jid = NULL;
-
-  path = user_histo_file(bjid);
-  while (path) {
-    if (lstat(path, &bufstat) != 0)
-      break;
-    if (S_ISLNK(bufstat.st_mode)) {
-      g_free(log_jid);
-      log_jid = g_new0(char, bufstat.st_size+1);
-      if (readlink(path, log_jid, bufstat.st_size) < 0) return NULL;
-      g_free(path);
-      path = user_histo_file(log_jid);
-    } else
-      break;
-  }
-
-  g_free(path);
-  return log_jid;
-}
-
-//  write_histo_line()
-// Adds a history (multi-)line to the jid's history logfile
-static void write_histo_line(const char *bjid,
-        time_t timestamp, guchar type, guchar info, const char *data)
-{
-  guint len = 0;
-  FILE *fp;
-  time_t ts;
-  const char *p;
-  char *filename;
-  char str_ts[20];
-  int err;
-
-  if (!UseFileLogging)
-    return;
-
-  // Do not log status messages when 'logging_ignore_status' is set
-  if (type == 'S' && settings_opt_get_int("logging_ignore_status"))
-    return;
-
-  filename = user_histo_file(bjid);
-
-  // If timestamp is null, get current date
-  if (timestamp)
-    ts = timestamp;
-  else
-    time(&ts);
-
-  if (!data)
-    data = "";
-
-  // Count number of extra lines
-  for (p=data ; *p ; p++)
-    if (*p == '\n') len++;
-
-  /* Line format: "TI yyyymmddThh:mm:ssZ LLL [data]"
-   * T=Type, I=Info, yyyymmddThh:mm:ssZ=date, LLL=0-padded-len
-   *
-   * Types:
-   * - M message    Info: S (send) R (receive) I (info)
-   * - S status     Info: [_ofdnai]
-   * We don't check them, we trust the caller.
-   * (Info messages are not sent nor received, they're generated
-   * locally by mcabber.)
-   */
-
-  fp = fopen(filename, "a");
-  g_free(filename);
-  if (!fp) {
-    scr_LogPrint(LPRINT_LOGNORM, "Unable to write history "
-                 "(cannot open logfile)");
-    return;
-  }
-
-  to_iso8601(str_ts, ts);
-  err = fprintf(fp, "%c%c %-18.18s %03d %s\n", type, info, str_ts, len, data);
-  fclose(fp);
-  if (err < 0) {
-    scr_LogPrint(LPRINT_LOGNORM, "Error while writing to log file: %s",
-                 strerror(errno));
-  }
-}
-
-//  hlog_read_history()
-// Reads the jid's history logfile
-void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width)
-{
-  char *filename;
-  guchar type, info;
-  char *data, *tail;
-  guint data_size;
-  char *xtext;
-  time_t timestamp;
-  guint prefix_flags;
-  guint len;
-  FILE *fp;
-  struct stat bufstat;
-  guint err = 0;
-  guint ln = 0; // line number
-  time_t starttime;
-  int max_num_of_blocks;
-
-  if (!FileLoadLogs)
-    return;
-
-  if ((roster_gettype(bjid) & ROSTER_TYPE_ROOM) &&
-      (settings_opt_get_int("load_muc_logs") != 1))
-    return;
-
-  data_size = HBB_BLOCKSIZE+32;
-  data = g_new(char, data_size);
-  if (!data) {
-    scr_LogPrint(LPRINT_LOGNORM, "Not enough memory to read history file");
-    return;
-  }
-
-  filename = user_histo_file(bjid);
-
-  fp = fopen(filename, "r");
-  g_free(filename);
-  if (!fp) {
-    g_free(data);
-    return;
-  }
-
-  // If file is large (> 3MB here), display a message to inform the user
-  // (it can take a while...)
-  if (!fstat(fileno(fp), &bufstat)) {
-    if (bufstat.st_size > 3145728) {
-      scr_LogPrint(LPRINT_NORMAL, "Reading <%s> history file...", bjid);
-      scr_DoUpdate();
-    }
-  }
-
-  max_num_of_blocks = get_max_history_blocks();
-
-  starttime = 0L;
-  if (settings_opt_get_int("max_history_age") > 0) {
-    int maxdays = settings_opt_get_int("max_history_age");
-    time(&starttime);
-    if (maxdays >= starttime/86400L)
-      starttime = 0L;
-    else
-      starttime -= maxdays * 86400L;
-  }
-
-  /* See write_histo_line() for line format... */
-  while (!feof(fp)) {
-    guint dataoffset = 25;
-    guint noeol;
-
-    if (fgets(data, data_size-1, fp) == NULL)
-      break;
-    ln++;
-
-    while (1) {
-      for (tail = data; *tail; tail++) ;
-      noeol = (*(tail-1) != '\n');
-      if (!noeol)
-        break;
-      /* TODO: duplicated code... could do better... */
-      if (tail == data + data_size-2) {
-        // The buffer is too small to contain the whole line.
-        // Let's allocate some more space.
-        if (!max_num_of_blocks ||
-            data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) {
-          guint toffset = tail - data;
-          // Allocate one more block.
-          data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE);
-          data = g_renew(char, data, data_size);
-          // Update the tail pointer, as the data may have been moved.
-          tail = data + toffset;
-          if (fgets(tail, data_size-1 - (tail-data), fp) == NULL)
-            break;
-        } else {
-          scr_LogPrint(LPRINT_LOGNORM, "Line too long in history file!");
-          ln--;
-          break;
-        }
-      }
-    }
-
-    type = data[0];
-    info = data[1];
-
-    if ((type != 'M' && type != 'S') ||
-        ((data[11] != 'T') || (data[20] != 'Z') ||
-         (data[21] != ' ') ||
-         (data[25] != ' ' && data[26] != ' '))) {
-      if (!err) {
-        scr_LogPrint(LPRINT_LOGNORM,
-                     "Error in history file format (%s), l.%u", bjid, ln);
-        err = 1;
-      }
-      continue;
-    }
-    // The number of lines can be written with 3 or 4 bytes.
-    if (data[25] != ' ') dataoffset = 26;
-    data[21] = data[dataoffset] = 0;
-    timestamp = from_iso8601(&data[3], 1);
-    len = (guint) atoi(&data[22]);
-
-    // Some checks
-    if (((type == 'M') && (info != 'S' && info != 'R' && info != 'I')) ||
-        ((type == 'S') && (!strchr("_OFDNAI", info)))) {
-      if (!err) {
-        scr_LogPrint(LPRINT_LOGNORM, "Error in history file format (%s), l.%u",
-                     bjid, ln);
-        err = 1;
-      }
-      continue;
-    }
-
-    while (len--) {
-      ln++;
-      if (fgets(tail, data_size-1 - (tail-data), fp) == NULL)
-        break;
-
-      while (*tail) tail++;
-      noeol = (*(tail-1) != '\n');
-      if (tail == data + data_size-2 && (len || noeol)) {
-        // The buffer is too small to contain the whole message.
-        // Let's allocate some more space.
-        if (!max_num_of_blocks ||
-            data_size/HBB_BLOCKSIZE < 5U*max_num_of_blocks) {
-          guint toffset = tail - data;
-          // If the line hasn't been read completely and we reallocate the
-          // buffer, we want to read one more time.
-          if (noeol)
-            len++;
-          // Allocate one more block.
-          data_size = HBB_BLOCKSIZE * (1 + data_size/HBB_BLOCKSIZE);
-          data = g_renew(char, data, data_size);
-          // Update the tail pointer, as the data may have been moved.
-          tail = data + toffset;
-        } else {
-          // There will probably be a parse error on next read, because
-          // this message hasn't been read entirely.
-          scr_LogPrint(LPRINT_LOGNORM, "Message too big in history file!");
-        }
-      }
-    }
-    // Remove last CR (we keep it if the line is empty, too)
-    if ((tail > data+dataoffset+1) && (*(tail-1) == '\n'))
-      *(tail-1) = 0;
-
-    // Check if the data is older than max_history_age
-    if (starttime) {
-      if (timestamp > starttime)
-        starttime = 0L; // From now on, load everything
-      else
-        continue;
-    }
-
-    if (type == 'M') {
-      char *converted;
-      if (info == 'S') {
-        prefix_flags = HBB_PREFIX_OUT | HBB_PREFIX_HLIGHT_OUT;
-      } else {
-        prefix_flags = HBB_PREFIX_IN;
-        if (info == 'I')
-          prefix_flags = HBB_PREFIX_INFO;
-      }
-      converted = from_utf8(&data[dataoffset+1]);
-      if (converted) {
-        xtext = ut_expand_tabs(converted); // Expand tabs
-        hbuf_add_line(p_buddyhbuf, xtext, timestamp, prefix_flags, width,
-                      max_num_of_blocks, 0, NULL);
-        if (xtext != converted)
-          g_free(xtext);
-        g_free(converted);
-      }
-      err = 0;
-    }
-  }
-  fclose(fp);
-  g_free(data);
-}
-
-//  hlog_enable()
-// Enable logging to files.  If root_dir is NULL, then $HOME/.mcabber is used.
-// If loadfiles is TRUE, we will try to load buddies history logs from file.
-void hlog_enable(guint enable, const char *root_dir, guint loadfiles)
-{
-  UseFileLogging = enable;
-  FileLoadLogs = loadfiles;
-
-  if (enable || loadfiles) {
-    if (root_dir) {
-      char *xp_root_dir;
-      int l = strlen(root_dir);
-      if (l < 1) {
-        scr_LogPrint(LPRINT_LOGNORM, "Error: logging dir name too short");
-        UseFileLogging = FileLoadLogs = FALSE;
-        return;
-      }
-      xp_root_dir = expand_filename(root_dir);
-      // RootDir must be slash-terminated
-      if (root_dir[l-1] == '/') {
-        RootDir = xp_root_dir;
-      } else {
-        RootDir = g_strdup_printf("%s/", xp_root_dir);
-        g_free(xp_root_dir);
-      }
-    } else {
-      char *home = getenv("HOME");
-      const char *dir = "/.mcabber/histo/";
-      RootDir = g_strdup_printf("%s%s", home, dir);
-    }
-    // Check directory permissions (should not be readable by group/others)
-    if (checkset_perm(RootDir, TRUE) == -1) {
-      // The directory does not actually exists
-      g_free(RootDir);
-      RootDir = NULL;
-      scr_LogPrint(LPRINT_LOGNORM, "ERROR: Cannot access "
-                   "history log directory, logging DISABLED");
-      UseFileLogging = FileLoadLogs = FALSE;
-    }
-  } else {  // Disable history logging
-    g_free(RootDir);
-    RootDir = NULL;
-  }
-}
-
-guint hlog_is_enabled(void)
-{
-  return UseFileLogging;
-}
-
-inline void hlog_write_message(const char *bjid, time_t timestamp, int sent,
-        const char *msg)
-{
-  guchar info;
-  /* sent=1   message sent by mcabber
-   * sent=0   message received by mcabber
-   * sent=-1  local info message
-   */
-  if (sent == 1)
-    info = 'S';
-  else if (sent == 0)
-    info = 'R';
-  else
-    info = 'I';
-  write_histo_line(bjid, timestamp, 'M', info, msg);
-}
-
-inline void hlog_write_status(const char *bjid, time_t timestamp,
-        enum imstatus status, const char *status_msg)
-{
-  // XXX Check status value?
-  write_histo_line(bjid, timestamp, 'S', toupper(imstatus2char[status]),
-          status_msg);
-}
-
-
-//  hlog_save_state()
-// If enabled, save the current state of the roster
-// (i.e. pending messages) to a temporary file.
-void hlog_save_state(void)
-{
-  gpointer unread_ptr, first_unread;
-  const char *bjid;
-  char *statefile_xp;
-  FILE *fp;
-  const char *statefile = settings_opt_get("statefile");
-
-  if (!statefile || !UseFileLogging)
-    return;
-
-  statefile_xp = expand_filename(statefile);
-  fp = fopen(statefile_xp, "w");
-  if (!fp) {
-    scr_LogPrint(LPRINT_NORMAL, "Cannot open state file [%s]",
-                 strerror(errno));
-    goto hlog_save_state_return;
-  }
-
-  if (!lm_connection_is_authenticated(lconnection)) {
-    // We're not connected.  Let's use the unread_jids hash.
-    GList *unread_jid = unread_jid_get_list();
-    unread_ptr = unread_jid;
-    for ( ; unread_jid ; unread_jid = g_list_next(unread_jid))
-      fprintf(fp, "%s\n", (char*)unread_jid->data);
-    g_list_free(unread_ptr);
-    goto hlog_save_state_return;
-  }
-
-  if (!current_buddy) // Safety check -- shouldn't happen.
-    goto hlog_save_state_return;
-
-  // We're connected.  Let's use unread_msg().
-  unread_ptr = first_unread = unread_msg(NULL);
-  if (!first_unread)
-    goto hlog_save_state_return;
-
-  do {
-    guint type = buddy_gettype(unread_ptr);
-    if (type & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) {
-      bjid = buddy_getjid(unread_ptr);
-      if (bjid)
-        fprintf(fp, "%s\n", bjid);
-    }
-    unread_ptr = unread_msg(unread_ptr);
-  } while (unread_ptr && unread_ptr != first_unread);
-
-hlog_save_state_return:
-  if (fp) {
-    long filelen = ftell(fp);
-    fclose(fp);
-    if (!filelen)
-      unlink(statefile_xp);
-  }
-  g_free(statefile_xp);
-}
-
-//  hlog_load_state()
-// If enabled, load the current state of the roster
-// (i.e. pending messages) from a temporary file.
-// This function adds the JIDs to the unread_jids hash table,
-// so it should only be called at startup.
-void hlog_load_state(void)
-{
-  char bjid[1024];
-  char *statefile_xp;
-  FILE *fp;
-  const char *statefile = settings_opt_get("statefile");
-
-  if (!statefile || !UseFileLogging)
-    return;
-
-  statefile_xp = expand_filename(statefile);
-  fp = fopen(statefile_xp, "r");
-  if (fp) {
-    char *eol;
-    while (!feof(fp)) {
-      if (fgets(bjid, sizeof bjid, fp) == NULL)
-        break;
-      // Let's remove the trailing newline.
-      // Also remove whitespace, if the file as been (badly) manually modified.
-      for (eol = bjid; *eol; eol++) ;
-      for (eol--; eol >= bjid && (*eol == '\n' || *eol == ' '); *eol-- = 0) ;
-      // Safety checks...
-      if (!bjid[0])
-        continue;
-      if (check_jid_syntax(bjid)) {
-        scr_LogPrint(LPRINT_LOGNORM,
-                     "ERROR: Invalid JID in state file.  Corrupted file?");
-        break;
-      }
-      // Display a warning if there are pending messages but the user
-      // won't see them because load_log isn't set.
-      if (!FileLoadLogs) {
-        scr_LogPrint(LPRINT_LOGNORM, "WARNING: unread message from <%s>.",
-                     bjid);
-        scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
-      }
-      // Add the JID to unread_jids.  It will be used when the contact is
-      // added to the roster.
-      unread_jid_add(bjid);
-    }
-    fclose(fp);
-  }
-  g_free(statefile_xp);
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/histolog.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-#ifndef __HISTOLOG_H__
-#define __HISTOLOG_H__ 1
-
-#include <glib.h>
-
-#include "xmpp.h"
-
-void hlog_enable(guint enable, const char *root_dir, guint loadfile);
-char *hlog_get_log_jid(const char *bjid);
-void hlog_read_history(const char *bjid, GList **p_buddyhbuf, guint width);
-void hlog_write_message(const char *bjid, time_t timestamp, int sent,
-                        const char *msg);
-void hlog_write_status(const char *bjid, time_t timestamp,
-                       enum imstatus status, const char *status_msg);
-void hlog_save_state(void);
-void hlog_load_state(void);
-
-#endif /* __HISTOLOG_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/hooks.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,668 +0,0 @@
-/*
- * hooks.c      -- Hooks layer
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <loudmouth/loudmouth.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include "hooks.h"
-#include "screen.h"
-#include "roster.h"
-#include "histolog.h"
-#include "hbuf.h"
-#include "settings.h"
-#include "utils.h"
-#include "utf8.h"
-#include "commands.h"
-#include "main.h"
-
-#ifdef MODULES_ENABLE
-#include <glib.h>
-
-typedef struct {
-  hk_handler_t handler;
-  guint32      flags;
-  gpointer     userdata;
-} hook_list_data_t;
-
-static GSList *hk_handler_queue = NULL;
-
-void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata)
-{
-  hook_list_data_t *h = g_new (hook_list_data_t, 1);
-  h->handler  = handler;
-  h->flags    = flags;
-  h->userdata = userdata;
-  hk_handler_queue = g_slist_append (hk_handler_queue, h);
-}
-
-static gint hk_queue_search_cb (hook_list_data_t *a, hook_list_data_t *b)
-{
-  if (a->handler == b->handler && a->userdata == b->userdata)
-    return 0;
-  else
-    return 1;
-}
-
-void hk_del_handler (hk_handler_t handler, gpointer userdata)
-{
-  hook_list_data_t h = { handler, 0, userdata };
-  GSList *el = g_slist_find_custom (hk_handler_queue, &h, (GCompareFunc) hk_queue_search_cb);
-  if (el) {
-    g_free (el->data);
-    hk_handler_queue = g_slist_delete_link (hk_handler_queue, el);
-  }
-}
-#endif
-
-static char *extcmd;
-
-static const char *COMMAND_ME = "/me ";
-
-void hk_message_in(const char *bjid, const char *resname,
-                   time_t timestamp, const char *msg, LmMessageSubType type,
-                   guint encrypted)
-{
-  int new_guy = FALSE;
-  int is_groupchat = FALSE; // groupchat message
-  int is_room = FALSE;      // window is a room window
-  int log_muc_conf = FALSE;
-  int active_window = FALSE;
-  int message_flags = 0;
-  guint rtype = ROSTER_TYPE_USER;
-  char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
-  GSList *roster_usr;
-  unsigned mucnicklen = 0;
-  const char *ename = NULL;
-
-  if (encrypted == ENCRYPTED_PGP)
-    message_flags |= HBB_PREFIX_PGPCRYPT;
-  else if (encrypted == ENCRYPTED_OTR)
-    message_flags |= HBB_PREFIX_OTRCRYPT;
-
-  if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
-    rtype = ROSTER_TYPE_ROOM;
-    is_groupchat = TRUE;
-    log_muc_conf = settings_opt_get_int("log_muc_conf");
-    if (!resname) {
-      message_flags = HBB_PREFIX_INFO | HBB_PREFIX_NOFLAG;
-      resname = "";
-      wmsg = bmsg = g_strdup_printf("~ %s", msg);
-    } else {
-      wmsg = bmsg = g_strdup_printf("<%s> %s", resname, msg);
-      mucnicklen = strlen(resname) + 2;
-      if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME)))
-        wmsg = mmsg = g_strdup_printf("*%s %s", resname, msg+4);
-    }
-  } else {
-    bmsg = g_strdup(msg);
-    if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
-      gchar *shortid = g_strdup(bjid);
-      if (settings_opt_get_int("buddy_me_fulljid") == FALSE) {
-        gchar *p = strchr(shortid, '@'); // Truncate the jid
-        if (p)
-          *p = '\0';
-      }
-      wmsg = mmsg = g_strdup_printf("*%s %s", shortid, msg+4);
-      g_free(shortid);
-    } else
-      wmsg = (char*) msg;
-  }
-
-  // If this user isn't in the roster, we add it
-  roster_usr = roster_find(bjid, jidsearch, 0);
-  if (!roster_usr) {
-    new_guy = TRUE;
-    roster_usr = roster_add_user(bjid, NULL, NULL, rtype, sub_none, -1);
-    if (!roster_usr) { // Shouldn't happen...
-      scr_LogPrint(LPRINT_LOGNORM, "ERROR: unable to add buddy!");
-      g_free(bmsg);
-      g_free(mmsg);
-      return;
-    }
-  } else if (is_groupchat) {
-    // Make sure the type is ROOM
-    buddy_settype(roster_usr->data, ROSTER_TYPE_ROOM);
-  }
-
-  is_room = !!(buddy_gettype(roster_usr->data) & ROSTER_TYPE_ROOM);
-
-  if (is_room) {
-    if (!is_groupchat) {
-      // This is a private message from a room participant
-      g_free(bmsg);
-      if (!resname) {
-        resname = "";
-        wmsg = bmsg = g_strdup(msg);
-      } else {
-        wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", resname, msg);
-        if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
-          g_free(mmsg);
-          wmsg = mmsg = g_strdup_printf("PRIV#*%s %s", resname, msg+4);
-        }
-      }
-      message_flags |= HBB_PREFIX_HLIGHT;
-    } else {
-      // This is a regular chatroom message.
-      const char *nick = buddy_getnickname(roster_usr->data);
-
-      if (nick) {
-        // Let's see if we are the message sender, in which case we'll
-        // highlight it.
-        if (resname && !strcmp(resname, nick)) {
-          message_flags |= HBB_PREFIX_HLIGHT_OUT;
-        } else if (!settings_opt_get_int("muc_disable_nick_hl")) {
-          // We're not the sender.  Can we see our nick?
-          const char *msgptr = msg;
-          while ((msgptr = strcasestr(msgptr, nick)) != NULL) {
-            const char *leftb, *rightb;
-            // The message contains our nick.  Let's check it's not
-            // in the middle of another word (i.e. preceded/followed
-            // immediately by an alphanumeric character or an underscore.
-            rightb = msgptr+strlen(nick);
-            if (msgptr == msg)
-              leftb = NULL;
-            else
-              leftb = prev_char((char*)msgptr, msg);
-            msgptr = next_char((char*)msgptr);
-            // Check left boundary
-            if (leftb && (iswalnum(get_char(leftb)) || get_char(leftb) == '_'))
-              continue;
-            // Check right boundary
-            if (!iswalnum(get_char(rightb)) && get_char(rightb) != '_')
-              message_flags |= HBB_PREFIX_HLIGHT;
-          }
-        }
-      }
-    }
-  }
-
-  if (type  == LM_MESSAGE_SUB_TYPE_ERROR) {
-    message_flags = HBB_PREFIX_ERR | HBB_PREFIX_IN;
-    scr_LogPrint(LPRINT_LOGNORM, "Error message received from <%s>", bjid);
-  }
-
-  // Note: the hlog_write should not be called first, because in some
-  // cases scr_WriteIncomingMessage() will load the history and we'd
-  // have the message twice...
-  scr_WriteIncomingMessage(bjid, wmsg, timestamp, message_flags, mucnicklen);
-
-  // We don't log the modified message, but the original one
-  if (wmsg == mmsg)
-    wmsg = bmsg;
-
-  // - We don't log the message if it is an error message
-  // - We don't log the message if it is a private conf. message
-  // - We don't log the message if it is groupchat message and the log_muc_conf
-  //   option is off (and it is not a history line)
-  if (!(message_flags & HBB_PREFIX_ERR) &&
-      (!is_room || (is_groupchat && log_muc_conf && !timestamp)))
-    hlog_write_message(bjid, timestamp, 0, wmsg);
-
-  if (settings_opt_get_int("events_ignore_active_window") &&
-      current_buddy && scr_get_chatmode()) {
-    gpointer bud = BUDDATA(current_buddy);
-    if (bud) {
-      const char *cjid = buddy_getjid(bud);
-      if (cjid && !strcasecmp(cjid, bjid))
-        active_window = TRUE;
-    }
-  }
-
-  if (settings_opt_get_int("eventcmd_use_nickname"))
-    ename = roster_getname(bjid);
-
-#ifdef MODULES_ENABLE
-  {
-    GSList *h = hk_handler_queue;
-    if (h) {
-#if 0
-      hk_arg_t *args = g_new (hk_arg_t, 5);
-      args[0].name = "hook";
-      args[0].value = "hook-message-in";
-      args[1].name = "jid";
-      args[1].value = bjid;
-      args[2].name = "message";
-      args[2].value = wmsg;
-      args[3].name = "groupchat";
-      args[3].value = is_groupchat ? "true" : "false";
-      args[4].name = NULL;
-      args[4].value = NULL;
-#else
-      // We can use a const array for keys/static values, so modules
-      // can do fast known to them args check by just comparing pointers...
-      hk_arg_t args[] = {
-        { "hook", "hook-message-in" },
-        { "jid", bjid },
-        { "message", wmsg },
-        { "groupchat", is_groupchat ? "true" : "false" },
-        { NULL, NULL },
-      };
-#endif
-      while (h) {
-        hook_list_data_t *data = h->data;
-        if (data->flags & HOOK_MESSAGE_IN)
-          (data->handler) (HOOK_MESSAGE_IN, args, data->userdata);
-        h = g_slist_next (h);
-      }
-    }
-  }
-#endif
-
-  // External command
-  // - We do not call hk_ext_cmd() for history lines in MUC
-  // - We do call hk_ext_cmd() for private messages in a room
-  // - We do call hk_ext_cmd() for messages to the current window
-  if (!active_window && ((is_groupchat && !timestamp) || !is_groupchat))
-    hk_ext_cmd(ename ? ename : bjid, (is_groupchat ? 'G' : 'M'), 'R', wmsg);
-
-  // Display the sender in the log window
-  if ((!is_groupchat) && !(message_flags & HBB_PREFIX_ERR) &&
-      settings_opt_get_int("log_display_sender")) {
-    const char *name = roster_getname(bjid);
-    if (!name) name = "";
-    scr_LogPrint(LPRINT_NORMAL, "Message received from %s <%s/%s>",
-                 name, bjid, (resname ? resname : ""));
-  }
-
-  // Beep, if enabled:
-  // - if it's a private message
-  // - if it's a public message and it's highlighted
-  if (settings_opt_get_int("beep_on_message")) {
-    if ((!is_groupchat && !(message_flags & HBB_PREFIX_ERR)) ||
-        (is_groupchat  && (message_flags & HBB_PREFIX_HLIGHT)))
-      scr_Beep();
-  }
-
-  // We need to update the roster if the sender is unknown or
-  // if the sender is offline/invisible and a filter is set.
-  if (new_guy ||
-      (buddy_getstatus(roster_usr->data, NULL) == offline &&
-       buddylist_isset_filter()))
-  {
-    update_roster = TRUE;
-  }
-
-  g_free(bmsg);
-  g_free(mmsg);
-}
-
-//  hk_message_out()
-// nick should be set for private messages in a chat room, and null for
-// normal messages.
-void hk_message_out(const char *bjid, const char *nick,
-                    time_t timestamp, const char *msg,
-                    guint encrypted, gpointer xep184)
-{
-  char *wmsg = NULL, *bmsg = NULL, *mmsg = NULL;
-  guint cryptflag = 0;
-
-  if (nick) {
-    wmsg = bmsg = g_strdup_printf("PRIV#<%s> %s", nick, msg);
-    if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
-      const char *mynick = roster_getnickname(bjid);
-      wmsg = mmsg = g_strdup_printf("PRIV#<%s> *%s %s", nick,
-                                    (mynick ? mynick : "me"), msg+4);
-    }
-  } else {
-    wmsg = (char*)msg;
-    if (!strncmp(msg, COMMAND_ME, strlen(COMMAND_ME))) {
-      char *myid = jid_get_username(settings_opt_get("jid"));
-      if (myid) {
-        wmsg = mmsg = g_strdup_printf("*%s %s", myid, msg+4);
-        g_free(myid);
-      }
-    }
-  }
-
-  // Note: the hlog_write should not be called first, because in some
-  // cases scr_WriteOutgoingMessage() will load the history and we'd
-  // have the message twice...
-  if (encrypted == ENCRYPTED_PGP)
-    cryptflag = HBB_PREFIX_PGPCRYPT;
-  else if (encrypted == ENCRYPTED_OTR)
-    cryptflag = HBB_PREFIX_OTRCRYPT;
-  scr_WriteOutgoingMessage(bjid, wmsg, cryptflag, xep184);
-
-  // We don't log private messages
-  if (!nick)
-    hlog_write_message(bjid, timestamp, 1, msg);
-
-#ifdef MODULES_ENABLE
-  {
-    GSList *h = hk_handler_queue;
-    if (h) {
-      hk_arg_t args[] = {
-        { "hook", "hook-message-out" },
-        { "jid", bjid },
-        { "message", wmsg },
-        { NULL, NULL },
-      };
-      while (h) {
-        hook_list_data_t *data = h->data;
-        if (data->flags & HOOK_MESSAGE_OUT)
-          (data->handler) (HOOK_MESSAGE_OUT, args, data->userdata);
-        h = g_slist_next (h);
-      }
-    }
-  }
-#endif
-
-  // External command
-  hk_ext_cmd(bjid, 'M', 'S', NULL);
-
-  g_free(bmsg);
-  g_free(mmsg);
-}
-
-void hk_statuschange(const char *bjid, const char *resname, gchar prio,
-                     time_t timestamp, enum imstatus status,
-                     const char *status_msg)
-{
-  int st_in_buf;
-  enum imstatus oldstat;
-  char *bn;
-  char *logsmsg;
-  const char *rn = (resname ? resname : "");
-  const char *ename = NULL;
-
-  if (settings_opt_get_int("eventcmd_use_nickname"))
-    ename = roster_getname(bjid);
-
-  oldstat = roster_getstatus(bjid, resname);
-
-  st_in_buf = settings_opt_get_int("show_status_in_buffer");
-
-  if (settings_opt_get_int("log_display_presence")) {
-    int buddy_format = settings_opt_get_int("buddy_format");
-    bn = NULL;
-    if (buddy_format) {
-      const char *name = roster_getname(bjid);
-      if (name && strcmp(name, bjid)) {
-        if (buddy_format == 1)
-          bn = g_strdup_printf("%s <%s/%s>", name, bjid, rn);
-        else if (buddy_format == 2)
-          bn = g_strdup_printf("%s/%s", name, rn);
-        else if (buddy_format == 3)
-          bn = g_strdup_printf("%s", name);
-      }
-    }
-
-    if (!bn)
-      bn = g_strdup_printf("<%s/%s>", bjid, rn);
-
-    logsmsg = g_strdup(status_msg ? status_msg : "");
-    replace_nl_with_dots(logsmsg);
-
-    scr_LogPrint(LPRINT_LOGNORM, "Buddy status has changed: [%c>%c] %s %s",
-                 imstatus2char[oldstat], imstatus2char[status], bn, logsmsg);
-    g_free(logsmsg);
-    g_free(bn);
-  }
-
-  if (st_in_buf == 2 ||
-      (st_in_buf == 1 && (status == offline || oldstat == offline))) {
-    // Write the status change in the buddy's buffer, only if it already exists
-    if (scr_BuddyBufferExists(bjid)) {
-      bn = g_strdup_printf("Buddy status has changed: [%c>%c] %s",
-                           imstatus2char[oldstat], imstatus2char[status],
-                           ((status_msg) ? status_msg : ""));
-      scr_WriteIncomingMessage(bjid, bn, timestamp,
-                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
-      g_free(bn);
-    }
-  }
-
-  roster_setstatus(bjid, rn, prio, status, status_msg, timestamp,
-                   role_none, affil_none, NULL);
-  buddylist_build();
-  scr_DrawRoster();
-  hlog_write_status(bjid, timestamp, status, status_msg);
-
-#ifdef MODULES_ENABLE
-  {
-    GSList *h = hk_handler_queue;
-    if (h) {
-      char os[2] = " \0";
-      char ns[2] = " \0";
-      hk_arg_t args[] = {
-        { "hook", "hook-status-change" },
-        { "jid", bjid },
-        { "resource", rn },
-        { "old_status", os },
-        { "new_status", ns },
-        { "message", status_msg ? status_msg : "" },
-        { NULL, NULL },
-      };
-      os[0] = imstatus2char[oldstat];
-      ns[0] = imstatus2char[status];
-      while (h) {
-        hook_list_data_t *data = h->data;
-        if (data->flags & HOOK_STATUS_CHANGE)
-          (data->handler) (HOOK_STATUS_CHANGE, args, data->userdata);
-        h = g_slist_next (h);
-      }
-    }
-  }
-#endif
-
-  // External command
-  hk_ext_cmd(ename ? ename : bjid, 'S', imstatus2char[status], NULL);
-}
-
-void hk_mystatuschange(time_t timestamp, enum imstatus old_status,
-                              enum imstatus new_status, const char *msg)
-{
-  scr_LogPrint(LPRINT_LOGNORM, "Your status has been set: [%c>%c] %s",
-               imstatus2char[old_status], imstatus2char[new_status],
-               (msg ? msg : ""));
-
-#ifdef MODULES_ENABLE
-  {
-    GSList *h = hk_handler_queue;
-    if (h) {
-      char ns[2] = " \0";
-      hk_arg_t args[] = {
-        { "hook", "hook-my-status-change" },
-        { "new_status", ns },
-        { "message", msg ? msg : "" },
-        { NULL, NULL },
-      };
-      ns[0] = imstatus2char[new_status];
-      while (h) {
-        hook_list_data_t *data = h->data;
-        if (data->flags & HOOK_MY_STATUS_CHANGE)
-          (data->handler) (HOOK_MY_STATUS_CHANGE, args, data->userdata);
-        h = g_slist_next (h);
-      }
-    }
-  }
-#endif
-
-  //hlog_write_status(NULL, 0, status);
-}
-
-
-/* Internal commands */
-
-void hook_execute_internal(const char *hookname)
-{
-  const char *hook_command;
-  char *buf;
-  char *cmdline;
-
-#ifdef MODULES_ENABLE
-  {
-    GSList *h = hk_handler_queue;
-    if (h) {
-      hk_arg_t args[] = {
-        { "hook", hookname },
-        { NULL, NULL },
-      };
-      while (h) {
-        hook_list_data_t *data = h->data;
-        if (data->flags & HOOK_INTERNAL)
-          (data->handler) (HOOK_INTERNAL, args, data->userdata);
-        h = g_slist_next (h);
-      }
-    }
-  }
-#endif
-
-  hook_command = settings_opt_get(hookname);
-  if (!hook_command)
-    return;
-
-  buf = g_strdup_printf("Running %s...", hookname);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-
-  cmdline = from_utf8(hook_command);
-  if (process_command(cmdline, TRUE) == 255)
-    mcabber_set_terminate_ui();
-
-  g_free(cmdline);
-  g_free(buf);
-}
-
-
-/* External commands */
-
-//  hk_ext_cmd_init()
-// Initialize external command variable.
-// Can be called with parameter NULL to reset and free memory.
-void hk_ext_cmd_init(const char *command)
-{
-  if (extcmd) {
-    g_free(extcmd);
-    extcmd = NULL;
-  }
-  if (command)
-    extcmd = expand_filename(command);
-}
-
-//  hk_ext_cmd()
-// Launch an external command (process) for the given event.
-// For now, data should be NULL.
-void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data)
-{
-  pid_t pid;
-  char *arg_type = NULL;
-  char *arg_info = NULL;
-  char *arg_data = NULL;
-  char status_str[2];
-  char *datafname = NULL;
-  char unread_str[16];
-
-  if (!extcmd) return;
-
-  // Prepare arg_* (external command parameters)
-  switch (type) {
-    case 'M': /* Normal message */
-        arg_type = "MSG";
-        if (info == 'R')
-          arg_info = "IN";
-        else if (info == 'S')
-          arg_info = "OUT";
-        break;
-    case 'G': /* Groupchat message */
-        arg_type = "MSG";
-        arg_info = "MUC";
-        break;
-    case 'S': /* Status change */
-        arg_type = "STATUS";
-        if (strchr(imstatus2char, tolower(info))) {
-          status_str[0] = toupper(info);
-          status_str[1] = 0;
-          arg_info = status_str;
-        }
-        break;
-    case 'U': /* Unread buffer count */
-        arg_type = "UNREAD";
-        g_snprintf(unread_str, sizeof unread_str, "%d", info);
-        arg_info = unread_str;  /* number of remaining unread bjids */
-        break;
-    default:
-        return;
-  }
-
-  if (!arg_type || !arg_info) return;
-
-  if (strchr("MG", type) && data && settings_opt_get_int("event_log_files")) {
-    int fd;
-    const char *prefix;
-    char *prefix_xp = NULL;
-    char *data_locale;
-
-    data_locale = from_utf8(data);
-    prefix = settings_opt_get("event_log_dir");
-    if (prefix)
-      prefix = prefix_xp = expand_filename(prefix);
-    else
-      prefix = ut_get_tmpdir();
-    datafname = g_strdup_printf("%s/mcabber-%d.XXXXXX", prefix, getpid());
-    g_free(prefix_xp);
-
-    // XXX Some old systems may require us to set umask first.
-    fd = mkstemp(datafname);
-    if (fd == -1) {
-      g_free(datafname);
-      datafname = NULL;
-      scr_LogPrint(LPRINT_LOGNORM,
-                   "Unable to create temp file for external command.");
-    } else {
-      size_t data_locale_len = strlen(data_locale);
-      ssize_t a = write(fd, data_locale, data_locale_len);
-      ssize_t b = write(fd, "\n", 1);
-      if ((size_t)a != data_locale_len || b != 1) {
-        g_free(datafname);
-        datafname = NULL;
-        scr_LogPrint(LPRINT_LOGNORM,
-                     "Unable to write to temp file for external command.");
-      }
-      close(fd);
-      arg_data = datafname;
-    }
-    g_free(data_locale);
-  }
-
-  if ((pid=fork()) == -1) {
-    scr_LogPrint(LPRINT_LOGNORM, "Fork error, cannot launch external command.");
-    g_free(datafname);
-    return;
-  }
-
-  if (pid == 0) { // child
-    // Close standard file descriptors
-    close(STDIN_FILENO);
-    close(STDOUT_FILENO);
-    close(STDERR_FILENO);
-    if (execl(extcmd, extcmd, arg_type, arg_info, bjid, arg_data,
-              (char *)NULL) == -1) {
-      // scr_LogPrint(LPRINT_LOGNORM, "Cannot execute external command.");
-      exit(1);
-    }
-  }
-  g_free(datafname);
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/hooks.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-#ifndef __HOOKS_H__
-#define __HOOKS_H__ 1
-
-#include <time.h>
-#include <loudmouth/loudmouth.h>
-#include "xmpp.h"
-
-// These two defines are used by hk_message_{in,out} arguments
-#define ENCRYPTED_PGP   1
-#define ENCRYPTED_OTR   2
-
-#include "config.h"
-#ifdef MODULES_ENABLE
-#include <glib.h>
-
-#define HOOK_MESSAGE_IN       ( 0x00000001 )
-#define HOOK_MESSAGE_OUT      ( 0x00000002 )
-#define HOOK_STATUS_CHANGE    ( 0x00000004 )
-#define HOOK_MY_STATUS_CHANGE ( 0x00000008 )
-#define HOOK_INTERNAL         ( 0x00000010 )
-
-typedef struct {
-  const char *name;
-  const char *value;
-} hk_arg_t;
-
-typedef void (*hk_handler_t) (guint32 flags, hk_arg_t *args, gpointer userdata);
-
-void hk_add_handler (hk_handler_t handler, guint32 flags, gpointer userdata);
-void hk_del_handler (hk_handler_t handler, gpointer userdata);
-#endif
-
-void hk_message_in(const char *bjid, const char *resname,
-                   time_t timestamp, const char *msg, LmMessageSubType type,
-                   guint encrypted);
-void hk_message_out(const char *bjid, const char *nickname,
-                    time_t timestamp, const char *msg,
-                    guint encrypted,  gpointer xep184);
-void hk_statuschange(const char *bjid, const char *resname, gchar prio,
-                     time_t timestamp, enum imstatus status,
-                     char const *status_msg);
-void hk_mystatuschange(time_t timestamp,
-                              enum imstatus old_status,
-                              enum imstatus new_status, const char *msg);
-
-void hook_execute_internal(const char *hookname);
-
-void hk_ext_cmd_init(const char *command);
-void hk_ext_cmd(const char *bjid, guchar type, guchar info, const char *data);
-
-#endif /* __HOOKS_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/logprint.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-#ifndef __LOGPRINT_H__
-#define __LOGPRINT_H__ 1
-
-// Flags for scr_LogPrint()
-#define LPRINT_NORMAL   1U  // Display in log window
-#define LPRINT_LOG      2U  // Log to file (if enabled)
-#define LPRINT_DEBUG    4U  // Debug message (log if enabled)
-#define LPRINT_NOTUTF8  8U  // Do not convert from UTF-8 to locale
-
-// For convenience...
-#define LPRINT_LOGNORM  (LPRINT_NORMAL|LPRINT_LOG)
-
-void scr_print_logwindow(const char *string);
-void scr_LogPrint(unsigned int flag, const char *fmt, ...);
-
-void scr_DoUpdate(void);
-
-#endif /* __LOGPRINT_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/main.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,494 +0,0 @@
-/*
- * main.c
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- * Parts of this file come from Cabber <cabber@ajmacias.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <signal.h>
-#include <termios.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <glib.h>
-#include <config.h>
-#include <poll.h>
-
-#include "caps.h"
-#include "screen.h"
-#include "settings.h"
-#include "roster.h"
-#include "commands.h"
-#include "histolog.h"
-#include "hooks.h"
-#include "utils.h"
-#include "pgp.h"
-#include "otr.h"
-#include "fifo.h"
-#include "xmpp.h"
-
-#ifdef ENABLE_HGCSET
-# include "hgcset.h"
-#endif
-
-#ifndef WAIT_ANY
-# define WAIT_ANY -1
-#endif
-
-static unsigned int terminate_ui;
-GMainContext *main_context;
-
-static gboolean update_screen = TRUE;
-
-static struct termios *backup_termios;
-
-char *mcabber_version(void)
-{
-  char *ver;
-#ifdef HGCSET
-  ver = g_strdup_printf("%s (%s)", PACKAGE_VERSION, HGCSET);
-#else
-  ver = g_strdup(PACKAGE_VERSION);
-#endif
-  return ver;
-}
-
-static void mcabber_terminate(const char *msg)
-{
-  fifo_deinit();
-  xmpp_disconnect();
-  scr_TerminateCurses();
-
-  // Restore term settings, if needed.
-  if (backup_termios)
-    tcsetattr(fileno(stdin), TCSAFLUSH, backup_termios);
-
-  if (msg)
-    fprintf(stderr, "%s\n", msg);
-  printf("Bye!\n");
-  exit(EXIT_SUCCESS);
-}
-
-void sig_handler(int signum)
-{
-  if (signum == SIGCHLD) {
-    int status;
-    pid_t pid;
-    do {
-      pid = waitpid (WAIT_ANY, &status, WNOHANG);
-      // Check the exit status value if 'eventcmd_checkstatus' is set
-      if (settings_opt_get_int("eventcmd_checkstatus")) {
-        if (pid > 0) {
-          // exit status 2 -> beep
-          if (WIFEXITED(status) && WEXITSTATUS(status) == 2) {
-            scr_Beep();
-          }
-        }
-      }
-    } while (pid > 0);
-    signal(SIGCHLD, sig_handler);
-  } else if (signum == SIGTERM) {
-    mcabber_terminate("Killed by SIGTERM");
-  } else if (signum == SIGINT) {
-    mcabber_terminate("Killed by SIGINT");
-#ifdef USE_SIGWINCH
-  } else if (signum == SIGWINCH) {
-    ungetch(KEY_RESIZE);
-#endif
-  } else {
-    scr_LogPrint(LPRINT_LOGNORM, "Caught signal: %d", signum);
-  }
-}
-
-//  ask_password(what)
-// Return the password, or NULL.
-// The string must be freed after use.
-static char *ask_password(const char *what)
-{
-  char *password, *p;
-  size_t passsize = 128;
-  struct termios orig, new;
-
-  password = g_new0(char, passsize);
-
-  /* Turn echoing off and fail if we can't. */
-  if (tcgetattr(fileno(stdin), &orig) != 0) return NULL;
-  backup_termios = &orig;
-
-  new = orig;
-  new.c_lflag &= ~ECHO;
-  if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) return NULL;
-
-  /* Read the password. */
-  printf("Please enter %s: ", what);
-  if (fgets(password, passsize, stdin) == NULL) return NULL;
-
-  /* Restore terminal. */
-  tcsetattr(fileno(stdin), TCSAFLUSH, &orig);
-  printf("\n");
-  backup_termios = NULL;
-
-  for (p = (char*)password; *p; p++)
-    ;
-  for ( ; p > (char*)password ; p--)
-    if (*p == '\n' || *p == '\r') *p = 0;
-
-  return password;
-}
-
-static void credits(void)
-{
-  const char *v_fmt = "MCabber %s -- Email: mcabber [at] lilotux [dot] net\n";
-  char *v = mcabber_version();
-  printf(v_fmt, v);
-  scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8, v_fmt, v);
-  g_free(v);
-}
-
-static void compile_options(void)
-{
-  puts("Installation data directory: " DATA_DIR "\n");
-#ifdef HAVE_UNICODE
-  puts("Compiled with unicode support.");
-#endif
-#ifdef MODULES_ENABLE
-  puts ("Compiled with modules support.");
-#endif
-#ifdef HAVE_GPGME
-  puts("Compiled with GPG support.");
-#endif
-#ifdef HAVE_LIBOTR
-  puts("Compiled with OTR support.");
-#endif
-#ifdef WITH_ENCHANT
-  puts("Compiled with Enchant support.");
-#endif
-#ifdef WITH_ASPELL
-  puts("Compiled with Aspell support.");
-#endif
-#ifdef ENABLE_DEBUG
-  puts("Compiled with debugging support.");
-#endif
-}
-
-static void main_init_pgp(void)
-{
-#ifdef HAVE_GPGME
-  const char *pk, *pp;
-  char *typed_passwd = NULL;
-  char *p;
-  bool pgp_invalid = FALSE;
-  bool pgp_agent;
-  int retries;
-
-  p = getenv("GPG_AGENT_INFO");
-  pgp_agent = (p && strchr(p, ':'));
-
-  pk = settings_opt_get("pgp_private_key");
-  pp = settings_opt_get("pgp_passphrase");
-
-  if (settings_opt_get("pgp_passphrase_retries"))
-    retries = settings_opt_get_int("pgp_passphrase_retries");
-  else
-    retries = 2;
-
-  if (!pk) {
-    scr_LogPrint(LPRINT_LOGNORM, "WARNING: unknown PGP private key");
-    pgp_invalid = TRUE;
-  } else if (!(pp || pgp_agent)) {
-    // Request PGP passphrase
-    pp = typed_passwd = ask_password("PGP passphrase");
-  }
-  gpg_init(pk, pp);
-  // Erase password from the settings array
-  if (pp) {
-    memset((char*)pp, 0, strlen(pp));
-    if (typed_passwd)
-      g_free(typed_passwd);
-    else
-      settings_set(SETTINGS_TYPE_OPTION, "pgp_passphrase", NULL);
-  }
-  if (!pgp_agent && pk && pp && gpg_test_passphrase()) {
-    // Let's check the pasphrase
-    int i;
-    for (i = 1; retries < 0 || i <= retries; i++) {
-      typed_passwd = ask_password("PGP passphrase"); // Ask again...
-      if (typed_passwd) {
-        gpg_set_passphrase(typed_passwd);
-        memset(typed_passwd, 0, strlen(typed_passwd));
-        g_free(typed_passwd);
-      }
-      if (!gpg_test_passphrase())
-        break; // Ok
-    }
-    if (i > retries)
-      pgp_invalid = TRUE;
-  }
-  if (pgp_invalid)
-    scr_LogPrint(LPRINT_LOGNORM, "WARNING: PGP key/pass invalid");
-#else /* not HAVE_GPGME */
-  scr_LogPrint(LPRINT_LOGNORM, "WARNING: not compiled with PGP support");
-#endif /* HAVE_GPGME */
-}
-
-void mcabber_set_terminate_ui(void)
-{
-  terminate_ui = TRUE;
-}
-
-typedef struct {
-  GSource source;
-  GPollFD pollfd;
-} mcabber_source_t;
-
-static gboolean mcabber_source_prepare(GSource *source, gint *timeout)
-{
-  *timeout = -1;
-  return FALSE;
-}
-
-static gboolean mcabber_source_check(GSource *source)
-{
-  mcabber_source_t *mc_source = (mcabber_source_t *) source;
-  gushort revents = mc_source->pollfd.revents;
-  if (revents)
-    return TRUE;
-  return FALSE;
-}
-
-static gboolean keyboard_activity(void)
-{
-  keycode kcode;
-
-  if (terminate_ui) {
-    return FALSE;
-  }
-  scr_DoUpdate();
-  scr_Getch(&kcode);
-
-  while (kcode.value != ERR) {
-    process_key(kcode);
-    update_screen = TRUE;
-    scr_Getch(&kcode);
-  }
-  scr_CheckAutoAway(FALSE);
-
-  return TRUE;
-}
-
-static gboolean mcabber_source_dispatch(GSource *source, GSourceFunc callback,
-                                        gpointer udata) {
-  return keyboard_activity();
-}
-
-static GSourceFuncs mcabber_source_funcs = {
-  mcabber_source_prepare,
-  mcabber_source_check,
-  mcabber_source_dispatch,
-  NULL,
-  NULL,
-  NULL
-};
-
-int main(int argc, char **argv)
-{
-  char *configFile = NULL;
-  const char *optstring;
-  int optval, optval2;
-  int ret;
-
-  credits();
-
-  signal(SIGTERM, sig_handler);
-  signal(SIGINT,  sig_handler);
-  signal(SIGCHLD, sig_handler);
-#ifdef USE_SIGWINCH
-  signal(SIGWINCH, sig_handler);
-#endif
-  signal(SIGPIPE, SIG_IGN);
-
-  /* Parse command line options */
-  while (1) {
-    int c = getopt(argc, argv, "hVf:");
-    if (c == -1) {
-      break;
-    } else
-      switch (c) {
-      case 'h':
-      case '?':
-        printf("Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]);
-        return (c == 'h' ? 0 : -1);
-      case 'V':
-        compile_options();
-        return 0;
-      case 'f':
-        configFile = g_strdup(optarg);
-        break;
-      }
-  }
-
-  if (optind < argc) {
-    fprintf(stderr, "Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]);
-    return -1;
-  }
-
-  /* Initialize command system, roster and default key bindings */
-  cmd_init();
-  roster_init();
-  settings_init();
-  scr_init_bindings();
-  caps_init();
-  /* Initialize charset */
-  scr_InitLocaleCharSet();
-
-  /* Parsing config file... */
-  ret = cfg_read_file(configFile, TRUE);
-  /* free() configFile if it has been allocated during options parsing */
-  g_free(configFile);
-  /* Leave if there was an error in the config. file */
-  if (ret == -2)
-    exit(EXIT_FAILURE);
-
-  optstring = settings_opt_get("tracelog_file");
-  if (optstring)
-    ut_InitDebug(settings_opt_get_int("tracelog_level"), optstring);
-
-  /* If no password is stored, we ask for it before entering
-     ncurses mode -- unless the username is unknown. */
-  if (settings_opt_get("jid") && !settings_opt_get("password")) {
-    const char *p;
-    char *pwd;
-    p = settings_opt_get("server");
-    if (p)
-      printf("Server: %s\n", p);
-    p = settings_opt_get("jid");
-    if (p)
-      printf("User JID: %s\n", p);
-
-    pwd = ask_password("Jabber password");
-    settings_set(SETTINGS_TYPE_OPTION, "password", pwd);
-    g_free(pwd);
-  }
-
-  /* Initialize PGP system
-     We do it before ncurses initialization because we may need to request
-     a passphrase. */
-  if (settings_opt_get_int("pgp"))
-    main_init_pgp();
-
-  /* Initialize N-Curses */
-  scr_LogPrint(LPRINT_DEBUG, "Initializing N-Curses...");
-  scr_InitCurses();
-  scr_DrawMainWindow(TRUE);
-
-  optval   = (settings_opt_get_int("logging") > 0);
-  optval2  = (settings_opt_get_int("load_logs") > 0);
-  if (optval || optval2)
-    hlog_enable(optval, settings_opt_get("logging_dir"), optval2);
-
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-  /* Initialize spelling */
-  if (settings_opt_get_int("spell_enable")) {
-    spellcheck_init();
-  }
-#endif
-
-  optstring = settings_opt_get("events_command");
-  if (optstring)
-    hk_ext_cmd_init(optstring);
-
-  optstring = settings_opt_get("roster_display_filter");
-  if (optstring)
-    scr_RosterDisplay(optstring);
-  // Empty filter isn't allowed...
-  if (!buddylist_get_filter())
-    scr_RosterDisplay("*");
-
-  chatstates_disabled = settings_opt_get_int("disable_chatstates");
-
-  /* Initialize FIFO named pipe */
-  fifo_init(settings_opt_get("fifo_name"));
-
-  /* Load previous roster state */
-  hlog_load_state();
-
-  main_context = g_main_context_default();
-
-  if (ret < 0) {
-    scr_LogPrint(LPRINT_NORMAL, "No configuration file has been found.");
-    scr_ShowBuddyWindow();
-  } else {
-    /* Connection */
-    xmpp_connect();
-  }
-
-  { // add keypress processing source
-    GSource *mc_source = g_source_new(&mcabber_source_funcs,
-                                      sizeof(mcabber_source_t));
-    GPollFD *mc_pollfd = &(((mcabber_source_t *)mc_source)->pollfd);
-    mc_pollfd->fd = STDIN_FILENO;
-    mc_pollfd->events = POLLIN|POLLERR|POLLPRI;
-    mc_pollfd->revents = 0;
-    g_source_add_poll(mc_source, mc_pollfd);
-    g_source_attach(mc_source, main_context);
-
-    scr_LogPrint(LPRINT_DEBUG, "Entering into main loop...");
-
-    while(!terminate_ui) {
-      if (g_main_context_iteration(main_context, TRUE) == FALSE)
-        keyboard_activity();
-      if (update_roster)
-        scr_DrawRoster();
-      if(update_screen)
-        scr_DoUpdate();
-    }
-
-    g_source_destroy(mc_source);
-    g_source_unref(mc_source);
-  }
-
-  scr_TerminateCurses();
-#ifdef MODULES_ENABLE
-  cmd_deinit();
-#endif
-  fifo_deinit();
-#ifdef HAVE_LIBOTR
-  otr_terminate();
-#endif
-  xmpp_disconnect();
-#ifdef HAVE_GPGME
-  gpg_terminate();
-#endif
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-  /* Deinitialize spelling */
-  if (settings_opt_get_int("spell_enable"))
-    spellcheck_deinit();
-#endif
-  /* Save pending message state */
-  hlog_save_state();
-  caps_free();
-
-  printf("\n\nThanks for using mcabber!\n");
-
-  return 0;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/main.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-#ifndef __MCABBER_MAIN_H__
-#define __MCABBER_MAIN_H__ 1
-
-extern GMainContext *main_context;
-
-void mcabber_set_terminate_ui(void);
-char *mcabber_version(void);
-
-#endif
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/nohtml.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-/*
- * nohtml.c     -- (X)HTML helper functions
- *
- * Copyright (C) 2008,2009 Mikael Berthe <mikael@lilotux.net>
- * Some portions come from the jabberd project, see below.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- *
- *
- * Some parts come from libjabber/str.c:
- * Copyright (c) 1999-2002 Jabber.com, Inc.  All Rights Reserved.  Contact
- * information for Jabber.com, Inc. is available at http://www.jabber.com/.
- * Portions Copyright (c) 1998-1999 Jeremie Miller.
- */
-
-#include <string.h>
-#include <glib.h>
-#include <config.h>
-
-
-/*  html_strip(htmlbuf)
- * Remove html entities from htmlbuf and try to convert it to plain text.
- * The caller must g_free the string after use.
- * Code mostly derived from strunescape(), in libjabber.
- */
-char *html_strip(const char *htmlbuf)
-{
-  int i, j=0;
-  char *nohtml;
-
-  if (!htmlbuf) return(NULL);
-
-  nohtml = g_strdup(htmlbuf);
-
-  if (!strchr(htmlbuf, '&') && !strchr(htmlbuf, '<'))
-    return(nohtml);
-
-  for (i = 0; i < (int)strlen(htmlbuf); i++) {
-    if (htmlbuf[i] == '&') {
-      if (!strncmp(&htmlbuf[i],"&amp;",5)) {
-        nohtml[j] = '&';
-        i += 4;
-      } else if (!strncmp(&htmlbuf[i],"&quot;", 6)) {
-        nohtml[j] = '\"';
-        i += 5;
-      } else if (!strncmp(&htmlbuf[i],"&apos;", 6)) {
-        nohtml[j] = '\'';
-        i += 5;
-      } else if (!strncmp(&htmlbuf[i],"&lt;", 4)) {
-        nohtml[j] = '<';
-        i += 3;
-      } else if (!strncmp(&htmlbuf[i],"&gt;", 4)) {
-        nohtml[j] = '>';
-        i += 3;
-      }
-    } else if (!strncmp(&htmlbuf[i],"<br>", 4) ||
-               !strncmp(&htmlbuf[i],"<br/>", 5)) {
-      nohtml[j] = '\n';
-      i += (htmlbuf[i+3] == '/' ? 4 : 3);
-    } else if (htmlbuf[i] == '<') {
-      /* Let's strip all unknown tags */
-      j--;
-      while (htmlbuf[++i] != '>');
-    } else
-      nohtml[j] = htmlbuf[i];
-    j++;
-  }
-  nohtml[j] = '\0';
-  return nohtml;
-}
-
-/*  html_escape(text)
- * Add (x)html entities to the text.
- * The caller must g_free the string after use.
- * Code mostly derived from strescape(), in libjabber.
- */
-char *html_escape(const char *text)
-{
-  int i, j;
-  int oldlen, newlen;
-  char *html;
-
-  if (!text) return(NULL);
-
-  oldlen = newlen = strlen(text);
-
-  for (i = 0; i < oldlen; i++) {
-    switch(text[i])
-    {
-      case '&':
-          newlen += 5;
-          break;
-      case '\'':
-          newlen += 6;
-          break;
-          case '\"':
-              newlen += 6;
-          break;
-      case '<':
-          newlen += 4;
-          break;
-      case '>':
-          newlen += 4;
-          break;
-      case '\n':
-          newlen += 5;
-    }
-  }
-
-  if (oldlen == newlen)
-    return g_strdup(text);
-
-  html = g_new0(char, newlen+1);
-
-  for (i = j = 0; i < oldlen; i++) {
-    switch(text[i])
-    {
-      case '&':
-          memcpy(&html[j], "&amp;", 5);
-          j += 5;
-          break;
-      case '\'':
-          memcpy(&html[j], "&apos;", 6);
-          j += 6;
-          break;
-      case '\"':
-          memcpy(&html[j], "&quot;", 6);
-          j += 6;
-          break;
-      case '<':
-          memcpy(&html[j], "&lt;", 4);
-          j += 4;
-          break;
-      case '>':
-          memcpy(&html[j], "&gt;", 4);
-          j += 4;
-          break;
-      case '\n':
-          memcpy(&html[j], "<br/>", 5);
-          j += 5;
-          break;
-      default:
-          html[j++] = text[i];
-    }
-  }
-  return html;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/nohtml.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-#ifndef __NOHTML_H__
-#define __NOHTML_H__ 1
-
-char *html_strip(const char *buf);
-char *html_escape(const char *text);
-
-#endif /* __NOHTML_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/otr.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,777 +0,0 @@
-/*
- * otr.c        -- Off-The-Record Messaging for mcabber
- *
- * Copyright (C) 2007-2009 Frank Zschockelt <mcabber_otr@freakysoft.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <config.h>
-#include <glib.h>
-
-#ifdef HAVE_LIBOTR
-
-#include "hbuf.h"
-#include "logprint.h"
-#include "nohtml.h"
-#include "otr.h"
-#include "roster.h"
-#include "screen.h"
-#include "settings.h"
-#include "utils.h"
-#include "xmpp.h"
-
-#define OTR_PROTOCOL_NAME "jabber"
-
-static OtrlUserState userstate = NULL;
-static char *account = NULL;
-static char *keyfile = NULL;
-static char *fprfile = NULL;
-
-static int otr_is_enabled = FALSE;
-
-static OtrlPolicy cb_policy             (void *opdata, ConnContext *ctx);
-static void       cb_create_privkey     (void *opdata,
-                                         const char *accountname,
-                                         const char *protocol);
-static int        cb_is_logged_in       (void *opdata,
-                                         const char *accountname,
-                                         const char *protocol,
-                                         const char *recipient);
-static void       cb_inject_message     (void *opdata,
-                                         const char *accountname,
-                                         const char *protocol,
-                                         const char *recipient,
-                                         const char *message);
-static void       cb_notify             (void *opdata,
-                                         OtrlNotifyLevel level,
-                                         const char *accountname,
-                                         const char *protocol,
-                                         const char *username,
-                                         const char *title,
-                                         const char *primary,
-                                         const char *secondary);
-static int        cb_display_otr_message(void *opdata,
-                                         const char *accountname,
-                                         const char *protocol,
-                                         const char *username,
-                                         const char *msg);
-static void       cb_update_context_list(void *opdata);
-static const char *cb_protocol_name     (void *opdata, const char *protocol);
-static void       cb_protocol_name_free (void *opdata,
-                                         const char *protocol_name);
-static void       cb_new_fingerprint    (void *opdata, OtrlUserState us,
-                                         const char *accountname,
-                                         const char *protocol,
-                                         const char *username,
-                                         unsigned char fingerprint[20]);
-static void       cb_write_fingerprints (void *opdata);
-static void       cb_gone_secure        (void *opdata, ConnContext *context);
-static void       cb_gone_insecure      (void *opdata, ConnContext *context);
-static void       cb_still_secure       (void *opdata, ConnContext *context,
-                                         int is_reply);
-static void       cb_log_message        (void *opdata, const char *message);
-static int        cb_max_message_size   (void *opdata, ConnContext *context);
-
-static OtrlMessageAppOps ops =
-{
-  cb_policy,
-  cb_create_privkey,
-  cb_is_logged_in,
-  cb_inject_message,
-  cb_notify,
-  cb_display_otr_message,
-  cb_update_context_list,
-  cb_protocol_name,
-  cb_protocol_name_free,
-  cb_new_fingerprint,
-  cb_write_fingerprints,
-  cb_gone_secure,
-  cb_gone_insecure,
-  cb_still_secure,
-  cb_log_message,
-  cb_max_message_size,
-  NULL, /*account_name*/
-  NULL  /*account_name_free*/
-};
-
-static void otr_message_disconnect(ConnContext *ctx);
-static ConnContext *otr_get_context(const char *buddy);
-static void otr_startstop(const char *buddy, int start);
-static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx);
-
-static char *otr_get_dir(void);
-
-void otr_init(const char *fjid)
-{
-  char *root;
-
-  if (userstate) //already initialised
-    return;
-
-  otr_is_enabled = !!settings_opt_get_int("otr");
-
-  if (!otr_is_enabled)
-    return;
-
-  OTRL_INIT;
-
-  userstate = otrl_userstate_create();
-
-  root = otr_get_dir();
-  account = jidtodisp(fjid);
-  keyfile = g_strdup_printf("%s%s.key", root, account);
-  fprfile = g_strdup_printf("%s%s.fpr", root, account);
-  g_free(root);
-
-  if (otrl_privkey_read(userstate, keyfile)){
-    scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR key from %s", keyfile);
-    cb_create_privkey(NULL, account, OTR_PROTOCOL_NAME);
-  }
-  if (otrl_privkey_read_fingerprints(userstate, fprfile, NULL, NULL)){
-    scr_LogPrint(LPRINT_LOGNORM, "Could not read OTR fingerprints from %s",
-                 fprfile);
-  }
-}
-
-void otr_terminate(void)
-{
-  ConnContext *ctx;
-
-  if (!otr_is_enabled)
-    return;
-
-  for (ctx = userstate->context_root; ctx; ctx = ctx->next)
-    if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
-      otr_message_disconnect(ctx);
-
-  g_free(account);
-  account = NULL;
-
-  /* XXX This #ifdef is a quick workaround: when mcabber
-   * is linked to both gnutls and libotr, libgcrypt will
-   * segfault when we call otrl_userstate_free().
-   * This is reported to be a bug in libgcrypt :-/
-   * Mikael
-   */
-#if defined(HAVE_GNUTLS) && !defined(HAVE_OPENSSL) //TODO: broken now
-  if (!settings_opt_get_int("ssl"))
-#endif
-  otrl_userstate_free(userstate);
-
-  userstate = NULL;
-  g_free(keyfile);
-  keyfile = NULL;
-}
-
-static char *otr_get_dir(void)
-{
-  const char *configured_dir = settings_opt_get("otr_dir");
-
-  if (configured_dir && *configured_dir) {
-    char *xp_conf_dir;
-    int l;
-    xp_conf_dir = expand_filename(configured_dir);
-    // The path must be slash-terminated
-    l = strlen(xp_conf_dir);
-    if (xp_conf_dir[l-1] != '/') {
-      char *xp_conf_dir_tmp = xp_conf_dir;
-      xp_conf_dir = g_strdup_printf("%s/", xp_conf_dir_tmp);
-      g_free(xp_conf_dir_tmp);
-    }
-    return xp_conf_dir;
-  } else {
-    return expand_filename("~/.mcabber/otr/");
-  }
-}
-
-static ConnContext *otr_get_context(const char *buddy)
-{
-  int null = 0;
-  ConnContext *ctx;
-  char *lowcasebuddy = g_strdup(buddy);
-
-  mc_strtolower(lowcasebuddy);
-  ctx = otrl_context_find(userstate, lowcasebuddy, account, OTR_PROTOCOL_NAME,
-                          1, &null, NULL, NULL);
-  g_free(lowcasebuddy);
-  return ctx;
-}
-
-static void otr_message_disconnect(ConnContext *ctx)
-{
-  if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
-    cb_gone_insecure(NULL, ctx);
-  otrl_message_disconnect(userstate, &ops, NULL, ctx->accountname,
-                          ctx->protocol, ctx->username);
-}
-
-static void otr_startstop(const char *buddy, int start)
-{
-  char *msg = NULL;
-  ConnContext *ctx = otr_get_context(buddy);
-
-  if (!userstate || !ctx)
-    return;
-
-  if (start && ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
-    otr_message_disconnect(ctx);
-
-  if (start) {
-    OtrlPolicy policy = cb_policy(NULL, ctx);
-    if (policy == plain) {
-      scr_LogPrint(LPRINT_LOGNORM, "The OTR policy for this user is set to"
-      " plain. You have to change it first.");
-      return;
-    }
-    msg = otrl_proto_default_query_msg(ctx->accountname, policy);
-    cb_inject_message(NULL, ctx->accountname, ctx->protocol, ctx->username,
-                      msg);
-    free (msg);
-  }
-  else
-    otr_message_disconnect(ctx);
-}
-
-void otr_establish(const char *buddy)
-{
-  otr_startstop(buddy, 1);
-}
-
-void otr_disconnect(const char *buddy)
-{
-  otr_startstop(buddy, 0);
-}
-
-void otr_fingerprint(const char *buddy, const char *trust)
-{
-  char fpr[45], *tr;
-  ConnContext *ctx = otr_get_context(buddy);
-  if (!userstate || !ctx)
-    return;
-
-  if (!ctx->active_fingerprint || !ctx->active_fingerprint->fingerprint) {
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "No active fingerprint - start OTR for this buddy first.");
-    return;
-  }
-
-  otrl_privkey_hash_to_human(fpr, ctx->active_fingerprint->fingerprint);
-  if (trust) {
-    if (strcmp(fpr, trust) == 0)
-      otrl_context_set_trust(ctx->active_fingerprint, "trust");
-    else
-      otrl_context_set_trust(ctx->active_fingerprint, NULL);
-  }
-
-  tr = ctx->active_fingerprint->trust;
-  scr_LogPrint(LPRINT_LOGNORM, "%s [%44s]: %s", ctx->username, fpr,
-               tr && *tr ?  "trusted" : "untrusted");
-  cb_write_fingerprints(NULL);
-}
-
-static void otr_handle_smp_tlvs(OtrlTLV *tlvs, ConnContext *ctx)
-{
-  OtrlTLV *tlv = NULL;
-  char *sbuf = NULL;
-  NextExpectedSMP nextMsg = ctx->smstate->nextExpected;
-
-  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP1);
-  if (tlv) {
-    if (nextMsg != OTRL_SMP_EXPECT1)
-      otr_smp_abort(ctx->username);
-    else {
-      sbuf = g_strdup_printf("OTR: Received SMP Initiation. "
-                             "Answer with /otr smpr %s $secret",
-                             ctx->username);
-    }
-  }
-  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP2);
-  if (tlv) {
-    if (nextMsg != OTRL_SMP_EXPECT2)
-      otr_smp_abort(ctx->username);
-    else {
-      sbuf = g_strdup("OTR: Received SMP Response.");
-      /* If we received TLV2, we will send TLV3 and expect TLV4 */
-      ctx->smstate->nextExpected = OTRL_SMP_EXPECT4;
-    }
-  }
-  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP3);
-  if (tlv) {
-    if (nextMsg != OTRL_SMP_EXPECT3)
-      otr_smp_abort(ctx->username);
-    else {
-      /* If we received TLV3, we will send TLV4
-       * We will not expect more messages, so prepare for next SMP */
-      ctx->smstate->nextExpected = OTRL_SMP_EXPECT1;
-      /* Report result to user */
-      if (ctx->active_fingerprint && ctx->active_fingerprint->trust &&
-         *ctx->active_fingerprint->trust != '\0')
-        sbuf = g_strdup("OTR: SMP succeeded");
-      else
-        sbuf = g_strdup("OTR: SMP failed");
-    }
-  }
-  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP4);
-  if (tlv) {
-    if (nextMsg != OTRL_SMP_EXPECT4)
-      otr_smp_abort(ctx->username);
-    else {
-      /* We will not expect more messages, so prepare for next SMP */
-      ctx->smstate->nextExpected = OTRL_SMP_EXPECT1;
-      /* Report result to user */
-      if (ctx->active_fingerprint && ctx->active_fingerprint->trust &&
-         *ctx->active_fingerprint->trust != '\0')
-        sbuf = g_strdup("OTR: SMP succeeded");
-      else
-        sbuf = g_strdup("OTR: SMP failed");
-    }
-  }
-  tlv = otrl_tlv_find(tlvs, OTRL_TLV_SMP_ABORT);
-  if (tlv) {
-    /* The message we are waiting for will not arrive, so reset
-     * and prepare for the next SMP */
-    sbuf = g_strdup("OTR: SMP aborted by your buddy");
-    ctx->smstate->nextExpected = OTRL_SMP_EXPECT1;
-  }
-
-  if (sbuf) {
-    scr_WriteIncomingMessage(ctx->username, sbuf, 0, HBB_PREFIX_INFO, 0);
-    g_free(sbuf);
-  }
-}
-
-/*
- * returns whether a otr_message was received
- * sets *otr_data to NULL, when it was an internal otr message
- */
-int otr_receive(char **otr_data, const char *buddy, int *free_msg)
-{
-  int ignore_message;
-  char *newmessage = NULL;
-  OtrlTLV *tlvs = NULL;
-  OtrlTLV *tlv = NULL;
-  ConnContext *ctx;
-
-  ctx = otr_get_context(buddy);
-  *free_msg = 0;
-  ignore_message = otrl_message_receiving(userstate, &ops, NULL,
-                                          ctx->accountname, ctx->protocol,
-                                          ctx->username, *otr_data,
-                                          &newmessage, &tlvs,NULL, NULL);
-
-
-  tlv = otrl_tlv_find(tlvs, OTRL_TLV_DISCONNECTED);
-  if (tlv) {
-    /* Notify the user that the other side disconnected. */
-    if (ctx) {
-      cb_gone_insecure(NULL, ctx);
-      otr_disconnect(ctx->username);
-    }
-  }
-
-  otr_handle_smp_tlvs(tlvs, ctx);
-
-  if (tlvs != NULL)
-    otrl_tlv_free(tlvs);
-
-  if (ignore_message)
-    *otr_data = NULL;
-
-  if (!ignore_message && newmessage) {
-    *free_msg = 1;
-    *otr_data = html_strip(newmessage);
-    otrl_message_free(newmessage);
-    if (ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
-      return 1;
-  }
-  return 0;
-}
-
-int otr_send(char **msg, const char *buddy)
-{
-  gcry_error_t err;
-  char *newmessage = NULL;
-  char *htmlmsg;
-  ConnContext *ctx = otr_get_context(buddy);
-
-  if (ctx->msgstate == OTRL_MSGSTATE_PLAINTEXT)
-    err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname,
-                               ctx->protocol, ctx->username, *msg, NULL,
-                               &newmessage, NULL, NULL);
-  else {
-    htmlmsg = html_escape(*msg);
-    err = otrl_message_sending(userstate, &ops, NULL, ctx->accountname,
-                               ctx->protocol, ctx->username, htmlmsg, NULL,
-                               &newmessage, NULL, NULL);
-    g_free(htmlmsg);
-  }
-
-  if (err)
-    *msg = NULL; /*something went wrong, don't send the plain-message! */
-
-  if (!err && newmessage) {
-    *msg = g_strdup(newmessage);
-    otrl_message_free(newmessage);
-    if (cb_policy(NULL, ctx) & OTRL_POLICY_REQUIRE_ENCRYPTION ||
-        ctx->msgstate == OTRL_MSGSTATE_ENCRYPTED)
-      return 1;
-  }
-  return 0;
-}
-
-/* Prints OTR connection state */
-void otr_print_info(const char *buddy)
-{
-  const char *state, *auth, *policy;
-  ConnContext *ctx = otr_get_context(buddy);
-  OtrlPolicy p = cb_policy(ctx->app_data, ctx);
-
-  if (!userstate || !ctx)
-    return;
-
-  switch (ctx->msgstate) {
-    case OTRL_MSGSTATE_PLAINTEXT: state = "plaintext"; break;
-    case OTRL_MSGSTATE_ENCRYPTED:
-     switch (ctx->protocol_version) {
-       case 1: state = "encrypted V1"; break;
-       case 2: state = "encrypted V2"; break;
-       default:state = "encrypted";
-     };
-     break;
-    case OTRL_MSGSTATE_FINISHED:  state = "finished";  break;
-    default:                      state = "unknown state";
-  }
-  switch (ctx->auth.authstate) {
-    case OTRL_AUTHSTATE_NONE:
-      switch (ctx->otr_offer) {
-        case OFFER_NOT:      auth = "no offer sent";  break;
-        case OFFER_SENT:     auth = "offer sent";     break;
-        case OFFER_ACCEPTED: auth = "offer accepted"; break;
-        case OFFER_REJECTED: auth = "offer rejected"; break;
-        default:             auth = "unknown auth";
-      }
-      break;
-    case OTRL_AUTHSTATE_AWAITING_DHKEY:
-      auth = "awaiting D-H key";          break;
-    case OTRL_AUTHSTATE_AWAITING_REVEALSIG:
-      auth = "awaiting reveal signature"; break;
-    case OTRL_AUTHSTATE_AWAITING_SIG:
-      auth = "awaiting signature";        break;
-    case OTRL_AUTHSTATE_V1_SETUP:
-      auth = "v1 setup";                  break;
-    default:
-      auth = "unknown auth";
-  }
-  if (p == OTRL_POLICY_NEVER)
-    policy = "plain";
-  else if (p == (OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1))
-    policy = "opportunistic";
-  else if (p == (OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1))
-    policy = "manual";
-  else if (p == (OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1))
-    policy = "always";
-  else
-    policy = "unknown";
-
-  scr_LogPrint(LPRINT_LOGNORM, "%s: %s (%s) [%s]",
-               ctx->username, state, auth, policy);
-}
-
-static ConnContext *otr_context_encrypted(const char *buddy)
-{
-  ConnContext *ctx = otr_get_context(buddy);
-
-  if (!userstate || !ctx  || ctx->msgstate != OTRL_MSGSTATE_ENCRYPTED){
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "You have to start an OTR channel with %s before you can "
-                 "use SMP.", buddy);
-    return NULL;
-  }
-
-  return ctx;
-}
-
-void otr_smp_query(const char *buddy, const char *secret)
-{
-  ConnContext *ctx = otr_context_encrypted(buddy);
-
-  if (!secret) {
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Using SMP without a secret isn't a good idea.");
-    return;
-  }
-
-  if (ctx) {
-    otrl_message_initiate_smp(userstate, &ops, NULL, ctx,
-                              (const unsigned char *)secret,
-                              strlen(secret));
-    scr_WriteIncomingMessage(ctx->username,
-                             "OTR: Socialist Millionaires' Protocol "
-                             "initiated.", 0, HBB_PREFIX_INFO, 0);
-  }
-}
-
-void otr_smp_respond(const char *buddy, const char *secret)
-{
-  ConnContext *ctx = otr_context_encrypted(buddy);
-
-  if (!secret) {
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Using SMP without a secret isn't a good idea.");
-    return;
-  }
-
-  if (ctx) {
-    if (!ctx->smstate->secret) {
-      scr_LogPrint(LPRINT_LOGNORM,
-                   "Don't call smpr until you have received an SMP "
-                   "Initiation!");
-      return;
-    }
-    otrl_message_respond_smp(userstate, &ops, NULL, ctx,
-                             (const unsigned char *)secret,
-                             strlen(secret));
-    scr_WriteIncomingMessage(ctx->username,
-                             "OTR: Socialist Millionaires' Protocol: "
-                             "response sent", 0, HBB_PREFIX_INFO, 0);
-  }
-}
-
-void otr_smp_abort(const char *buddy)
-{
-  ConnContext *ctx = otr_context_encrypted(buddy);
-
-  if (ctx) {
-    otrl_message_abort_smp(userstate, &ops, NULL, ctx);
-    scr_WriteIncomingMessage(ctx->username,
-                             "OTR: Socialist Millionaires' Protocol aborted.",
-                             0, HBB_PREFIX_INFO, 0);
-  }
-}
-
-void otr_key(void)
-{
-  OtrlPrivKey *key;
-  char readable[45] = "";
-
-  if(!userstate)
-    return;
-  for (key = userstate->privkey_root; key; key = key->next) {
-    otrl_privkey_fingerprint(userstate, readable, key->accountname,
-                             key->protocol);
-    scr_LogPrint(LPRINT_LOGNORM, "%s: %s", key->accountname, readable);
-  }
-}
-
-/* Return the OTR policy for the given context. */
-static OtrlPolicy cb_policy(void *opdata, ConnContext *ctx)
-{
-  enum otr_policy p = settings_otr_getpolicy(NULL);
-
-  if(ctx)
-    if(settings_otr_getpolicy(ctx->username))
-      p = settings_otr_getpolicy(ctx->username);
-
-  switch (p) {
-    case plain:
-      return OTRL_POLICY_NEVER;
-    case opportunistic:
-      return OTRL_POLICY_OPPORTUNISTIC & ~OTRL_POLICY_ALLOW_V1;
-    case manual:
-      return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1;
-    case always:
-      return OTRL_POLICY_ALWAYS & ~OTRL_POLICY_ALLOW_V1;
-  }
-
-  return OTRL_POLICY_MANUAL & ~OTRL_POLICY_ALLOW_V1;
-}
-
-/* Create a private key for the given accountname/protocol if
- * desired. */
-static void cb_create_privkey(void *opdata, const char *accountname,
-                              const char *protocol)
-{
-  gcry_error_t e;
-  char *root;
-
-  scr_LogPrint(LPRINT_LOGNORM,
-               "Generating new OTR key for %s. This may take a while...",
-               accountname);
-  scr_DoUpdate();
-
-  e = otrl_privkey_generate(userstate, keyfile, accountname, protocol);
-
-  if (e) {
-    root = otr_get_dir();
-    scr_LogPrint(LPRINT_LOGNORM, "OTR key generation failed! Please mkdir "
-                 "%s if you want to use otr encryption.", root);
-    g_free(root);
-  }
-  else
-    scr_LogPrint(LPRINT_LOGNORM, "OTR key generated.");
-}
-
-/* Report whether you think the given user is online.  Return 1 if
- * you think he is, 0 if you think he isn't, -1 if you're not sure.
- * If you return 1, messages such as heartbeats or other
- * notifications may be sent to the user, which could result in "not
- * logged in" errors if you're wrong. */
-static int cb_is_logged_in(void *opdata, const char *accountname,
-                           const char *protocol, const char *recipient)
-{
-  int ret = (roster_getstatus(recipient, NULL) != offline);
-  return ret;
-}
-
-/* Send the given IM to the given recipient from the given
- * accountname/protocol. */
-static void cb_inject_message(void *opdata, const char *accountname,
-                              const char *protocol, const char *recipient,
-                              const char *message)
-{
-  if (roster_gettype(recipient) == ROSTER_TYPE_USER)
-    xmpp_send_msg(recipient, message, ROSTER_TYPE_USER, "", TRUE, NULL,
-                  LM_MESSAGE_SUB_TYPE_NOT_SET, NULL);
-}
-
-/* Display a notification message for a particular
- * accountname / protocol / username conversation. */
-static void cb_notify(void *opdata, OtrlNotifyLevel level,
-                      const char *accountname, const char *protocol,
-                      const char *username, const char *title,
-                      const char *primary, const char *secondary)
-{
-  char *type;
-  char *sbuf = NULL;
-  switch (level) {
-    case OTRL_NOTIFY_ERROR:   type = "error";   break;
-    case OTRL_NOTIFY_WARNING: type = "warning"; break;
-    case OTRL_NOTIFY_INFO:    type = "info";    break;
-    default:                  type = "unknown";
-  }
-  sbuf = g_strdup_printf("OTR %s:%s\n%s\n%s",type,title, primary, secondary);
-  scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0);
-  g_free(sbuf);
-}
-
-/* Display an OTR control message for a particular
- * accountname / protocol / username conversation.  Return 0 if you are able
- * to successfully display it.  If you return non-0 (or if this
- * function is NULL), the control message will be displayed inline,
- * as a received message, or else by using the above notify()
- * callback. */
-static int cb_display_otr_message(void *opdata, const char *accountname,
-                                  const char *protocol, const char *username,
-                                  const char *msg)
-{
-  char *strippedmsg = html_strip(msg);
-  scr_WriteIncomingMessage(username, strippedmsg, 0, HBB_PREFIX_INFO, 0);
-  g_free(strippedmsg);
-  return 0;
-}
-
-/* When the list of ConnContexts changes (including a change in
- * state), this is called so the UI can be updated. */
-static void cb_update_context_list(void *opdata)
-{
-  /*maybe introduce new status characters for mcabber,
-   * then use this function (?!)*/
-}
-
-/* Return a newly allocated string containing a human-friendly name
- * for the given protocol id */
-static const char *cb_protocol_name(void *opdata, const char *protocol)
-{
-  return protocol;
-}
-
-/* Deallocate a string allocated by protocol_name */
-static void cb_protocol_name_free (void *opdata, const char *protocol_name)
-{
-  /* We didn't allocated memory, so we don't have to free anything :p */
-}
-
-/* A new fingerprint for the given user has been received. */
-static void cb_new_fingerprint(void *opdata, OtrlUserState us,
-                               const char *accountname, const char *protocol,
-                               const char *username,
-                               unsigned char fingerprint[20])
-{
-  char *sbuf = NULL;
-  char readable[45];
-
-  otrl_privkey_hash_to_human(readable, fingerprint);
-  sbuf = g_strdup_printf("OTR: new fingerprint: %s", readable);
-  scr_WriteIncomingMessage(username, sbuf, 0, HBB_PREFIX_INFO, 0);
-  g_free(sbuf);
-}
-
-/* The list of known fingerprints has changed.  Write them to disk. */
-static void cb_write_fingerprints(void *opdata)
-{
-  otrl_privkey_write_fingerprints(userstate, fprfile);
-}
-
-/* A ConnContext has entered a secure state. */
-static void cb_gone_secure(void *opdata, ConnContext *context)
-{
-  scr_WriteIncomingMessage(context->username, "OTR: channel established", 0,
-                           HBB_PREFIX_INFO, 0);
-}
-
-/* A ConnContext has left a secure state. */
-static void cb_gone_insecure(void *opdata, ConnContext *context)
-{
-  scr_WriteIncomingMessage(context->username, "OTR: channel closed", 0,
-                           HBB_PREFIX_INFO, 0);
-}
-
-/* We have completed an authentication, using the D-H keys we
- * already knew.  is_reply indicates whether we initiated the AKE. */
-static void cb_still_secure(void *opdata, ConnContext *context, int is_reply)
-{
-  scr_WriteIncomingMessage(context->username, "OTR: channel reestablished", 0,
-                           HBB_PREFIX_INFO, 0);
-}
-
-/* Log a message.  The passed message will end in "\n". */
-static void cb_log_message(void *opdata, const char *message)
-{
-  scr_LogPrint(LPRINT_DEBUG, "OTR: %s", message);
-}
-
-/* Find the maximum message size supported by this protocol. */
-static int cb_max_message_size(void *opdata, ConnContext *context)
-{
-  return 8192;
-}
-
-int otr_enabled(void)
-{
-  return otr_is_enabled;
-}
-
-#else /* !HAVE_LIBOTR */
-
-int otr_enabled(void)
-{
-  return FALSE;
-}
-
-#endif /* HAVE_LIBOTR */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/otr.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,40 +0,0 @@
-#ifndef __OTR_H__
-#define __OTR_H__ 1
-
-#ifdef HAVE_LIBOTR
-
-#include <libotr/proto.h>
-#include <libotr/message.h>
-#include <libotr/privkey.h>
-
-enum otr_policy {
-  plain,
-  opportunistic,
-  manual,
-  always
-};
-
-void otr_init(const char *jid);
-void otr_terminate(void);
-
-void otr_establish  (const char * buddy);
-void otr_disconnect (const char * buddy);
-void otr_fingerprint(const char * buddy, const char * trust);
-void otr_print_info (const char * buddy);
-
-void otr_smp_query  (const char * buddy, const char * secret);
-void otr_smp_respond(const char * buddy, const char * secret);
-void otr_smp_abort  (const char * buddy);
-
-void otr_key        (void);
-
-int  otr_receive    (char **otr_data, const char * buddy, int * free_msg);
-int  otr_send       (char **msg, const char *buddy);
-
-#endif /* HAVE_LIBOTR */
-
-int  otr_enabled    (void);
-
-#endif /* __OTR_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/pgp.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,475 +0,0 @@
-/*
- * pgp.c        -- PGP utility functions
- *
- * Copyright (C) 2006-2009 Mikael Berthe <mikael@lilotux.net>
- * Some parts inspired by centericq (impgp.cc)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <config.h>
-
-#ifdef HAVE_GPGME
-
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <locale.h>
-#include <sys/mman.h>
-#include <glib.h>
-
-#include "pgp.h"
-#include "logprint.h"
-
-#define MIN_GPGME_VERSION "1.0.0"
-
-static struct gpg_struct
-{
-  int enabled;
-  char *private_key;
-  char *passphrase;
-} gpg;
-
-
-//  gpg_init(priv_key, passphrase)
-// Initialize the GPG sub-systems.  This function must be invoked early.
-// Note: priv_key & passphrase are optional, they can be set later.
-// This function returns 0 if gpgme is available and initialized;
-// if not it returns the gpgme error code.
-int gpg_init(const char *priv_key, const char *passphrase)
-{
-  gpgme_error_t err;
-
-  // Check for version and OpenPGP protocol support.
-  if (!gpgme_check_version(MIN_GPGME_VERSION)) {
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "GPGME initialization error: Bad library version");
-    return -1;
-  }
-
-  err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
-  if (err) {
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME initialization error: %s", gpgme_strerror(err));
-    return err;
-  }
-
-  // Set the locale information.
-  gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
-  gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
-
-  // Store private data.
-  gpg_set_private_key(priv_key);
-  gpg_set_passphrase(passphrase);
-
-  gpg.enabled = 1;
-  return 0;
-}
-
-//  gpg_terminate()
-// Destroy data and free memory.
-void gpg_terminate(void)
-{
-  gpg.enabled = 0;
-  gpg_set_passphrase(NULL);
-  gpg_set_private_key(NULL);
-}
-
-//  gpg_set_passphrase(passphrase)
-// Set the current passphrase (use NULL to erase it).
-void gpg_set_passphrase(const char *passphrase)
-{
-  // Remove current passphrase
-  if (gpg.passphrase) {
-    ssize_t len = strlen(gpg.passphrase);
-    memset(gpg.passphrase, 0, len);
-    munlock(gpg.passphrase, len);
-    g_free(gpg.passphrase);
-  }
-  if (passphrase) {
-    gpg.passphrase = g_strdup(passphrase);
-    mlock(gpg.passphrase, strlen(gpg.passphrase));
-  } else {
-    gpg.passphrase = NULL;
-  }
-}
-
-//  gpg_set_private_key(keyid)
-// Set the current private key id (use NULL to unset it).
-void gpg_set_private_key(const char *priv_keyid)
-{
-  g_free(gpg.private_key);
-  if (priv_keyid)
-    gpg.private_key = g_strdup(priv_keyid);
-  else
-    gpg.private_key = NULL;
-}
-
-//  strip_header_footer(data)
-// Remove PGP header & footer from data.
-// Return a new string, or NULL.
-// The string must be freed by the caller with g_free() when no longer needed.
-static char *strip_header_footer(const char *data)
-{
-  char *p, *q;
-
-  if (!data)
-    return NULL;
-
-  // p: beginning of real data
-  // q: end of real data
-
-  // Strip header (to the first empty line)
-  p = strstr(data, "\n\n");
-  if (!p)
-    return g_strdup(data);
-
-  // Strip footer
-  // We want to remove the last lines, until the line beginning with a '-'
-  p += 2;
-  for (q = p ; *q; q++) ;
-  // (q is at the end of data now)
-  for (q--; q > p && (*q != '\n' || *(q+1) != '-'); q--) ;
-
-  if (q <= p)
-    return NULL; // Shouldn't happen...
-
-  return g_strndup(p, q-p);
-}
-
-// GCC ignores casts to void, thus we need to hack around that
-static inline void ignore(void*x) {}
-
-//  passphrase_cb()
-// GPGME passphrase callback function.
-static gpgme_error_t passphrase_cb(void *hook, const char *uid_hint,
-                       const char *passphrase_info, int prev_was_bad, int fd)
-{
-  ssize_t len;
-
-  // Abort if we do not have the password.
-  if (!gpg.passphrase) {
-    ignore((void*)write(fd, "\n", 1)); // We have an error anyway, thus it does
-                                       // not matter if we fail again.
-    return gpg_error(GPG_ERR_CANCELED);
-  }
-
-  // Write the passphrase to the file descriptor.
-  len = strlen(gpg.passphrase);
-  if (write(fd, gpg.passphrase, len) != len)
-    return gpg_error(GPG_ERR_CANCELED);
-  if (write(fd, "\n", 1) != 1)
-    return gpg_error(GPG_ERR_CANCELED);
-
-  return 0; // Success
-}
-
-//  gpg_verify(gpg_data, text, *sigsum)
-// Verify that gpg_data is a correct signature for text.
-// Return the key id (or fingerprint), and set *sigsum to
-// the gpgme signature summary value.
-// The returned string must be freed with g_free() after use.
-char *gpg_verify(const char *gpg_data, const char *text,
-                 gpgme_sigsum_t *sigsum)
-{
-  gpgme_ctx_t ctx;
-  gpgme_data_t data_sign, data_text;
-  char *data;
-  char *verified_key = NULL;
-  gpgme_key_t key;
-  gpgme_error_t err;
-  const char prefix[] = "-----BEGIN PGP SIGNATURE-----\n\n";
-  const char suffix[] = "\n-----END PGP SIGNATURE-----\n";
-
-  // Reset the summary.
-  *sigsum = 0;
-
-  if (!gpg.enabled)
-    return NULL;
-
-  err = gpgme_new(&ctx);
-  if (err) {
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME error: %s", gpgme_strerror(err));
-    return NULL;
-  }
-
-  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
-
-  // Surround the given data with the prefix & suffix
-  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
-  strcpy(data, prefix);
-  strcat(data, gpg_data);
-  strcat(data, suffix);
-
-  err = gpgme_data_new_from_mem(&data_sign, data, strlen(data), 0);
-  if (!err) {
-    err = gpgme_data_new_from_mem(&data_text, text, strlen(text), 0);
-    if (!err) {
-      err = gpgme_op_verify(ctx, data_sign, data_text, 0);
-      if (!err) {
-        gpgme_verify_result_t vr = gpgme_op_verify_result(ctx);
-        if (vr && vr->signatures) {
-          char *r = vr->signatures->fpr;
-          // Found the fingerprint.  Let's try to get the key id.
-          if (!gpgme_get_key(ctx, r, &key, 0) && key) {
-            r = key->subkeys->keyid;
-            gpgme_key_release(key);
-          }
-          // r is a static variable, let's copy it.
-          verified_key = g_strdup(r);
-          *sigsum = vr->signatures->summary;
-          // For some reason summary could be 0 when status is 0 too,
-          // which means the signature is valid...
-          if (!*sigsum && !vr->signatures->status)
-            *sigsum = GPGME_SIGSUM_GREEN;
-        }
-      }
-      gpgme_data_release(data_text);
-    }
-    gpgme_data_release(data_sign);
-  }
-  if (err)
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME verification error: %s", gpgme_strerror(err));
-  gpgme_release(ctx);
-  g_free(data);
-  return verified_key;
-}
-
-//  gpg_sign(gpg_data)
-// Return a signature of gpg_data (or NULL).
-// The returned string must be freed with g_free() after use.
-char *gpg_sign(const char *gpg_data)
-{
-  gpgme_ctx_t ctx;
-  gpgme_data_t in, out;
-  char *p;
-  char *signed_data = NULL;
-  size_t nread;
-  gpgme_key_t key;
-  gpgme_error_t err;
-
-  if (!gpg.enabled || !gpg.private_key)
-    return NULL;
-
-  err = gpgme_new(&ctx);
-  if (err) {
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME error: %s", gpgme_strerror(err));
-    return NULL;
-  }
-
-  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
-  gpgme_set_textmode(ctx, 0);
-  gpgme_set_armor(ctx, 1);
-
-  p = getenv("GPG_AGENT_INFO");
-  if (!(p && strchr(p, ':')))
-    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);
-
-  err = gpgme_get_key(ctx, gpg.private_key, &key, 1);
-  if (err || !key) {
-    scr_LogPrint(LPRINT_LOGNORM, "GPGME error: private key not found");
-    gpgme_release(ctx);
-    return NULL;
-  }
-
-  gpgme_signers_clear(ctx);
-  gpgme_signers_add(ctx, key);
-  gpgme_key_release(key);
-  err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
-  if (!err) {
-    err = gpgme_data_new(&out);
-    if (!err) {
-      err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH);
-      if (!err) {
-        signed_data = gpgme_data_release_and_get_mem(out, &nread);
-        if (signed_data) {
-          // We need to add a trailing NULL
-          char *dd = g_strndup(signed_data, nread);
-          free(signed_data);
-          signed_data = strip_header_footer(dd);
-          g_free(dd);
-        }
-      } else {
-        gpgme_data_release(out);
-      }
-    }
-    gpgme_data_release(in);
-  }
-  if (err && err != GPG_ERR_CANCELED)
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME signature error: %s", gpgme_strerror(err));
-  gpgme_release(ctx);
-  return signed_data;
-}
-
-//  gpg_decrypt(gpg_data)
-// Return decrypted gpg_data (or NULL).
-// The returned string must be freed with g_free() after use.
-char *gpg_decrypt(const char *gpg_data)
-{
-  gpgme_ctx_t ctx;
-  gpgme_data_t in, out;
-  char *p, *data;
-  char *decrypted_data = NULL;
-  size_t nread;
-  gpgme_error_t err;
-  const char prefix[] = "-----BEGIN PGP MESSAGE-----\n\n";
-  const char suffix[] = "\n-----END PGP MESSAGE-----\n";
-
-  if (!gpg.enabled)
-    return NULL;
-
-  err = gpgme_new(&ctx);
-  if (err) {
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME error: %s", gpgme_strerror(err));
-    return NULL;
-  }
-
-  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
-
-  p = getenv("GPG_AGENT_INFO");
-  if (!(p && strchr(p, ':')))
-    gpgme_set_passphrase_cb(ctx, passphrase_cb, 0);
-
-  // Surround the given data with the prefix & suffix
-  data = g_new(char, sizeof(prefix) + sizeof(suffix) + strlen(gpg_data));
-  strcpy(data, prefix);
-  strcat(data, gpg_data);
-  strcat(data, suffix);
-
-  err = gpgme_data_new_from_mem(&in, data, strlen(data), 0);
-  if (!err) {
-    err = gpgme_data_new(&out);
-    if (!err) {
-      err = gpgme_op_decrypt(ctx, in, out);
-      if (!err) {
-        decrypted_data = gpgme_data_release_and_get_mem(out, &nread);
-        if (decrypted_data) {
-          // We need to add a trailing NULL
-          char *dd = g_strndup(decrypted_data, nread);
-          free(decrypted_data);
-          decrypted_data = dd;
-        }
-      } else {
-        gpgme_data_release(out);
-      }
-    }
-    gpgme_data_release(in);
-  }
-  if (err && err != GPG_ERR_CANCELED)
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME decryption error: %s", gpgme_strerror(err));
-  gpgme_release(ctx);
-  g_free(data);
-  return decrypted_data;
-}
-
-//  gpg_encrypt(gpg_data, keyid)
-// Return encrypted gpg_data with the key keyid (or NULL).
-// The returned string must be freed with g_free() after use.
-char *gpg_encrypt(const char *gpg_data, const char *keyid)
-{
-  gpgme_ctx_t ctx;
-  gpgme_data_t in, out;
-  char *encrypted_data = NULL, *edata;
-  size_t nread;
-  gpgme_key_t key;
-  gpgme_error_t err;
-
-  if (!gpg.enabled)
-    return NULL;
-
-  err = gpgme_new(&ctx);
-  if (err) {
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME error: %s", gpgme_strerror(err));
-    return NULL;
-  }
-
-  gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP);
-  gpgme_set_textmode(ctx, 0);
-  gpgme_set_armor(ctx, 1);
-
-  err = gpgme_get_key(ctx, keyid, &key, 0);
-  if (!err && key) {
-    gpgme_key_t keys[] = { key, 0 };
-    err = gpgme_data_new_from_mem(&in, gpg_data, strlen(gpg_data), 0);
-    if (!err) {
-      err = gpgme_data_new(&out);
-      if (!err) {
-        err = gpgme_op_encrypt(ctx, keys, GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
-        if (!err)
-          encrypted_data = gpgme_data_release_and_get_mem(out, &nread);
-        else
-          gpgme_data_release(out);
-      }
-      gpgme_data_release(in);
-    }
-    gpgme_key_release(key);
-  } else {
-    scr_LogPrint(LPRINT_LOGNORM, "GPGME encryption error: key not found");
-    err = 0;
-  }
-  if (err && err != GPG_ERR_CANCELED)
-    scr_LogPrint(LPRINT_LOGNORM|LPRINT_NOTUTF8,
-                 "GPGME encryption error: %s", gpgme_strerror(err));
-  gpgme_release(ctx);
-  edata = strip_header_footer(encrypted_data);
-  if (encrypted_data)
-    free(encrypted_data);
-  return edata;
-}
-
-//  gpg_test_passphrase()
-// Test the current gpg.passphrase with gpg.private_key.
-// If the test doesn't succeed, the passphrase is cleared and a non-null
-// value is returned.
-int gpg_test_passphrase(void)
-{
-  char *s;
-
-  if (!gpg.private_key)
-    return -1; // No private key...
-
-  s = gpg_sign("test");
-  if (s) {
-    free(s);
-    return 0; // Ok, test successful
-  }
-  // The passphrase is wrong (if provided)
-  gpg_set_passphrase(NULL);
-  return -1;
-}
-
-int gpg_enabled(void)
-{
-  return gpg.enabled;
-}
-
-#else  /* not HAVE_GPGME */
-
-int gpg_enabled(void)
-{
-  return 0;
-}
-
-#endif /* HAVE_GPGME */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/pgp.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-#ifndef __PGP_H__
-#define __PGP_H__ 1
-
-#include <config.h>
-
-#ifdef HAVE_GPGME
-
-#define GPGME_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_USER_1
-#include <gpgme.h>
-
-int   gpg_init(const char *priv_key, const char *passphrase);
-void  gpg_terminate(void);
-void  gpg_set_passphrase(const char *passphrase);
-void  gpg_set_private_key(const char *priv_keyid);
-char *gpg_verify(const char *gpg_data, const char *text,
-                 gpgme_sigsum_t *sigsum);
-char *gpg_sign(const char *gpg_data);
-char *gpg_decrypt(const char *gpg_data);
-char *gpg_encrypt(const char *gpg_data, const char *keyid);
-
-int   gpg_test_passphrase(void);
-
-#endif /* HAVE_GPGME */
-
-int gpg_enabled(void);
-
-#endif /* __PGP_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/roster.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1624 +0,0 @@
-/*
- * roster.c     -- Local roster implementation
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <string.h>
-
-#include "roster.h"
-#include "utils.h"
-#include "hooks.h"
-
-extern void hlog_save_state(void);
-
-char *strrole[] = {   /* Should match enum in roster.h */
-  "none",
-  "moderator",
-  "participant",
-  "visitor"
-};
-
-char *straffil[] = {  /* Should match enum in roster.h */
-  "none",
-  "owner",
-  "admin",
-  "member",
-  "outcast"
-};
-
-char *strprintstatus[] = {  /* Should match enum in roster.h */
-  "default",
-  "none",
-  "in_and_out",
-  "all"
-};
-
-char *strautowhois[] = {    /* Should match enum in roster.h */
-  "default",
-  "off",
-  "on",
-};
-
-/* Resource structure */
-
-typedef struct {
-  gchar *name;
-  gchar prio;
-  enum imstatus status;
-  gchar *status_msg;
-  time_t status_timestamp;
-  enum imrole role;
-  enum imaffiliation affil;
-  gchar *realjid;       /* for chatrooms, if buddy's real jid is known */
-  guint events;
-  char *caps;
-#ifdef JEP0022
-  struct jep0022 jep22;
-#endif
-#ifdef JEP0085
-  struct jep0085 jep85;
-#endif
-#ifdef HAVE_GPGME
-  struct pgp_data pgpdata;
-#endif
-} res;
-
-/* This is a private structure type for the roster */
-
-typedef struct {
-  gchar *name;
-  gchar *jid;
-  guint type;
-  enum subscr subscription;
-  GSList *resource;
-
-  /* For groupchats */
-  gchar *nickname;
-  gchar *topic;
-  guint inside_room;
-  guint print_status;
-  guint auto_whois;
-
-  /* on_server is TRUE if the item is present on the server roster */
-  guint on_server;
-
-  /* To keep track of last status message */
-  gchar *offline_status_message;
-
-  /* Flag used for the UI */
-  guint flags;
-
-  // list: user -> points to his group; group -> points to its users list
-  GSList *list;
-} roster;
-
-
-/* ### Variables ### */
-
-static guchar display_filter;
-static GSList *groups;
-static GSList *unread_list;
-static GHashTable *unread_jids;
-GList *buddylist;
-GList *current_buddy;
-GList *alternate_buddy;
-
-static roster roster_special;
-
-static int  unread_jid_del(const char *jid);
-
-#define DFILTER_ALL     63
-#define DFILTER_ONLINE  62
-
-
-/* ### Initialization ### */
-
-void roster_init(void)
-{
-  roster_special.name = SPECIAL_BUFFER_STATUS_ID;
-  roster_special.type = ROSTER_TYPE_SPECIAL;
-}
-
-/* ### Resources functions ### */
-
-static inline void free_resource_data(res *p_res)
-{
-  if (!p_res)
-    return;
-  g_free((gchar*)p_res->status_msg);
-  g_free((gchar*)p_res->name);
-  g_free((gchar*)p_res->realjid);
-#ifdef JEP0022
-  g_free(p_res->jep22.last_msgid_sent);
-  g_free(p_res->jep22.last_msgid_rcvd);
-#endif
-#ifdef HAVE_GPGME
-  g_free(p_res->pgpdata.sign_keyid);
-#endif
-  g_free(p_res->caps);
-  g_free(p_res);
-}
-
-static void free_all_resources(GSList **reslist)
-{
-  GSList *lip;
-
-  for (lip = *reslist; lip ; lip = g_slist_next(lip))
-    free_resource_data((res*)lip->data);
-  // Free all nodes but the first (which is static)
-  g_slist_free(*reslist);
-  *reslist = NULL;
-}
-
-// Resources are sorted in ascending order
-static gint resource_compare_prio(res *a, res *b) {
-  //return (a->prio - b->prio);
-  if (a->prio < b->prio) return -1;
-  else                   return 1;
-}
-
-//  get_resource(rost, resname)
-// Return a pointer to the resource with name resname, in rost's resources list
-// - if rost has no resources, return NULL
-// - if resname is defined, return the match or NULL
-// - if resname is NULL, the last resource is returned, currently
-//   This could change in the future, because we should return the best one
-//   (priority? last used? and fall back to the first resource)
-//
-static res *get_resource(roster *rost, const char *resname)
-{
-  GSList *p;
-  res *r = NULL;
-
-  for (p = rost->resource; p; p = g_slist_next(p)) {
-    r = p->data;
-    if (resname && !strcmp(r->name, resname))
-      return r;
-  }
-
-  // The last resource is one of the resources with the highest priority,
-  // however, we don't know if it is the more-recently-used.
-  if (!resname) return r;
-  return NULL;
-}
-
-//  get_or_add_resource(rost, resname, priority)
-// - if there is a "resname" resource in rost's resources, return a pointer
-//   on this resource
-// - if not, add the resource, set the name, and return a pointer on this
-//   new resource
-static res *get_or_add_resource(roster *rost, const char *resname, gchar prio)
-{
-  GSList *p;
-  res *nres;
-
-  if (!resname) return NULL;
-
-  for (p = rost->resource; p; p = g_slist_next(p)) {
-    res *r = p->data;
-    if (!strcmp(r->name, resname)) {
-      if (prio != r->prio) {
-        r->prio = prio;
-        rost->resource = g_slist_sort(rost->resource,
-                                      (GCompareFunc)&resource_compare_prio);
-      }
-      return r;
-    }
-  }
-
-  // Resource not found
-  nres = g_new0(res, 1);
-  nres->name = g_strdup(resname);
-  nres->prio = prio;
-  rost->resource = g_slist_insert_sorted(rost->resource, nres,
-                                         (GCompareFunc)&resource_compare_prio);
-  return nres;
-}
-
-static void del_resource(roster *rost, const char *resname)
-{
-  GSList *p;
-  GSList *p_res_elt = NULL;
-  res *p_res;
-
-  if (!resname) return;
-
-  for (p = rost->resource; p; p = g_slist_next(p)) {
-    res *r = p->data;
-    if (!strcmp(r->name, resname))
-      p_res_elt = p;
-  }
-
-  if (!p_res_elt) return;   // Resource not found
-
-  p_res = p_res_elt->data;
-
-  // Keep a copy of the status message when a buddy goes offline
-  if (g_slist_length(rost->resource) == 1) {
-    g_free(rost->offline_status_message);
-    rost->offline_status_message = p_res->status_msg;
-    p_res->status_msg = NULL;
-  }
-
-  // Free allocations and delete resource node
-  free_resource_data(p_res);
-  rost->resource = g_slist_delete_link(rost->resource, p_res_elt);
-  return;
-}
-
-
-/* ### Roster functions ### */
-
-static inline void free_roster_user_data(roster *roster_usr)
-{
-  if (!roster_usr)
-    return;
-  g_free((gchar*)roster_usr->jid);
-  g_free((gchar*)roster_usr->name);
-  g_free((gchar*)roster_usr->nickname);
-  g_free((gchar*)roster_usr->topic);
-  g_free((gchar*)roster_usr->offline_status_message);
-  free_all_resources(&roster_usr->resource);
-  g_free(roster_usr);
-}
-
-// Comparison function used to search in the roster (compares jids and types)
-static gint roster_compare_jid_type(roster *a, roster *b) {
-  if (! (a->type & b->type))
-    return -1; // arbitrary (but should be != 0, of course)
-  return strcasecmp(a->jid, b->jid);
-}
-
-// Comparison function used to search in the roster (compares names and types)
-static gint roster_compare_name_type(roster *a, roster *b) {
-  if (! (a->type & b->type))
-    return -1; // arbitrary (but should be != 0, of course)
-  return strcmp(a->name, b->name);
-}
-
-// Comparison function used to sort the roster (by name)
-static gint roster_compare_name(roster *a, roster *b) {
-  return strcmp(a->name, b->name);
-}
-
-// Finds a roster element (user, group, agent...), by jid or name
-// If roster_type is 0, returns match of any type.
-// Returns the roster GSList element, or NULL if jid/name not found
-GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type)
-{
-  GSList *sl_roster_elt = groups;
-  GSList *resource;
-  roster sample;
-  GCompareFunc comp;
-
-  if (!jidname) return NULL;
-
-  if (!roster_type)
-    roster_type = ROSTER_TYPE_USER  | ROSTER_TYPE_ROOM |
-                  ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP;
-
-  sample.type = roster_type;
-  if (type == jidsearch) {
-    sample.jid = (gchar*)jidname;
-    comp = (GCompareFunc)&roster_compare_jid_type;
-  } else if (type == namesearch) {
-    sample.name = (gchar*)jidname;
-    comp = (GCompareFunc)&roster_compare_name_type;
-  } else
-    return NULL;    // Should not happen...
-
-  while (sl_roster_elt) {
-    roster *roster_elt = (roster*)sl_roster_elt->data;
-    if (roster_type & ROSTER_TYPE_GROUP) {
-      if ((type == namesearch) && !strcmp(jidname, roster_elt->name))
-        return sl_roster_elt;
-    }
-    resource = g_slist_find_custom(roster_elt->list, &sample, comp);
-    if (resource) return resource;
-    sl_roster_elt = g_slist_next(sl_roster_elt);
-  }
-  return NULL;
-}
-
-// Returns pointer to new group, or existing group with that name
-GSList *roster_add_group(const char *name)
-{
-  roster *roster_grp;
-  GSList *p_group;
-
-  // #1 Check name doesn't already exist
-  p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP);
-  if (!p_group) {
-    // #2 Create the group node
-    roster_grp = g_new0(roster, 1);
-    roster_grp->name = g_strdup(name);
-    roster_grp->type = ROSTER_TYPE_GROUP;
-    // #3 Insert (sorted)
-    groups = g_slist_insert_sorted(groups, roster_grp,
-            (GCompareFunc)&roster_compare_name);
-    p_group = roster_find(name, namesearch, ROSTER_TYPE_GROUP);
-  }
-  return p_group;
-}
-
-// Returns a pointer to the new user, or existing user with that name
-// Note: if onserver is -1, the flag won't be changed.
-GSList *roster_add_user(const char *jid, const char *name, const char *group,
-                        guint type, enum subscr esub, gint onserver)
-{
-  roster *roster_usr;
-  roster *my_group;
-  GSList *slist;
-
-  if ((type != ROSTER_TYPE_USER) &&
-      (type != ROSTER_TYPE_ROOM) &&
-      (type != ROSTER_TYPE_AGENT)) {
-    // XXX Error message?
-    return NULL;
-  }
-
-  // Let's be arbitrary: default group has an empty name ("").
-  if (!group)  group = "";
-
-  // #1 Check this user doesn't already exist
-  slist = roster_find(jid, jidsearch, 0);
-  if (slist) {
-    char *oldgroupname;
-    // That's an update
-    roster_usr = slist->data;
-    roster_usr->subscription = esub;
-    if (onserver >= 0)
-      buddy_setonserverflag(slist->data, onserver);
-    if (name)
-      buddy_setname(slist->data, (char*)name);
-    // Let's check if the group name has changed
-    oldgroupname = ((roster*)((GSList*)roster_usr->list)->data)->name;
-    if (group && strcmp(oldgroupname, group)) {
-      buddy_setgroup(slist->data, (char*)group);
-      // Note: buddy_setgroup() updates the user lists so we cannot
-      // use slist anymore.
-      return roster_find(jid, jidsearch, 0);
-    }
-    return slist;
-  }
-  // #2 add group if necessary
-  slist = roster_add_group(group);
-  if (!slist) return NULL;
-  my_group = (roster*)slist->data;
-  // #3 Create user node
-  roster_usr = g_new0(roster, 1);
-  roster_usr->jid   = g_strdup(jid);
-  if (name) {
-    roster_usr->name  = g_strdup(name);
-  } else {
-    gchar *p, *str = g_strdup(jid);
-    p = strchr(str, JID_RESOURCE_SEPARATOR);
-    if (p)  *p = '\0';
-    roster_usr->name = g_strdup(str);
-    g_free(str);
-  }
-  if (unread_jid_del(jid)) {
-    roster_usr->flags |= ROSTER_FLAG_MSG;
-    // Append the roster_usr to unread_list
-    unread_list = g_slist_append(unread_list, roster_usr);
-  }
-  roster_usr->type = type;
-  roster_usr->subscription = esub;
-  roster_usr->list = slist;    // (my_group SList element)
-  if (onserver == 1)
-    roster_usr->on_server = TRUE;
-  // #4 Insert node (sorted)
-  my_group->list = g_slist_insert_sorted(my_group->list, roster_usr,
-                                         (GCompareFunc)&roster_compare_name);
-  return roster_find(jid, jidsearch, type);
-}
-
-// Removes user (jid) from roster, frees allocated memory
-void roster_del_user(const char *jid)
-{
-  GSList *sl_user, *sl_group;
-  GSList **sl_group_listptr;
-  roster *roster_usr;
-  GSList *node;
-
-  sl_user = roster_find(jid, jidsearch,
-                        ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
-  if (sl_user == NULL)
-    return;
-  roster_usr = (roster*)sl_user->data;
-
-  // Remove (if present) from unread messages list
-  node = g_slist_find(unread_list, roster_usr);
-  if (node) unread_list = g_slist_delete_link(unread_list, node);
-  // If there is a pending unread message, keep track of it
-  if (roster_usr->flags & ROSTER_FLAG_MSG)
-    unread_jid_add(roster_usr->jid);
-
-  sl_group = roster_usr->list;
-
-  // Let's free roster_usr memory (jid, name, status message...)
-  free_roster_user_data(roster_usr);
-
-  // That's a little complex, we need to dereference twice
-  sl_group_listptr = &((roster*)(sl_group->data))->list;
-  *sl_group_listptr = g_slist_delete_link(*sl_group_listptr, sl_user);
-
-  // We need to rebuild the list
-  if (current_buddy)
-    buddylist_build();
-  // TODO What we could do, too, is to check if the deleted node is
-  // current_buddy, in which case we could move current_buddy to the
-  // previous (or next) node.
-}
-
-// Free all roster data and call buddylist_build() to free the buddylist.
-void roster_free(void)
-{
-  GSList *sl_grp = groups;
-
-  // Free unread_list
-  if (unread_list) {
-    g_slist_free(unread_list);
-    unread_list = NULL;
-  }
-
-  // Walk through groups
-  while (sl_grp) {
-    roster *roster_grp = (roster*)sl_grp->data;
-    GSList *sl_usr = roster_grp->list;
-    // Walk through this group users
-    while (sl_usr) {
-      roster *roster_usr = (roster*)sl_usr->data;
-      // If there is a pending unread message, keep track of it
-      if (roster_usr->flags & ROSTER_FLAG_MSG)
-        unread_jid_add(roster_usr->jid);
-      // Free roster_usr data (jid, name, status message...)
-      free_roster_user_data(roster_usr);
-      sl_usr = g_slist_next(sl_usr);
-    }
-    // Free group's users list
-    if (roster_grp->list)
-      g_slist_free(roster_grp->list);
-    // Free group's name and jid
-    g_free((gchar*)roster_grp->jid);
-    g_free((gchar*)roster_grp->name);
-    g_free(roster_grp);
-    sl_grp = g_slist_next(sl_grp);
-  }
-  // Free groups list
-  if (groups) {
-    g_slist_free(groups);
-    groups = NULL;
-    // Update (i.e. free) buddylist
-    if (buddylist)
-      buddylist_build();
-  }
-}
-
-//  roster_setstatus()
-// Note: resname, role, affil and realjid are for room members only
-void roster_setstatus(const char *jid, const char *resname, gchar prio,
-                      enum imstatus bstat, const char *status_msg,
-                      time_t status_time,
-                      enum imrole role, enum imaffiliation affil,
-                      const char *realjid)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-  res *p_res;
-
-  sl_user = roster_find(jid, jidsearch,
-                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
-  // If we can't find it, we add it
-  if (sl_user == NULL)
-    sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER,
-                              sub_none, -1);
-
-  // If there is no resource name, we can leave now
-  if (!resname) return;
-
-  roster_usr = (roster*)sl_user->data;
-
-  // New or updated resource
-  p_res = get_or_add_resource(roster_usr, resname, prio);
-  p_res->status = bstat;
-  if (p_res->status_msg) {
-    g_free((gchar*)p_res->status_msg);
-    p_res->status_msg = NULL;
-  }
-  if (status_msg)
-    p_res->status_msg = g_strdup(status_msg);
-  if (!status_time)
-    time(&status_time);
-  p_res->status_timestamp = status_time;
-
-  p_res->role = role;
-  p_res->affil = affil;
-
-  if (p_res->realjid) {
-    g_free((gchar*)p_res->realjid);
-    p_res->realjid = NULL;
-  }
-  if (realjid)
-    p_res->realjid = g_strdup(realjid);
-
-  // If bstat is offline, we MUST delete the resource, actually
-  if (bstat == offline) {
-    del_resource(roster_usr, resname);
-    return;
-  }
-}
-
-//  roster_setflags()
-// Set one or several flags to value (TRUE/FALSE)
-void roster_setflags(const char *jid, guint flags, guint value)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-
-  sl_user = roster_find(jid, jidsearch,
-                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
-  if (sl_user == NULL)
-    return;
-
-  roster_usr = (roster*)sl_user->data;
-  if (value)
-    roster_usr->flags |= flags;
-  else
-    roster_usr->flags &= ~flags;
-}
-
-//  roster_msg_setflag()
-// Set the ROSTER_FLAG_MSG to the given value for the given jid.
-// It will update the buddy's group message flag.
-// Update the unread messages list too.
-void roster_msg_setflag(const char *jid, guint special, guint value)
-{
-  GSList *sl_user;
-  roster *roster_usr, *roster_grp;
-  int new_roster_item = FALSE;
-  guint unread_list_modified = FALSE;
-
-  if (special) {
-    //sl_user = roster_find(jid, namesearch, ROSTER_TYPE_SPECIAL);
-    //if (!sl_user) return;
-    //roster_usr = (roster*)sl_user->data;
-    roster_usr = &roster_special;
-    if (value) {
-      if (!(roster_usr->flags & ROSTER_FLAG_MSG))
-        unread_list_modified = TRUE;
-      roster_usr->flags |= ROSTER_FLAG_MSG;
-      // Append the roster_usr to unread_list, but avoid duplicates
-      if (!g_slist_find(unread_list, roster_usr))
-        unread_list = g_slist_append(unread_list, roster_usr);
-    } else {
-      if (roster_usr->flags & ROSTER_FLAG_MSG)
-        unread_list_modified = TRUE;
-      roster_usr->flags &= ~ROSTER_FLAG_MSG;
-      if (unread_list) {
-        GSList *node = g_slist_find(unread_list, roster_usr);
-        if (node)
-          unread_list = g_slist_delete_link(unread_list, node);
-      }
-    }
-    goto roster_msg_setflag_return;
-  }
-
-  sl_user = roster_find(jid, jidsearch,
-                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
-  // If we can't find it, we add it
-  if (sl_user == NULL) {
-    sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER, sub_none, -1);
-    new_roster_item = TRUE;
-  }
-
-  roster_usr = (roster*)sl_user->data;
-  roster_grp = (roster*)roster_usr->list->data;
-  if (value) {
-    if (!(roster_usr->flags & ROSTER_FLAG_MSG))
-      unread_list_modified = TRUE;
-    // Message flag is TRUE.  This is easy, we just have to set both flags
-    // to TRUE...
-    roster_usr->flags |= ROSTER_FLAG_MSG;
-    roster_grp->flags |= ROSTER_FLAG_MSG; // group
-    // Append the roster_usr to unread_list, but avoid duplicates
-    if (!g_slist_find(unread_list, roster_usr))
-      unread_list = g_slist_append(unread_list, roster_usr);
-  } else {
-    // Message flag is FALSE.
-    guint msg = FALSE;
-    if (roster_usr->flags & ROSTER_FLAG_MSG)
-      unread_list_modified = TRUE;
-    roster_usr->flags &= ~ROSTER_FLAG_MSG;
-    if (unread_list) {
-      GSList *node = g_slist_find(unread_list, roster_usr);
-      if (node)
-        unread_list = g_slist_delete_link(unread_list, node);
-    }
-    // For the group value we need to watch all buddies in this group;
-    // if one is flagged, then the group will be flagged.
-    // I will re-use sl_user and roster_usr here, as they aren't used
-    // anymore.
-    sl_user = roster_grp->list;
-    while (sl_user) {
-      roster_usr = (roster*)sl_user->data;
-      if (roster_usr->flags & ROSTER_FLAG_MSG) {
-        msg = TRUE;
-        break;
-      }
-      sl_user = g_slist_next(sl_user);
-    }
-    if (!msg)
-      roster_grp->flags &= ~ROSTER_FLAG_MSG;
-    else
-      roster_grp->flags |= ROSTER_FLAG_MSG;
-      // Actually the "else" part is useless, because the group
-      // ROSTER_FLAG_MSG should already be set...
-  }
-
-  if (buddylist && (new_roster_item || !g_list_find(buddylist, roster_usr)))
-    buddylist_build();
-
-roster_msg_setflag_return:
-  if (unread_list_modified) {
-    guint unread_count = g_slist_length(unread_list);
-    hlog_save_state();
-    /* Call external command */
-    hk_ext_cmd("", 'U', (guchar)MIN(255, unread_count), NULL);
-  }
-}
-
-const char *roster_getname(const char *jid)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-
-  sl_user = roster_find(jid, jidsearch,
-                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
-  if (sl_user == NULL)
-    return NULL; // Not in the roster...
-
-  roster_usr = (roster*)sl_user->data;
-  return roster_usr->name;
-}
-
-const char *roster_getnickname(const char *jid)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-
-  sl_user = roster_find(jid, jidsearch,
-                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
-  if (sl_user == NULL)
-    return NULL; // Not in the roster...
-
-  roster_usr = (roster*)sl_user->data;
-  return roster_usr->nickname;
-}
-
-void roster_settype(const char *jid, guint type)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-
-  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
-    return;
-
-  roster_usr = (roster*)sl_user->data;
-  roster_usr->type = type;
-}
-
-enum imstatus roster_getstatus(const char *jid, const char *resname)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-  res *p_res;
-
-  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
-  if (sl_user == NULL)
-    return offline; // Not in the roster, anyway...
-
-  roster_usr = (roster*)sl_user->data;
-  p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->status;
-  return offline;
-}
-
-const char *roster_getstatusmsg(const char *jid, const char *resname)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-  res *p_res;
-
-  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
-  if (sl_user == NULL)
-    return NULL; // Not in the roster, anyway...
-
-  roster_usr = (roster*)sl_user->data;
-  p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->status_msg;
-  return roster_usr->offline_status_message;
-}
-
-guint roster_gettype(const char *jid)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-
-  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
-    return 0;
-
-  roster_usr = (roster*)sl_user->data;
-  return roster_usr->type;
-}
-
-guint roster_getsubscription(const char *jid)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-
-  if ((sl_user = roster_find(jid, jidsearch, 0)) == NULL)
-    return 0;
-
-  roster_usr = (roster*)sl_user->data;
-  return roster_usr->subscription;
-}
-
-//  roster_unsubscribed()
-// We have lost buddy's presence updates; this function clears the status
-// message, sets the buddy offline and frees the resources
-void roster_unsubscribed(const char *jid)
-{
-  GSList *sl_user;
-  roster *roster_usr;
-
-  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
-  if (sl_user == NULL)
-    return;
-
-  roster_usr = (roster*)sl_user->data;
-  free_all_resources(&roster_usr->resource);
-}
-
-
-/* ### BuddyList functions ### */
-
-//  buddylist_set_hide_offline_buddies(hide)
-// "hide" values: 1=hide 0=show_all -1=invert
-void buddylist_set_hide_offline_buddies(int hide)
-{
-  if (hide < 0) {               // NEG   (invert)
-    if (display_filter == DFILTER_ALL)
-      display_filter = DFILTER_ONLINE;
-    else
-      display_filter = DFILTER_ALL;
-  } else if (hide == 0) {       // FALSE (don't hide -- andfo_)
-    display_filter = DFILTER_ALL;
-  } else {                      // TRUE  (hide -- andfo)
-    display_filter = DFILTER_ONLINE;
-  }
-}
-
-int buddylist_isset_filter(void)
-{
-  return (display_filter != DFILTER_ALL);
-}
-
-int buddylist_is_status_filtered(enum imstatus status)
-{
-  return display_filter & (1 << status);
-}
-
-void buddylist_set_filter(guchar filter)
-{
-  display_filter = filter;
-}
-
-guchar buddylist_get_filter(void)
-{
-  return display_filter;
-}
-
-//  buddylist_build()
-// Creates the buddylist from the roster entries.
-void buddylist_build(void)
-{
-  GSList *sl_roster_elt = groups;
-  roster *roster_elt;
-  roster *roster_current_buddy = NULL;
-  roster *roster_alternate_buddy = NULL;
-  int shrunk_group;
-
-  // We need to remember which buddy is selected.
-  if (current_buddy)
-    roster_current_buddy = BUDDATA(current_buddy);
-  current_buddy = NULL;
-  if (alternate_buddy)
-    roster_alternate_buddy = BUDDATA(alternate_buddy);
-  alternate_buddy = NULL;
-
-  // Destroy old buddylist
-  if (buddylist) {
-    g_list_free(buddylist);
-    buddylist = NULL;
-  }
-
-  buddylist = g_list_append(buddylist, &roster_special);
-
-  // Create the new list
-  while (sl_roster_elt) {
-    GSList *sl_roster_usrelt;
-    roster *roster_usrelt;
-    guint pending_group = TRUE;
-    roster_elt = (roster*) sl_roster_elt->data;
-
-    shrunk_group = roster_elt->flags & ROSTER_FLAG_HIDE;
-
-    sl_roster_usrelt = roster_elt->list;
-    while (sl_roster_usrelt) {
-      roster_usrelt = (roster*) sl_roster_usrelt->data;
-
-      // Buddy will be added if either:
-      // - buddy's status matches the display_filter
-      // - buddy has a lock (for example the buddy window is currently open)
-      // - buddy has a pending (non-read) message
-      // - group isn't hidden (shrunk)
-      // - this is the current_buddy
-      if (roster_usrelt == roster_current_buddy ||
-          buddylist_is_status_filtered(buddy_getstatus((gpointer)roster_usrelt,
-                                                       NULL)) ||
-          (buddy_getflags((gpointer)roster_usrelt) &
-               (ROSTER_FLAG_LOCK | ROSTER_FLAG_USRLOCK | ROSTER_FLAG_MSG))) {
-        // This user should be added.  Maybe the group hasn't been added yet?
-        if (pending_group) {
-          // It hasn't been done yet
-          buddylist = g_list_append(buddylist, roster_elt);
-          pending_group = FALSE;
-        }
-        // Add user
-        // XXX Should we add the user if there is a message and
-        //     the group is shrunk? If so, we'd need to check LOCK flag too,
-        //     perhaps...
-        if (!shrunk_group)
-          buddylist = g_list_append(buddylist, roster_usrelt);
-      }
-
-      sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
-    }
-    sl_roster_elt = g_slist_next(sl_roster_elt);
-  }
-
-  // Check if we can find our saved current_buddy...
-  if (roster_current_buddy)
-    current_buddy = g_list_find(buddylist, roster_current_buddy);
-  if (roster_alternate_buddy)
-    alternate_buddy = g_list_find(buddylist, roster_alternate_buddy);
-  // current_buddy initialization
-  if (!current_buddy || (g_list_position(buddylist, current_buddy) == -1))
-    current_buddy = g_list_first(buddylist);
-}
-
-//  buddy_hide_group(roster, hide)
-// "hide" values: 1=hide 0=show_all -1=invert
-void buddy_hide_group(gpointer rosterdata, int hide)
-{
-  roster *roster_usr = rosterdata;
-  if (hide > 0)                     // TRUE   (hide)
-    roster_usr->flags |= ROSTER_FLAG_HIDE;
-  else if (hide < 0)                // NEG    (invert)
-    roster_usr->flags ^= ROSTER_FLAG_HIDE;
-  else                              // FALSE  (don't hide)
-    roster_usr->flags &= ~ROSTER_FLAG_HIDE;
-}
-
-const char *buddy_getjid(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  if (!rosterdata)
-    return NULL;
-  return roster_usr->jid;
-}
-
-//  buddy_setgroup()
-// Change the group of current buddy
-//
-// Note: buddy_setgroup() updates the user lists.
-//
-void buddy_setgroup(gpointer rosterdata, char *newgroupname)
-{
-  roster *roster_usr = rosterdata;
-  GSList **sl_group;
-  GSList *sl_newgroup;
-  roster *my_newgroup;
-
-  // A group has no group :)
-  if (roster_usr->type & ROSTER_TYPE_GROUP) return;
-
-  // Add newgroup if necessary
-  if (!newgroupname)  newgroupname = "";
-  sl_newgroup = roster_add_group(newgroupname);
-  if (!sl_newgroup) return;
-  my_newgroup = (roster*)sl_newgroup->data;
-
-  // Remove the buddy from current group
-  sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list;
-  *sl_group = g_slist_remove(*sl_group, rosterdata);
-
-  // Remove old group if it is empty
-  if (!*sl_group) {
-    roster *roster_grp = (roster*)((GSList*)roster_usr->list)->data;
-    g_free((gchar*)roster_grp->jid);
-    g_free((gchar*)roster_grp->name);
-    g_free(roster_grp);
-    groups = g_slist_remove(groups, roster_grp);
-  }
-
-  // Add the buddy to its new group
-  roster_usr->list = sl_newgroup;    // (my_newgroup SList element)
-  my_newgroup->list = g_slist_insert_sorted(my_newgroup->list, roster_usr,
-                                            (GCompareFunc)&roster_compare_name);
-
-  buddylist_build();
-}
-
-void buddy_setname(gpointer rosterdata, char *newname)
-{
-  roster *roster_usr = rosterdata;
-  GSList **sl_group;
-
-  // TODO For groups, we need to check for unicity
-  // However, renaming a group boils down to moving all its buddies to
-  // another group, so calling this function is not really necessary...
-  if (roster_usr->type & ROSTER_TYPE_GROUP) return;
-
-  if (roster_usr->name) {
-    g_free((gchar*)roster_usr->name);
-    roster_usr->name = NULL;
-  }
-  if (newname)
-    roster_usr->name = g_strdup(newname);
-
-  // We need to resort the group list
-  sl_group = &((roster*)((GSList*)roster_usr->list)->data)->list;
-  *sl_group = g_slist_sort(*sl_group, (GCompareFunc)&roster_compare_name);
-
-  buddylist_build();
-}
-
-const char *buddy_getname(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->name;
-}
-
-//  buddy_setnickname(buddy, newnickname)
-// Only for chatrooms
-void buddy_setnickname(gpointer rosterdata, const char *newname)
-{
-  roster *roster_usr = rosterdata;
-
-  if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return; // XXX Error message?
-
-  if (roster_usr->nickname) {
-    g_free((gchar*)roster_usr->nickname);
-    roster_usr->nickname = NULL;
-  }
-  if (newname)
-    roster_usr->nickname = g_strdup(newname);
-}
-
-const char *buddy_getnickname(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->nickname;
-}
-
-//  buddy_setinsideroom(buddy, inside)
-// Only for chatrooms
-void buddy_setinsideroom(gpointer rosterdata, guint inside)
-{
-  roster *roster_usr = rosterdata;
-
-  if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return;
-
-  roster_usr->inside_room = inside;
-}
-
-guint buddy_getinsideroom(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->inside_room;
-}
-
-//  buddy_settopic(buddy, newtopic)
-// Only for chatrooms
-void buddy_settopic(gpointer rosterdata, const char *newtopic)
-{
-  roster *roster_usr = rosterdata;
-
-  if (!(roster_usr->type & ROSTER_TYPE_ROOM)) return;
-
-  if (roster_usr->topic) {
-    g_free((gchar*)roster_usr->topic);
-    roster_usr->topic = NULL;
-  }
-  if (newtopic)
-    roster_usr->topic = g_strdup(newtopic);
-}
-
-const char *buddy_gettopic(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->topic;
-}
-
-void buddy_setprintstatus(gpointer rosterdata, enum room_printstatus pstatus)
-{
-  roster *roster_usr = rosterdata;
-  roster_usr->print_status = pstatus;
-}
-
-enum room_printstatus buddy_getprintstatus(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->print_status;
-}
-
-void buddy_setautowhois(gpointer rosterdata, enum room_autowhois awhois)
-{
-  roster *roster_usr = rosterdata;
-  roster_usr->auto_whois = awhois;
-}
-
-enum room_autowhois buddy_getautowhois(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->auto_whois;
-}
-
-//  buddy_getgroupname()
-// Returns a pointer on buddy's group name.
-const char *buddy_getgroupname(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-
-  if (roster_usr->type & ROSTER_TYPE_GROUP)
-    return roster_usr->name;
-
-  if (roster_usr->type & ROSTER_TYPE_SPECIAL)
-    return NULL;
-
-  // This is a user
-  return ((roster*)((GSList*)roster_usr->list)->data)->name;
-}
-
-//  buddy_getgroup()
-// Returns a pointer on buddy's group.
-gpointer buddy_getgroup(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-
-  if (roster_usr->type & ROSTER_TYPE_GROUP)
-    return rosterdata;
-
-  if (roster_usr->type & ROSTER_TYPE_SPECIAL)
-    return NULL;
-
-  // This is a user
-  return (gpointer)((GSList*)roster_usr->list)->data;
-}
-
-void buddy_settype(gpointer rosterdata, guint type)
-{
-  roster *roster_usr = rosterdata;
-  roster_usr->type = type;
-}
-
-guint buddy_gettype(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->type;
-}
-
-guint buddy_getsubscription(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->subscription;
-}
-
-enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->status;
-  return offline;
-}
-
-const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->status_msg;
-  return roster_usr->offline_status_message;
-}
-
-time_t buddy_getstatustime(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->status_timestamp;
-  return 0;
-}
-
-gchar buddy_getresourceprio(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->prio;
-  return 0;
-}
-
-guint buddy_resource_getevents(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->events;
-  return ROSTER_EVENT_NONE;
-}
-
-void buddy_resource_setevents(gpointer rosterdata, const char *resname,
-                              guint events)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    p_res->events = events;
-}
-
-char *buddy_resource_getcaps(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->caps;
-  return NULL;
-}
-
-void buddy_resource_setcaps(gpointer rosterdata, const char *resname,
-                            const char *caps)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res) {
-    g_free(p_res->caps);
-    p_res->caps = g_strdup(caps);
-  }
-}
-
-struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname)
-{
-#ifdef JEP0022
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return &p_res->jep22;
-#endif
-  return NULL;
-}
-
-struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname)
-{
-#ifdef JEP0085
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return &p_res->jep85;
-#endif
-  return NULL;
-}
-
-struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname)
-{
-#ifdef HAVE_GPGME
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return &p_res->pgpdata;
-#endif
-  return NULL;
-}
-
-enum imrole buddy_getrole(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->role;
-  return role_none;
-}
-
-enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->affil;
-  return affil_none;
-}
-
-const char *buddy_getrjid(gpointer rosterdata, const char *resname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res)
-    return p_res->realjid;
-  return NULL;
-}
-
-//  buddy_getresources(roster_data)
-// Return a singly-linked-list of resource names
-// Note: the caller should free the list (and data) after use
-// If roster_data is null, the current buddy is selected
-GSList *buddy_getresources(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  GSList *reslist = NULL, *lp;
-
-  if (!roster_usr) {
-    if (!current_buddy) return NULL;
-    roster_usr = BUDDATA(current_buddy);
-  }
-  for (lp = roster_usr->resource; lp; lp = g_slist_next(lp))
-    reslist = g_slist_append(reslist, g_strdup(((res*)lp->data)->name));
-
-  return reslist;
-}
-
-//  buddy_getresources_locale(roster_data)
-// Same as buddy_getresources() but names are converted to user's locale
-// Note: the caller should free the list (and data) after use
-GSList *buddy_getresources_locale(gpointer rosterdata)
-{
-  GSList *reslist, *lp;
-
-  reslist = buddy_getresources(rosterdata);
-  // Convert each item to UI's locale
-  for (lp = reslist; lp; lp = g_slist_next(lp)) {
-    gchar *oldname = lp->data;
-    lp->data = from_utf8(oldname);
-    if (lp->data)
-      g_free(oldname);
-    else
-      lp->data = oldname;
-  }
-  return reslist;
-}
-
-/*
-//  buddy_isresource(roster_data)
-// Return true if there is at least one resource
-// (which means, for a room, that it isn't empty)
-int buddy_isresource(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  if (!roster_usr)
-    return FALSE;
-  if (roster_usr->resource)
-    return TRUE;
-  return FALSE;
-}
-*/
-
-//  buddy_resource_setname(roster_data, oldname, newname)
-// Useful for nickname change in a MUC room
-void buddy_resource_setname(gpointer rosterdata, const char *resname,
-                            const char *newname)
-{
-  roster *roster_usr = rosterdata;
-  res *p_res = get_resource(roster_usr, resname);
-  if (p_res) {
-    if (p_res->name) {
-      g_free((gchar*)p_res->name);
-      p_res->name = NULL;
-    }
-    if (newname)
-      p_res->name = g_strdup(newname);
-  }
-}
-
-//  buddy_del_all_resources()
-// Remove all resources from the specified buddy
-void buddy_del_all_resources(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-
-  while (roster_usr->resource) {
-    res *r = roster_usr->resource->data;
-    del_resource(roster_usr, r->name);
-  }
-}
-
-//  buddy_setflags()
-// Set one or several flags to value (TRUE/FALSE)
-void buddy_setflags(gpointer rosterdata, guint flags, guint value)
-{
-  roster *roster_usr = rosterdata;
-  if (value)
-    roster_usr->flags |= flags;
-  else
-    roster_usr->flags &= ~flags;
-}
-
-guint buddy_getflags(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->flags;
-}
-
-//  buddy_setonserverflag()
-// Set the on_server flag
-void buddy_setonserverflag(gpointer rosterdata, guint onserver)
-{
-  roster *roster_usr = rosterdata;
-  roster_usr->on_server = onserver;
-}
-
-guint buddy_getonserverflag(gpointer rosterdata)
-{
-  roster *roster_usr = rosterdata;
-  return roster_usr->on_server;
-}
-
-//  buddy_search_jid(jid)
-// Look for a buddy with specified jid.
-// Search begins at buddylist; if no match is found in the the buddylist,
-// return NULL;
-GList *buddy_search_jid(const char *jid)
-{
-  GList *buddy;
-  roster *roster_usr;
-
-  if (!buddylist) return NULL;
-
-  for (buddy = buddylist; buddy; buddy = g_list_next(buddy)) {
-    roster_usr = (roster*)buddy->data;
-    if (roster_usr->jid && !strcasecmp(roster_usr->jid, jid))
-      return buddy;
-  }
-  return NULL;
-}
-
-//  buddy_search(string)
-// Look for a buddy whose name or jid contains string.
-// Search begins at current_buddy; if no match is found in the the buddylist,
-// return NULL;
-GList *buddy_search(char *string)
-{
-  GList *buddy = current_buddy;
-  roster *roster_usr;
-  if (!buddylist || !current_buddy) return NULL;
-  for (;;) {
-    gchar *jid_locale, *name_locale;
-    char *found = NULL;
-
-    buddy = g_list_next(buddy);
-    if (!buddy)
-      buddy = buddylist;
-
-    roster_usr = (roster*)buddy->data;
-
-    jid_locale = from_utf8(roster_usr->jid);
-    if (jid_locale) {
-      found = strcasestr(jid_locale, string);
-      g_free(jid_locale);
-      if (found)
-        return buddy;
-    }
-    name_locale = from_utf8(roster_usr->name);
-    if (name_locale) {
-      found = strcasestr(name_locale, string);
-      g_free(name_locale);
-      if (found)
-        return buddy;
-    }
-
-    if (buddy == current_buddy)
-      return NULL; // Back to the beginning, and no match found
-  }
-}
-
-//  foreach_buddy(roster_type, pfunction, param)
-// Call pfunction(buddy, param) for each buddy from the roster with
-// type matching roster_type.
-void foreach_buddy(guint roster_type,
-                   void (*pfunc)(gpointer rosterdata, void *param),
-                   void *param)
-{
-  GSList *sl_roster_elt = groups;
-  roster *roster_elt;
-  GSList *sl_roster_usrelt;
-  roster *roster_usrelt;
-
-  while (sl_roster_elt) {       // group list loop
-    roster_elt = (roster*) sl_roster_elt->data;
-    if (roster_elt->type & ROSTER_TYPE_SPECIAL)
-      continue; // Skip special items
-    sl_roster_usrelt = roster_elt->list;
-    while (sl_roster_usrelt) {  // user list loop
-      roster_usrelt = (roster*) sl_roster_usrelt->data;
-
-      if (roster_usrelt->type & roster_type)
-        pfunc(roster_usrelt, param);
-
-      sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
-    }
-    sl_roster_elt = g_slist_next(sl_roster_elt);
-  }
-}
-
-//  foreach_group_member(group, pfunction, param)
-// Call pfunction(buddy, param) for each buddy in the specified group.
-void foreach_group_member(gpointer groupdata,
-                   void (*pfunc)(gpointer rosterdata, void *param),
-                   void *param)
-{
-  roster *roster_elt;
-  GSList *sl_roster_usrelt;
-  roster *roster_usrelt;
-
-  roster_elt = groupdata;
-
-  if (!(roster_elt->type & ROSTER_TYPE_GROUP))
-    return;
-
-  sl_roster_usrelt = roster_elt->list;
-  while (sl_roster_usrelt) {  // user list loop
-    roster_usrelt = (roster*) sl_roster_usrelt->data;
-
-    pfunc(roster_usrelt, param);
-    sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
-  }
-}
-
-//  compl_list(type)
-// Returns a list of jid's or groups.  (For commands completion)
-// type: ROSTER_TYPE_USER (jid's) or ROSTER_TYPE_GROUP (group names)
-// The list should be freed by the caller after use.
-GSList *compl_list(guint type)
-{
-  GSList *list = NULL;
-  GSList *sl_roster_elt = groups;
-  roster *roster_elt;
-  GSList *sl_roster_usrelt;
-  roster *roster_usrelt;
-
-  while (sl_roster_elt) {       // group list loop
-    roster_elt = (roster*) sl_roster_elt->data;
-
-    if (roster_elt->type & ROSTER_TYPE_SPECIAL)
-      continue; // Skip special items
-
-    if (type == ROSTER_TYPE_GROUP) { // (group names)
-      if (roster_elt->name && *(roster_elt->name))
-        list = g_slist_append(list, from_utf8(roster_elt->name));
-    } else { // ROSTER_TYPE_USER (jid) (or agent, or chatroom...)
-      sl_roster_usrelt = roster_elt->list;
-      while (sl_roster_usrelt) {  // user list loop
-        roster_usrelt = (roster*) sl_roster_usrelt->data;
-
-        if (roster_usrelt->jid)
-          list = g_slist_append(list, from_utf8(roster_usrelt->jid));
-
-        sl_roster_usrelt = g_slist_next(sl_roster_usrelt);
-      }
-    }
-    sl_roster_elt = g_slist_next(sl_roster_elt);
-  }
-
-  return list;
-}
-
-//  unread_msg(rosterdata)
-// Return the next buddy with an unread message.  If the parameter is NULL,
-// return the first buddy with an unread message.
-gpointer unread_msg(gpointer rosterdata)
-{
-  GSList *unread, *next_unread;
-
-  if (!unread_list)
-    return NULL;
-
-  // First unread message
-  if (!rosterdata)
-    return unread_list->data;
-
-  unread = g_slist_find(unread_list, rosterdata);
-  if (!unread)
-    return unread_list->data;
-
-  next_unread = g_slist_next(unread);
-  if (next_unread)
-    return next_unread->data;
-  return unread_list->data;
-}
-
-
-/* ### "unread_jids" functions ###
- *
- * The unread_jids hash table is used to keep track of the buddies with
- * unread messages when a disconnection occurs.
- * When removing a buddy with an unread message from the roster, the
- * jid should be added to the unread_jids table.  When adding a buddy to
- * the roster, we check if (s)he had a pending unread message.
- */
-
-//  unread_jid_add(jid)
-// Add jid to the unread_jids hash table
-void unread_jid_add(const char *jid)
-{
-  if (!unread_jids) {
-    // Initialize unread_jids hash table
-    unread_jids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
-  }
-  // The 2nd unread_jids is an arbitrary non-null pointer:
-  g_hash_table_insert(unread_jids, g_strdup(jid), unread_jids);
-}
-
-//  unread_jid_del(jid)
-// Return TRUE if jid is found in the table (and remove it), FALSE if not
-static int unread_jid_del(const char *jid)
-{
-  if (!unread_jids)
-    return FALSE;
-  return g_hash_table_remove(unread_jids, jid);
-}
-
-// Helper function for unread_jid_get_list()
-static void add_to_unreadjids(gpointer key, gpointer value, gpointer udata)
-{
-  GList **listp = udata;
-  *listp = g_list_append(*listp, key);
-}
-
-//  unread_jid_get_list()
-// Return the JID list.
-// The content of the list should not be modified or freed.
-// The caller should call g_list_free() after use.
-GList *unread_jid_get_list(void)
-{
-  GList *list = NULL;
-
-  if (!unread_jids)
-    return NULL;
-
-  // g_hash_table_get_keys() is only in glib >= 2.14
-  //return g_hash_table_get_keys(unread_jids);
-
-  g_hash_table_foreach(unread_jids, add_to_unreadjids, &list);
-  return list;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/roster.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,246 +0,0 @@
-#ifndef __ROSTER_H__
-#define __ROSTER_H__ 1
-
-#include <glib.h>
-#include <time.h>
-
-#include "pgp.h"
-
-#define SPECIAL_BUFFER_STATUS_ID  "[status]"
-
-enum imstatus {
-    offline,
-    available,
-    freeforchat,
-    dontdisturb,
-    notavail,
-    away,
-    invisible,
-    imstatus_size
-};
-
-extern char imstatus2char[]; // Should match enum above
-
-enum imrole {
-  role_none,
-  role_moderator,
-  role_participant,
-  role_visitor,
-  imrole_size
-};
-
-extern char *strrole[]; // Should match enum above
-
-enum imaffiliation {
-  affil_none,
-  affil_owner,
-  affil_admin,
-  affil_member,
-  affil_outcast,
-  imaffiliation_size
-};
-
-extern char *straffil[]; // Should match enum above
-
-enum subscr {
-  sub_none    = 0,
-  sub_pending = 1,
-  sub_to      = 1 << 1,
-  sub_from    = 1 << 2,
-  sub_both    = sub_to|sub_from,
-  sub_remove  = 1 << 3
-};
-
-enum findwhat {
-  jidsearch,
-  namesearch
-};
-
-extern char *strprintstatus[];
-
-// Note: do not change the ordering as these values are visible
-// to the user (option 'muc_print_status')!
-enum room_printstatus {
-  status_default,
-  status_none,
-  status_in_and_out,
-  status_all
-};
-
-extern char *strautowhois[];
-
-enum room_autowhois {
-  autowhois_default,
-  autowhois_off,
-  autowhois_on
-};
-
-struct role_affil {
-  enum { type_role, type_affil } type;
-  union {
-    enum imrole role;
-    enum imaffiliation affil;
-  } val;
-};
-
-// Roster_type is a set of flags, so values should be 2^n
-#define ROSTER_TYPE_USER    1U
-#define ROSTER_TYPE_GROUP   (1U<<1)
-#define ROSTER_TYPE_AGENT   (1U<<2)
-#define ROSTER_TYPE_ROOM    (1U<<3)
-#define ROSTER_TYPE_SPECIAL (1U<<4)
-
-// Flags:
-#define ROSTER_FLAG_MSG     1U      // Message not read
-#define ROSTER_FLAG_HIDE    (1U<<1) // Group hidden (or buddy window closed)
-#define ROSTER_FLAG_LOCK    (1U<<2) // Node should not be removed from buddylist
-#define ROSTER_FLAG_USRLOCK (1U<<3) // Node should not be removed from buddylist
-// ROSTER_FLAG_LOCAL   (1U<<4) // Buddy not on server's roster  (??)
-
-#define JEP0022
-#define JEP0085
-
-struct jep0022 {
-  guint support;
-  guint last_state_sent;
-  gchar *last_msgid_sent;
-  guint last_state_rcvd;
-  gchar *last_msgid_rcvd;
-};
-struct jep0085 {
-  guint support;
-  guint last_state_sent;
-  guint last_state_rcvd;
-};
-
-enum chatstate_support {
-  CHATSTATES_SUPPORT_UNKNOWN = 0,
-  CHATSTATES_SUPPORT_PROBED,
-  CHATSTATES_SUPPORT_NONE,
-  CHATSTATES_SUPPORT_OK
-};
-
-struct pgp_data {
-  gchar *sign_keyid;  // KeyId used by the contact to sign their presence/msg
-#ifdef HAVE_GPGME
-  gpgme_sigsum_t last_sigsum; // Last signature summary
-#endif
-};
-
-/* Message event and chat state flags */
-#define ROSTER_EVENT_NONE      0U
-/* JEP-22 Message Events */
-#define ROSTER_EVENT_OFFLINE   (1U<<0)
-#define ROSTER_EVENT_DELIVERED (1U<<1)
-#define ROSTER_EVENT_DISPLAYED (1U<<2)
-/* JEP-22 & JEP-85 */
-#define ROSTER_EVENT_COMPOSING (1U<<3)
-/* JEP-85 Chat State Notifications */
-#define ROSTER_EVENT_ACTIVE    (1U<<4)
-#define ROSTER_EVENT_PAUSED    (1U<<5)
-#define ROSTER_EVENT_INACTIVE  (1U<<6)
-#define ROSTER_EVENT_GONE      (1U<<7)
-
-extern GList *buddylist;
-extern GList *current_buddy;
-extern GList *alternate_buddy;
-
-// Macros...
-
-#define BUDDATA(glist_node) ((glist_node)->data)
-#define CURRENT_JID         buddy_getjid(BUDDATA(current_buddy))
-
-// Prototypes...
-void    roster_init(void);
-GSList *roster_add_group(const char *name);
-GSList *roster_add_user(const char *jid, const char *name, const char *group,
-                        guint type, enum subscr esub, gint on_server);
-GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type);
-void    roster_del_user(const char *jid);
-void    roster_free(void);
-void    roster_setstatus(const char *jid, const char *resname, gchar prio,
-                         enum imstatus bstat, const char *status_msg,
-                         time_t timestamp,
-                         enum imrole role, enum imaffiliation affil,
-                         const char *realjid);
-void    roster_setflags(const char *jid, guint flags, guint value);
-void    roster_msg_setflag(const char *jid, guint special, guint value);
-const char *roster_getname(const char *jid);
-const char *roster_getnickname(const char *jid);
-void    roster_settype(const char *jid, guint type);
-enum imstatus roster_getstatus(const char *jid, const char *resname);
-const char   *roster_getstatusmsg(const char *jid, const char *resname);
-guint   roster_gettype(const char *jid);
-guint   roster_getsubscription(const char *jid);
-void    roster_unsubscribed(const char *jid);
-
-void    buddylist_build(void);
-void    buddy_hide_group(gpointer rosterdata, int hide);
-void    buddylist_set_hide_offline_buddies(int hide);
-int     buddylist_isset_filter(void);
-int     buddylist_is_status_filtered(enum imstatus status);
-void    buddylist_set_filter(guchar);
-guchar  buddylist_get_filter(void);
-const char *buddy_getjid(gpointer rosterdata);
-void        buddy_setname(gpointer rosterdata, char *newname);
-const char *buddy_getname(gpointer rosterdata);
-void        buddy_setnickname(gpointer rosterdata, const char *newname);
-const char *buddy_getnickname(gpointer rosterdata);
-void        buddy_setinsideroom(gpointer rosterdata, guint inside);
-guint       buddy_getinsideroom(gpointer rosterdata);
-void        buddy_settopic(gpointer rosterdata, const char *newtopic);
-const char *buddy_gettopic(gpointer rosterdata);
-void    buddy_setprintstatus(gpointer rosterdata, enum room_printstatus);
-enum room_printstatus buddy_getprintstatus(gpointer rosterdata);
-void    buddy_setautowhois(gpointer rosterdata, enum room_autowhois);
-enum room_autowhois buddy_getautowhois(gpointer rosterdata);
-void    buddy_settype(gpointer rosterdata, guint type);
-guint   buddy_gettype(gpointer rosterdata);
-guint   buddy_getsubscription(gpointer rosterdata);
-void    buddy_setgroup(gpointer rosterdata, char *newgroupname);
-const char *buddy_getgroupname(gpointer rosterdata);
-gpointer buddy_getgroup(gpointer rosterdata);
-enum imstatus buddy_getstatus(gpointer rosterdata, const char *resname);
-const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname);
-time_t  buddy_getstatustime(gpointer rosterdata, const char *resname);
-gchar   buddy_getresourceprio(gpointer rosterdata, const char *resname);
-//int   buddy_isresource(gpointer rosterdata);
-GSList *buddy_getresources(gpointer rosterdata);
-GSList *buddy_getresources_locale(gpointer rosterdata);
-void    buddy_resource_setname(gpointer rosterdata, const char *resname,
-                               const char *newname);
-void    buddy_resource_setevents(gpointer rosterdata, const char *resname,
-                                 guint event);
-guint   buddy_resource_getevents(gpointer rosterdata, const char *resname);
-void    buddy_resource_setcaps(gpointer rosterdata, const char *resname,
-                               const char *caps);
-char   *buddy_resource_getcaps(gpointer rosterdata, const char *resname);
-struct jep0022 *buddy_resource_jep22(gpointer rosterdata, const char *resname);
-struct jep0085 *buddy_resource_jep85(gpointer rosterdata, const char *resname);
-struct pgp_data *buddy_resource_pgp(gpointer rosterdata, const char *resname);
-enum imrole buddy_getrole(gpointer rosterdata, const char *resname);
-enum imaffiliation buddy_getaffil(gpointer rosterdata, const char *resname);
-const char *buddy_getrjid(gpointer rosterdata, const char *resname);
-void    buddy_del_all_resources(gpointer rosterdata);
-void    buddy_setflags(gpointer rosterdata, guint flags, guint value);
-guint   buddy_getflags(gpointer rosterdata);
-void    buddy_setonserverflag(gpointer rosterdata, guint onserver);
-guint   buddy_getonserverflag(gpointer rosterdata);
-GList  *buddy_search_jid(const char *jid);
-GList  *buddy_search(char *string);
-void    foreach_buddy(guint roster_type,
-                      void (*pfunc)(gpointer rosterdata, void *param),
-                      void *param);
-void    foreach_group_member(gpointer groupdata,
-                             void (*pfunc)(gpointer rosterdata, void *param),
-                             void *param);
-gpointer unread_msg(gpointer rosterdata);
-
-void   unread_jid_add(const char *jid);
-GList *unread_jid_get_list(void);
-
-GSList *compl_list(guint type);
-
-#endif /* __ROSTER_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/screen.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4124 +0,0 @@
-/*
- * screen.c     -- UI stuff
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- * Parts of this file come from the Cabber project <cabber@ajmacias.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include <ctype.h>
-
-#include <config.h>
-#include <locale.h>
-#include <assert.h>
-#ifdef USE_SIGWINCH
-# include <sys/ioctl.h>
-# include <termios.h>
-# include <unistd.h>
-#endif
-
-#ifdef HAVE_LOCALCHARSET_H
-# include <localcharset.h>
-#else
-# include <langinfo.h>
-#endif
-
-#ifdef WITH_ENCHANT
-# include <enchant.h>
-#endif
-
-#ifdef WITH_ASPELL
-# include <aspell.h>
-#endif
-
-#include "screen.h"
-#include "utf8.h"
-#include "hbuf.h"
-#include "commands.h"
-#include "compl.h"
-#include "roster.h"
-#include "histolog.h"
-#include "settings.h"
-#include "utils.h"
-#include "xmpp.h"
-#include "main.h"
-
-#define get_color(col)  (COLOR_PAIR(col)|COLOR_ATTRIB[col])
-#define compose_color(col)  (COLOR_PAIR(col->color_pair)|col->color_attrib)
-
-#define DEFAULT_LOG_WIN_HEIGHT (5+2)
-#define DEFAULT_ROSTER_WIDTH    24
-#define CHAT_WIN_HEIGHT (maxY-1-Log_Win_Height)
-
-const char *LocaleCharSet = "C";
-
-static unsigned short int Log_Win_Height;
-static unsigned short int Roster_Width;
-
-static inline void check_offset(int);
-static void scr_cancel_current_completion(void);
-static void scr_end_current_completion(void);
-static void scr_insert_text(const char*);
-static void scr_handle_tab(void);
-
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-static void spellcheck(char *, char *);
-#endif
-
-static GHashTable *winbufhash;
-
-typedef struct {
-  GList  *hbuf;
-  GList  *top;     // If top is NULL, we'll display the last lines
-  char    cleared; // For ex, user has issued a /clear command...
-  char    lock;
-} buffdata;
-
-typedef struct {
-  WINDOW *win;
-  PANEL  *panel;
-  buffdata *bd;
-} winbuf;
-
-struct dimensions {
-  int l;
-  int c;
-};
-
-static WINDOW *rosterWnd, *chatWnd, *activechatWnd, *inputWnd, *logWnd;
-static WINDOW *mainstatusWnd, *chatstatusWnd;
-static PANEL *rosterPanel, *chatPanel, *activechatPanel, *inputPanel;
-static PANEL *mainstatusPanel, *chatstatusPanel;
-static PANEL *logPanel;
-static int maxY, maxX;
-static int prev_chatwidth;
-static winbuf *statusWindow;
-static winbuf *currentWindow;
-static GList  *statushbuf;
-
-static int roster_hidden;
-static int chatmode;
-static int multimode;
-static char *multiline, *multimode_subj;
-int update_roster;
-int utf8_mode = 0;
-static bool Curses;
-static bool log_win_on_top;
-static bool roster_win_on_right;
-static time_t LastActivity;
-
-static char       inputLine[INPUTLINE_LENGTH+1];
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-static char       maskLine[INPUTLINE_LENGTH+1];
-#endif
-static char      *ptr_inputline;
-static short int  inputline_offset;
-static int    completion_started;
-static GList *cmdhisto;
-static GList *cmdhisto_cur;
-static guint  cmdhisto_nblines;
-static char   cmdhisto_backup[INPUTLINE_LENGTH+1];
-
-static int    chatstate; /* (0=active, 1=composing, 2=paused) */
-static bool   lock_chatstate;
-static time_t chatstate_timestamp;
-static guint  chatstate_timeout_id = 0;
-int chatstates_disabled;
-
-#define MAX_KEYSEQ_LENGTH 8
-
-typedef struct {
-  char *seqstr;
-  guint mkeycode;
-  gint  value;
-} keyseq;
-
-#ifdef HAVE_GLIB_REGEX
-static GRegex *url_regex;
-#endif
-
-GSList *keyseqlist;
-static void add_keyseq(char *seqstr, guint mkeycode, gint value);
-
-void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp,
-                       unsigned int prefix_flags, int force_show,
-                       unsigned mucnicklen, gpointer xep184);
-
-void scr_WriteMessage(const char *bjid, const char *text,
-                      time_t timestamp, guint prefix_flags,
-                      unsigned mucnicklen, gpointer xep184);
-
-inline void scr_UpdateBuddyWindow(void);
-inline void scr_set_chatmode(int enable);
-
-#define SPELLBADCHAR 5
-
-#ifdef WITH_ENCHANT
-EnchantBroker *spell_broker;
-EnchantDict *spell_checker;
-#endif
-
-#ifdef WITH_ASPELL
-AspellConfig *spell_config;
-AspellSpeller *spell_checker;
-#endif
-
-typedef struct {
-	int color_pair;
-	int color_attrib;
-} ccolor;
-
-typedef struct {
-  char *status, *wildcard;
-  ccolor *color;
-  GPatternSpec *compiled;
-} rostercolor;
-
-static GSList *rostercolrules = NULL;
-
-static GHashTable *muccolors = NULL, *nickcolors = NULL;
-
-typedef struct {
-  bool manual; // Manually set?
-  ccolor *color;
-} nickcolor;
-
-static int nickcolcount = 0;
-static ccolor ** nickcols = NULL;
-static muccoltype glob_muccol = MC_OFF;
-
-/* Functions */
-
-static int FindColor(const char *name)
-{
-  int result;
-
-  if (!strcmp(name, "default"))
-    return -1;
-  if (!strcmp(name, "black"))
-    return COLOR_BLACK;
-  if (!strcmp(name, "red"))
-    return COLOR_RED;
-  if (!strcmp(name, "green"))
-    return COLOR_GREEN;
-  if (!strcmp(name, "yellow"))
-    return COLOR_YELLOW;
-  if (!strcmp(name, "blue"))
-    return COLOR_BLUE;
-  if (!strcmp(name, "magenta"))
-    return COLOR_MAGENTA;
-  if (!strcmp(name, "cyan"))
-    return COLOR_CYAN;
-  if (!strcmp(name, "white"))
-    return COLOR_WHITE;
-
-  // Directly support 256-color values
-  result = atoi(name);
-  if (result > 0 && result < COLORS)
-    return result;
-
-  scr_LogPrint(LPRINT_LOGNORM, "ERROR: Wrong color: %s", name);
-  return -1;
-}
-
-static ccolor *get_user_color(const char *color)
-{
-  bool isbright = FALSE;
-  int cl;
-  ccolor *ccol;
-  if (!strncmp(color, "bright", 6)) {
-    isbright = TRUE;
-    color += 6;
-  }
-  cl = FindColor(color);
-  if (cl < 0)
-    return NULL;
-  ccol = g_new0(ccolor, 1);
-  ccol->color_attrib = isbright ? A_BOLD : A_NORMAL;
-  ccol->color_pair = cl + COLOR_max; //user colors come after the internal ones
-  return ccol;
-}
-
-static void ensure_string_htable(GHashTable **table,
-    GDestroyNotify value_destroy_func)
-{
-  if (*table)//Have it already
-    return;
-  *table = g_hash_table_new_full(g_str_hash, g_str_equal,
-      g_free, value_destroy_func);
-}
-
-// Sets the coloring mode for given MUC
-// The MUC room does not need to be in the roster at that time
-// muc - the JID of room
-// type - the new type
-void scr_MucColor(const char *muc, muccoltype type)
-{
-  gchar *muclow = g_utf8_strdown(muc, -1);
-  if (type == MC_REMOVE) {//Remove it
-    if (strcmp(muc, "*")) {
-      if (muccolors && g_hash_table_lookup(muccolors, muclow))
-        g_hash_table_remove(muccolors, muclow);
-    } else {
-      scr_LogPrint(LPRINT_NORMAL, "Can not remove global coloring mode");
-    }
-    g_free(muclow);
-  } else {//Add or overwrite
-    if (strcmp(muc, "*")) {
-      muccoltype *value = g_new(muccoltype, 1);
-      *value = type;
-      ensure_string_htable(&muccolors, g_free);
-      g_hash_table_replace(muccolors, muclow, value);
-    } else {
-      glob_muccol = type;
-      g_free(muclow);
-    }
-  }
-  //Need to redraw?
-  if (chatmode &&
-      ((buddy_search_jid(muc) == current_buddy) || !strcmp(muc, "*")))
-    scr_UpdateBuddyWindow();
-}
-
-// Sets the color for nick in MUC
-// If color is "-", the color is marked as automaticly assigned and is
-// not used if the room is in the "preset" mode
-void scr_MucNickColor(const char *nick, const char *color)
-{
-  char *snick, *mnick;
-  bool need_update = FALSE;
-  snick = g_strdup_printf("<%s>", nick);
-  mnick = g_strdup_printf("*%s ", nick);
-  if (!strcmp(color, "-")) {//Remove the color
-    if (nickcolors) {
-      nickcolor *nc = g_hash_table_lookup(nickcolors, snick);
-      if (nc) {//Have this nick already
-        nc->manual = FALSE;
-        nc = g_hash_table_lookup(nickcolors, mnick);
-        assert(nc);//Must have both at the same time
-        nc->manual = FALSE;
-      }// Else -> no color saved, nothing to delete
-    }
-    g_free(snick);//They are not saved in the hash
-    g_free(mnick);
-    need_update = TRUE;
-  } else {
-    ccolor *cl = get_user_color(color);
-    if (!cl) {
-      scr_LogPrint(LPRINT_NORMAL, "No such color name");
-      g_free(snick);
-      g_free(mnick);
-    } else {
-      nickcolor *nc = g_new(nickcolor, 1);
-      ensure_string_htable(&nickcolors, NULL);
-      nc->manual = TRUE;
-      nc->color = cl;
-      //Free the struct, if any there already
-      g_free(g_hash_table_lookup(nickcolors, mnick));
-      //Save the new ones
-      g_hash_table_replace(nickcolors, mnick, nc);
-      g_hash_table_replace(nickcolors, snick, nc);
-      need_update = TRUE;
-    }
-  }
-  if (need_update && chatmode &&
-      (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM))
-    scr_UpdateBuddyWindow();
-}
-
-static void free_rostercolrule(rostercolor *col)
-{
-  g_free(col->status);
-  g_free(col->wildcard);
-  g_free(col->color);
-  g_pattern_spec_free(col->compiled);
-  g_free(col);
-}
-
-// Removes all roster coloring rules
-void scr_RosterClearColor(void)
-{
-  GSList *head;
-  for (head = rostercolrules; head; head = g_slist_next(head)) {
-    free_rostercolrule(head->data);
-  }
-  g_slist_free(rostercolrules);
-  rostercolrules = NULL;
-}
-
-// Adds, modifies or removes roster coloring rule
-// color set to "-" removes the rule,
-// otherwise it is modified (if exists) or added
-//
-// Returns weather it was successfull (therefore the roster should be
-// redrawed) or not. If it failed, for example because of invalid color
-// name, it also prints the error.
-bool scr_RosterColor(const char *status, const char *wildcard,
-                     const char *color)
-{
-  GSList *head;
-  GSList *found = NULL;
-  for (head = rostercolrules; head; head = g_slist_next(head)) {
-    rostercolor *rc = head->data;
-    if ((!strcmp(status, rc->status)) && (!strcmp(wildcard, rc->wildcard))) {
-      found = head;
-      break;
-    }
-  }
-  if (!strcmp(color,"-")) {//Delete the rule
-    if (found) {
-      free_rostercolrule(found->data);
-      rostercolrules = g_slist_delete_link(rostercolrules, found);
-      return TRUE;
-    } else {
-      scr_LogPrint(LPRINT_NORMAL, "No such color rule, nothing removed");
-      return FALSE;
-    }
-  } else {
-    ccolor *cl = get_user_color(color);
-    if (!cl) {
-      scr_LogPrint(LPRINT_NORMAL, "No such color name");
-      return FALSE;
-    }
-    if (found) {
-      rostercolor *rc = found->data;
-			g_free(rc->color);
-      rc->color = cl;
-    } else {
-      rostercolor *rc = g_new(rostercolor, 1);
-      rc->status = g_strdup(status);
-      rc->wildcard = g_strdup(wildcard);
-      rc->compiled = g_pattern_spec_new(wildcard);
-      rc->color = cl;
-      rostercolrules = g_slist_prepend(rostercolrules, rc);
-    }
-    return TRUE;
-  }
-}
-
-static void ParseColors(void)
-{
-  const char *colors[] = {
-    "", "",
-    "general",
-    "msgout",
-    "msghl",
-    "status",
-    "roster",
-    "rostersel",
-    "rosterselmsg",
-    "rosternewmsg",
-    "info",
-    "msgin",
-    NULL
-  };
-
-  const char *color;
-  const char *background   = settings_opt_get("color_background");
-  const char *backselected = settings_opt_get("color_bgrostersel");
-  const char *backstatus   = settings_opt_get("color_bgstatus");
-  char *tmp;
-  int i;
-
-  // Initialize color attributes
-  memset(COLOR_ATTRIB, 0, sizeof(COLOR_ATTRIB));
-
-  // Default values
-  if (!background)   background   = "black";
-  if (!backselected) backselected = "cyan";
-  if (!backstatus)   backstatus   = "blue";
-
-  for (i=0; colors[i]; i++) {
-    tmp = g_strdup_printf("color_%s", colors[i]);
-    color = settings_opt_get(tmp);
-    g_free(tmp);
-
-    if (color) {
-      if (!strncmp(color, "bright", 6)) {
-        COLOR_ATTRIB[i+1] = A_BOLD;
-        color += 6;
-      }
-    }
-
-    switch (i + 1) {
-      case 1:
-          init_pair(1, COLOR_BLACK, COLOR_WHITE);
-          break;
-      case 2:
-          init_pair(2, COLOR_WHITE, COLOR_BLACK);
-          break;
-      case COLOR_GENERAL:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
-                    FindColor(background));
-          break;
-      case COLOR_MSGOUT:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_CYAN),
-                    FindColor(background));
-          break;
-      case COLOR_MSGHL:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_YELLOW),
-                    FindColor(background));
-          break;
-      case COLOR_STATUS:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
-                    FindColor(backstatus));
-          break;
-      case COLOR_ROSTER:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_GREEN),
-                    FindColor(background));
-          break;
-      case COLOR_ROSTERSEL:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_BLUE),
-                    FindColor(backselected));
-          break;
-      case COLOR_ROSTERSELNMSG:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED),
-                    FindColor(backselected));
-          break;
-      case COLOR_ROSTERNMSG:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_RED),
-                    FindColor(background));
-          break;
-      case COLOR_INFO:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
-                    FindColor(background));
-          break;
-      case COLOR_MSGIN:
-          init_pair(i+1, ((color) ? FindColor(color) : COLOR_WHITE),
-                    FindColor(background));
-          break;
-    }
-  }
-  for (i = COLOR_max; i < (COLOR_max + COLORS); i++)
-    init_pair(i, i-COLOR_max, FindColor(background));
-
-  if (!nickcols) {
-    char *ncolors = g_strdup(settings_opt_get("nick_colors"));
-    if (ncolors) {
-      char *ncolor_start, *ncolor_end;
-      ncolor_start = ncolor_end = ncolors;
-
-      while (*ncolor_end)
-        ncolor_end++;
-
-      while (ncolors < ncolor_end && *ncolors) {
-        if ((*ncolors == ' ') || (*ncolors == '\t')) {
-          ncolors++;
-        } else {
-          char *end = ncolors;
-          ccolor *cl;
-          while (*end && (*end != ' ') && (*end != '\t'))
-            end++;
-          *end = '\0';
-          cl = get_user_color(ncolors);
-          if (!cl) {
-            scr_LogPrint(LPRINT_NORMAL, "Unknown color %s", ncolors);
-          } else {
-            nickcols = g_realloc(nickcols, (++nickcolcount) * sizeof *nickcols);
-            nickcols[nickcolcount-1] = cl;
-          }
-          ncolors = end+1;
-        }
-      }
-      g_free(ncolor_start);
-    }
-    if (!nickcols) {//Fallback to have something
-      nickcolcount = 1;
-			nickcols = g_new(ccolor*, 1);
-			*nickcols = g_new(ccolor, 1);
-      (*nickcols)->color_pair = COLOR_GENERAL;
-      (*nickcols)->color_attrib = A_NORMAL;
-    }
-  }
-}
-
-static void init_keycodes(void)
-{
-  add_keyseq("O5A", MKEY_EQUIV, 521); // Ctrl-Up
-  add_keyseq("O5B", MKEY_EQUIV, 514); // Ctrl-Down
-  add_keyseq("O5C", MKEY_EQUIV, 518); // Ctrl-Right
-  add_keyseq("O5D", MKEY_EQUIV, 516); // Ctrl-Left
-  add_keyseq("O6A", MKEY_EQUIV, 520); // Shift-Up
-  add_keyseq("O6B", MKEY_EQUIV, 513); // Shift-Down
-  add_keyseq("O6C", MKEY_EQUIV, 402); // Shift-Right
-  add_keyseq("O6D", MKEY_EQUIV, 393); // Shift-Left
-  add_keyseq("O2A", MKEY_EQUIV, 520); // Shift-Up
-  add_keyseq("O2B", MKEY_EQUIV, 513); // Shift-Down
-  add_keyseq("O2C", MKEY_EQUIV, 402); // Shift-Right
-  add_keyseq("O2D", MKEY_EQUIV, 393); // Shift-Left
-  add_keyseq("[5^", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
-  add_keyseq("[6^", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
-  add_keyseq("[5@", MKEY_CTRL_SHIFT_PGUP, 0);   // Ctrl-Shift-PageUp
-  add_keyseq("[6@", MKEY_CTRL_SHIFT_PGDOWN, 0); // Ctrl-Shift-PageDown
-  add_keyseq("[7@", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
-  add_keyseq("[8@", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
-  add_keyseq("[8^", MKEY_CTRL_END, 0);  // Ctrl-End
-  add_keyseq("[7^", MKEY_CTRL_HOME, 0); // Ctrl-Home
-  add_keyseq("[2^", MKEY_CTRL_INS, 0);  // Ctrl-Insert
-  add_keyseq("[3^", MKEY_CTRL_DEL, 0);  // Ctrl-Delete
-
-  // Xterm
-  add_keyseq("[1;5A", MKEY_EQUIV, 521); // Ctrl-Up
-  add_keyseq("[1;5B", MKEY_EQUIV, 514); // Ctrl-Down
-  add_keyseq("[1;5C", MKEY_EQUIV, 518); // Ctrl-Right
-  add_keyseq("[1;5D", MKEY_EQUIV, 516); // Ctrl-Left
-  add_keyseq("[1;6A", MKEY_EQUIV, 520); // Ctrl-Shift-Up
-  add_keyseq("[1;6B", MKEY_EQUIV, 513); // Ctrl-Shift-Down
-  add_keyseq("[1;6C", MKEY_EQUIV, 402); // Ctrl-Shift-Right
-  add_keyseq("[1;6D", MKEY_EQUIV, 393); // Ctrl-Shift-Left
-  add_keyseq("[1;6H", MKEY_CTRL_SHIFT_HOME, 0); // Ctrl-Shift-Home
-  add_keyseq("[1;6F", MKEY_CTRL_SHIFT_END, 0);  // Ctrl-Shift-End
-  add_keyseq("[1;2A", MKEY_EQUIV, 521); // Shift-Up
-  add_keyseq("[1;2B", MKEY_EQUIV, 514); // Shift-Down
-  add_keyseq("[5;5~", MKEY_CTRL_PGUP, 0);   // Ctrl-PageUp
-  add_keyseq("[6;5~", MKEY_CTRL_PGDOWN, 0); // Ctrl-PageDown
-  add_keyseq("[1;5F", MKEY_CTRL_END, 0);  // Ctrl-End
-  add_keyseq("[1;5H", MKEY_CTRL_HOME, 0); // Ctrl-Home
-  add_keyseq("[2;5~", MKEY_CTRL_INS, 0);  // Ctrl-Insert
-  add_keyseq("[3;5~", MKEY_CTRL_DEL, 0);  // Ctrl-Delete
-
-  // PuTTY
-  add_keyseq("[A", MKEY_EQUIV, 521); // Ctrl-Up
-  add_keyseq("[B", MKEY_EQUIV, 514); // Ctrl-Down
-  add_keyseq("[C", MKEY_EQUIV, 518); // Ctrl-Right
-  add_keyseq("[D", MKEY_EQUIV, 516); // Ctrl-Left
-
-  // screen
-  add_keyseq("Oa", MKEY_EQUIV, 521); // Ctrl-Up
-  add_keyseq("Ob", MKEY_EQUIV, 514); // Ctrl-Down
-  add_keyseq("Oc", MKEY_EQUIV, 518); // Ctrl-Right
-  add_keyseq("Od", MKEY_EQUIV, 516); // Ctrl-Left
-  add_keyseq("[a", MKEY_EQUIV, 520); // Shift-Up
-  add_keyseq("[b", MKEY_EQUIV, 513); // Shift-Down
-  add_keyseq("[c", MKEY_EQUIV, 402); // Shift-Right
-  add_keyseq("[d", MKEY_EQUIV, 393); // Shift-Left
-  add_keyseq("[5$", MKEY_SHIFT_PGUP, 0);   // Shift-PageUp
-  add_keyseq("[6$", MKEY_SHIFT_PGDOWN, 0); // Shift-PageDown
-
-  // VT100
-  add_keyseq("[H", MKEY_EQUIV, KEY_HOME); // Home
-  add_keyseq("[F", MKEY_EQUIV, KEY_END);  // End
-
-  // Konsole Linux
-  add_keyseq("[1~", MKEY_EQUIV, KEY_HOME); // Home
-  add_keyseq("[4~", MKEY_EQUIV, KEY_END);  // End
-}
-
-//  scr_init_bindings()
-// Create default key bindings
-// Return 0 if error and 1 if none
-void scr_init_bindings(void)
-{
-  GString *sbuf = g_string_new("");
-
-  // Common backspace key codes: 8, 127
-  settings_set(SETTINGS_TYPE_BINDING, "8", "iline char_bdel");    // Ctrl-h
-  settings_set(SETTINGS_TYPE_BINDING, "127", "iline char_bdel");
-  g_string_printf(sbuf, "%d", KEY_BACKSPACE);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_bdel");
-  g_string_printf(sbuf, "%d", KEY_DC);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline char_fdel");
-  g_string_printf(sbuf, "%d", KEY_LEFT);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline bchar");
-  g_string_printf(sbuf, "%d", KEY_RIGHT);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline fchar");
-  settings_set(SETTINGS_TYPE_BINDING, "7", "iline compl_cancel"); // Ctrl-g
-  g_string_printf(sbuf, "%d", KEY_UP);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
-               "iline hist_beginning_search_bwd");
-  g_string_printf(sbuf, "%d", KEY_DOWN);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str,
-               "iline hist_beginning_search_fwd");
-  g_string_printf(sbuf, "%d", KEY_PPAGE);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster up");
-  g_string_printf(sbuf, "%d", KEY_NPAGE);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "roster down");
-  g_string_printf(sbuf, "%d", KEY_HOME);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_start");
-  settings_set(SETTINGS_TYPE_BINDING, "1", "iline iline_start");  // Ctrl-a
-  g_string_printf(sbuf, "%d", KEY_END);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_end");
-  settings_set(SETTINGS_TYPE_BINDING, "5", "iline iline_end");    // Ctrl-e
-  // Ctrl-o (accept-line-and-down-history):
-  settings_set(SETTINGS_TYPE_BINDING, "15", "iline iline_accept_down_hist");
-  settings_set(SETTINGS_TYPE_BINDING, "21", "iline iline_bdel");  // Ctrl-u
-  g_string_printf(sbuf, "%d", KEY_EOL);
-  settings_set(SETTINGS_TYPE_BINDING, sbuf->str, "iline iline_fdel");
-  settings_set(SETTINGS_TYPE_BINDING, "11", "iline iline_fdel");  // Ctrl-k
-  settings_set(SETTINGS_TYPE_BINDING, "16", "buffer up");         // Ctrl-p
-  settings_set(SETTINGS_TYPE_BINDING, "14", "buffer down");       // Ctrl-n
-  settings_set(SETTINGS_TYPE_BINDING, "20", "iline char_swap");   // Ctrl-t
-  settings_set(SETTINGS_TYPE_BINDING, "23", "iline word_bdel");   // Ctrl-w
-  settings_set(SETTINGS_TYPE_BINDING, "M98", "iline bword");      // Meta-b
-  settings_set(SETTINGS_TYPE_BINDING, "M102", "iline fword");     // Meta-f
-  settings_set(SETTINGS_TYPE_BINDING, "M100", "iline word_fdel"); // Meta-d
-  // Ctrl-Left  (2 codes):
-  settings_set(SETTINGS_TYPE_BINDING, "515", "iline bword");
-  settings_set(SETTINGS_TYPE_BINDING, "516", "iline bword");
-  // Ctrl-Right (2 codes):
-  settings_set(SETTINGS_TYPE_BINDING, "517", "iline fword");
-  settings_set(SETTINGS_TYPE_BINDING, "518", "iline fword");
-  settings_set(SETTINGS_TYPE_BINDING, "12", "screen_refresh");    // Ctrl-l
-  settings_set(SETTINGS_TYPE_BINDING, "27", "chat_disable --show-roster");// Esc
-  settings_set(SETTINGS_TYPE_BINDING, "M27", "chat_disable");     // Esc-Esc
-  settings_set(SETTINGS_TYPE_BINDING, "4", "iline send_multiline"); // Ctrl-d
-  settings_set(SETTINGS_TYPE_BINDING, "M117", "iline word_upcase"); // Meta-u
-  settings_set(SETTINGS_TYPE_BINDING, "M108", "iline word_downcase"); // Meta-l
-  settings_set(SETTINGS_TYPE_BINDING, "M99", "iline word_capit"); // Meta-c
-
-  settings_set(SETTINGS_TYPE_BINDING, "265", "help"); // Bind F1 to help...
-
-  g_string_free(sbuf, TRUE);
-}
-
-//  is_speckey(key)
-// Return TRUE if key is a special code, i.e. no char should be displayed on
-// the screen.  It's not very nice, it's a workaround for the systems where
-// isprint(KEY_PPAGE) returns TRUE...
-static int is_speckey(int key)
-{
-  switch (key) {
-    case 127:
-    case 393:
-    case 402:
-    case KEY_BACKSPACE:
-    case KEY_DC:
-    case KEY_LEFT:
-    case KEY_RIGHT:
-    case KEY_UP:
-    case KEY_DOWN:
-    case KEY_PPAGE:
-    case KEY_NPAGE:
-    case KEY_HOME:
-    case KEY_END:
-    case KEY_EOL:
-        return TRUE;
-  }
-
-  // Fn keys
-  if (key >= 265 && key < 265+12)
-    return TRUE;
-
-  // Special key combinations
-  if (key >= 513 && key <= 521)
-    return TRUE;
-
-  return FALSE;
-}
-
-void scr_InitLocaleCharSet(void)
-{
-  setlocale(LC_ALL, "");
-#ifdef HAVE_LOCALCHARSET_H
-  LocaleCharSet = locale_charset();
-#else
-  LocaleCharSet = nl_langinfo(CODESET);
-#endif
-  utf8_mode = (strcmp(LocaleCharSet, "UTF-8") == 0);
-}
-
-void scr_InitCurses(void)
-{
-  /* Key sequences initialization */
-  init_keycodes();
-
-  initscr();
-  raw();
-  noecho();
-  nonl();
-  intrflush(stdscr, FALSE);
-  start_color();
-  use_default_colors();
-#ifdef NCURSES_MOUSE_VERSION
-  if (settings_opt_get_int("use_mouse"))
-    mousemask(ALL_MOUSE_EVENTS, NULL);
-#endif
-
-  if (settings_opt_get("escdelay")) {
-#ifdef HAVE_ESCDELAY
-    ESCDELAY = (unsigned) settings_opt_get_int("escdelay");
-#else
-    scr_LogPrint(LPRINT_LOGNORM, "ERROR: no ESCDELAY support.");
-#endif
-  }
-
-  ParseColors();
-
-  getmaxyx(stdscr, maxY, maxX);
-  Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
-  // Note scr_DrawMainWindow() should be called early after scr_InitCurses()
-  // to update Log_Win_Height and set max{X,Y}
-
-  inputLine[0] = 0;
-  ptr_inputline = inputLine;
-
-  if (settings_opt_get("url_regex")) {
-#ifdef HAVE_GLIB_REGEX
-    url_regex = g_regex_new(settings_opt_get("url_regex"),
-                            G_REGEX_OPTIMIZE, 0, NULL);
-#else
-    scr_LogPrint(LPRINT_LOGNORM, "ERROR: Your glib version is too old, "
-                 "cannot use url_regex.");
-#endif // HAVE_GLIB_REGEX
-  }
-
-  Curses = TRUE;
-  return;
-}
-
-void scr_TerminateCurses(void)
-{
-  if (!Curses) return;
-  clear();
-  refresh();
-  endwin();
-#ifdef HAVE_GLIB_REGEX
-  if (url_regex)
-    g_regex_unref(url_regex);
-#endif
-  Curses = FALSE;
-  return;
-}
-
-void scr_Beep(void)
-{
-  beep();
-}
-
-// This and following belongs to dynamic setting of time prefix
-static const char *timeprefixes[] = {
-  "%m-%d %H:%M ",
-  "%H:%M ",
-  " "
-};
-
-static const char *spectimeprefixes[] = {
-  "%m-%d %H:%M:%S   ",
-  "%H:%M:%S   ",
-  "   "
-};
-
-static int timepreflengths[] = {
-  // (length of the corresponding timeprefix + 5)
-  17,
-  11,
-  6
-};
-
-static const char *gettprefix(void)
-{
-  guint n = settings_opt_get_int("time_prefix");
-  return timeprefixes[(n < 3 ? n : 0)];
-}
-
-static const char *getspectprefix(void)
-{
-  guint n = settings_opt_get_int("time_prefix");
-  return spectimeprefixes[(n < 3 ? n : 0)];
-}
-
-guint scr_getprefixwidth(void)
-{
-  guint n = settings_opt_get_int("time_prefix");
-  return timepreflengths[(n < 3 ? n : 0)];
-}
-
-//  scr_print_logwindow(string)
-// Display the string in the log window.
-// Note: The string must be in the user's locale!
-void scr_print_logwindow(const char *string)
-{
-  time_t timestamp;
-  char strtimestamp[64];
-
-  timestamp = time(NULL);
-  strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
-  if (Curses) {
-    wprintw(logWnd, "\n%s %s", strtimestamp, string);
-    update_panels();
-  } else {
-    printf("%s %s\n", strtimestamp, string);
-  }
-}
-
-//  scr_LogPrint(...)
-// Display a message in the log window and in the status buffer.
-// Add the message to the tracelog file if the log flag is set.
-// This function will convert from UTF-8 unless the LPRINT_NOTUTF8 flag is set.
-void scr_LogPrint(unsigned int flag, const char *fmt, ...)
-{
-  time_t timestamp;
-  char strtimestamp[64];
-  char *buffer, *btext;
-  char *convbuf1 = NULL, *convbuf2 = NULL;
-  va_list ap;
-
-  if (!(flag & ~LPRINT_NOTUTF8)) return; // Shouldn't happen
-
-  timestamp = time(NULL);
-  strftime(strtimestamp, 48, "[%H:%M:%S]", localtime(&timestamp));
-  va_start(ap, fmt);
-  btext = g_strdup_vprintf(fmt, ap);
-  va_end(ap);
-
-  if (flag & LPRINT_NORMAL) {
-    char *buffer_locale;
-    char *buf_specialwindow;
-
-    buffer = g_strdup_printf("%s %s", strtimestamp, btext);
-
-    // Convert buffer to current locale for wprintw()
-    if (!(flag & LPRINT_NOTUTF8))
-      buffer_locale = convbuf1 = from_utf8(buffer);
-    else
-      buffer_locale = buffer;
-
-    if (!buffer_locale) {
-      wprintw(logWnd,
-              "\n%s*Error: cannot convert string to locale.", strtimestamp);
-      update_panels();
-      g_free(buffer);
-      g_free(btext);
-      return;
-    }
-
-    // For the special status buffer, we need utf-8, but without the timestamp
-    if (flag & LPRINT_NOTUTF8)
-      buf_specialwindow = convbuf2 = to_utf8(btext);
-    else
-      buf_specialwindow = btext;
-
-    if (Curses) {
-      wprintw(logWnd, "\n%s", buffer_locale);
-      update_panels();
-      scr_WriteInWindow(NULL, buf_specialwindow, timestamp,
-                        HBB_PREFIX_SPECIAL, FALSE, 0, NULL);
-    } else {
-      printf("%s\n", buffer_locale);
-      // ncurses are not initialized yet, so we call directly hbuf routine
-      hbuf_add_line(&statushbuf, buf_specialwindow, timestamp,
-        HBB_PREFIX_SPECIAL, 0, 0, 0, NULL);
-    }
-
-    g_free(convbuf1);
-    g_free(convbuf2);
-    g_free(buffer);
-  }
-
-  if (flag & (LPRINT_LOG|LPRINT_DEBUG)) {
-    strftime(strtimestamp, 23, "[%Y-%m-%d %H:%M:%S]", localtime(&timestamp));
-    buffer = g_strdup_printf("%s %s\n", strtimestamp, btext);
-    ut_WriteLog(flag, buffer);
-    g_free(buffer);
-  }
-  g_free(btext);
-}
-
-static winbuf *scr_SearchWindow(const char *winId, int special)
-{
-  char *id;
-  winbuf *wbp;
-
-  if (special)
-    return statusWindow; // Only one special window atm.
-
-  if (!winId)
-    return NULL;
-
-  id = g_strdup(winId);
-  mc_strtolower(id);
-  wbp = g_hash_table_lookup(winbufhash, id);
-  g_free(id);
-  return wbp;
-}
-
-int scr_BuddyBufferExists(const char *bjid)
-{
-  return (scr_SearchWindow(bjid, FALSE) != NULL);
-}
-
-//  scr_new_buddy(title, dontshow)
-// Note: title (aka winId/jid) can be NULL for special buffers
-static winbuf *scr_new_buddy(const char *title, int dont_show)
-{
-  winbuf *tmp;
-
-  tmp = g_new0(winbuf, 1);
-
-  tmp->win = activechatWnd;
-  tmp->panel = activechatPanel;
-
-  if (!dont_show) {
-    currentWindow = tmp;
-  } else {
-    if (currentWindow)
-      top_panel(currentWindow->panel);
-    else
-      top_panel(chatPanel);
-  }
-  update_panels();
-
-  // If title is NULL, this is a special buffer
-  if (title) {
-    char *id;
-    id = hlog_get_log_jid(title);
-    if (id) {
-      winbuf *wb = scr_SearchWindow(id, FALSE);
-      if (!wb)
-        wb = scr_new_buddy(id, TRUE);
-      tmp->bd=wb->bd;
-      g_free(id);
-    } else {  // Load buddy history from file (if enabled)
-      tmp->bd = g_new0(buffdata, 1);
-      hlog_read_history(title, &tmp->bd->hbuf,
-                        maxX - Roster_Width - scr_getprefixwidth());
-    }
-
-    id = g_strdup(title);
-    mc_strtolower(id);
-    g_hash_table_insert(winbufhash, id, tmp);
-  } else {
-    tmp->bd = g_new0(buffdata, 1);
-  }
-  return tmp;
-}
-
-//  scr_line_prefix(line, pref, preflen)
-// Use data from the hbb_line structure and write the prefix
-// to pref (not exceeding preflen, trailing null byte included).
-void scr_line_prefix(hbb_line *line, char *pref, guint preflen)
-{
-  char date[64];
-
-  if (line->timestamp &&
-      !(line->flags & (HBB_PREFIX_SPECIAL|HBB_PREFIX_CONT))) {
-    strftime(date, 30, gettprefix(), localtime(&line->timestamp));
-  } else
-    strcpy(date, "           ");
-
-  if (!(line->flags & HBB_PREFIX_CONT)) {
-    if (line->flags & HBB_PREFIX_INFO) {
-      char dir = '*';
-      if (line->flags & HBB_PREFIX_IN)
-        dir = '<';
-      else if (line->flags & HBB_PREFIX_OUT)
-        dir = '>';
-      g_snprintf(pref, preflen, "%s*%c* ", date, dir);
-    } else if (line->flags & HBB_PREFIX_ERR) {
-      char dir = '#';
-      if (line->flags & HBB_PREFIX_IN)
-        dir = '<';
-      else if (line->flags & HBB_PREFIX_OUT)
-        dir = '>';
-      g_snprintf(pref, preflen, "%s#%c# ", date, dir);
-    } else if (line->flags & HBB_PREFIX_IN) {
-      char cryptflag;
-      if (line->flags & HBB_PREFIX_PGPCRYPT)
-        cryptflag = '~';
-      else if (line->flags & HBB_PREFIX_OTRCRYPT)
-        cryptflag = 'O';
-      else
-        cryptflag = '=';
-      g_snprintf(pref, preflen, "%s<%c= ", date, cryptflag);
-    } else if (line->flags & HBB_PREFIX_OUT) {
-      char cryptflag, receiptflag;
-      if (line->flags & HBB_PREFIX_PGPCRYPT)
-        cryptflag = '~';
-      else if (line->flags & HBB_PREFIX_OTRCRYPT)
-        cryptflag = 'O';
-      else
-        cryptflag = '-';
-      if (line->flags & HBB_PREFIX_RECEIPT)
-        receiptflag = 'r';
-      else
-        receiptflag = '-';
-      g_snprintf(pref, preflen, "%s%c%c> ", date, receiptflag, cryptflag);
-    } else if (line->flags & HBB_PREFIX_SPECIAL) {
-      strftime(date, 30, getspectprefix(), localtime(&line->timestamp));
-      g_snprintf(pref, preflen, "%s   ", date);
-    } else {
-      g_snprintf(pref, preflen, "%s    ", date);
-    }
-  } else {
-    g_snprintf(pref, preflen, "                ");
-  }
-}
-
-//  scr_UpdateWindow()
-// (Re-)Display the given chat window.
-static void scr_UpdateWindow(winbuf *win_entry)
-{
-  int n;
-  guint prefixwidth;
-  char pref[96];
-  hbb_line **lines, *line;
-  GList *hbuf_head;
-  int color;
-
-  prefixwidth = scr_getprefixwidth();
-  prefixwidth = MIN(prefixwidth, sizeof pref);
-
-  // Should the window be empty?
-  if (win_entry->bd->cleared) {
-    werase(win_entry->win);
-    return;
-  }
-
-  // win_entry->bd->top is the top message of the screen.  If it set to NULL,
-  // we are displaying the last messages.
-
-  // We will show the last CHAT_WIN_HEIGHT lines.
-  // Let's find out where it begins.
-  if (!win_entry->bd->top || (g_list_position(g_list_first(win_entry->bd->hbuf),
-                                              win_entry->bd->top) == -1)) {
-    // Move up CHAT_WIN_HEIGHT lines
-    win_entry->bd->hbuf = g_list_last(win_entry->bd->hbuf);
-    hbuf_head = win_entry->bd->hbuf;
-    win_entry->bd->top = NULL; // (Just to make sure)
-    n = 0;
-    while (hbuf_head && (n < CHAT_WIN_HEIGHT-1) && g_list_previous(hbuf_head)) {
-      hbuf_head = g_list_previous(hbuf_head);
-      n++;
-    }
-    // If the buffer is locked, remember current "top" line for the next time.
-    if (win_entry->bd->lock)
-      win_entry->bd->top = hbuf_head;
-  } else
-    hbuf_head = win_entry->bd->top;
-
-  // Get the last CHAT_WIN_HEIGHT lines.
-  lines = hbuf_get_lines(hbuf_head, CHAT_WIN_HEIGHT);
-
-  // Display these lines
-  for (n = 0; n < CHAT_WIN_HEIGHT; n++) {
-    wmove(win_entry->win, n, 0);
-    line = *(lines+n);
-    if (line) {
-      if (line->flags & HBB_PREFIX_HLIGHT_OUT)
-        color = COLOR_MSGOUT;
-      else if (line->flags & HBB_PREFIX_HLIGHT)
-        color = COLOR_MSGHL;
-      else if (line->flags & HBB_PREFIX_INFO)
-        color = COLOR_INFO;
-      else if (line->flags & HBB_PREFIX_IN)
-        color = COLOR_MSGIN;
-      else
-        color = COLOR_GENERAL;
-
-      if (color != COLOR_GENERAL)
-        wattrset(win_entry->win, get_color(color));
-
-      // Generate the prefix area and display it
-      scr_line_prefix(line, pref, prefixwidth);
-      wprintw(win_entry->win, pref);
-
-      // Make sure we are at the right position
-      wmove(win_entry->win, n, prefixwidth-1);
-
-      // The MUC nick - overwrite with proper color
-      if (line->mucnicklen) {
-        char *mucjid;
-        char tmp;
-        nickcolor *actual = NULL;
-        muccoltype type, *typetmp;
-
-        // Store the char after the nick
-        tmp = line->text[line->mucnicklen];
-        type = glob_muccol;
-        // Terminate the string after the nick
-        line->text[line->mucnicklen] = '\0';
-        mucjid = g_utf8_strdown(CURRENT_JID, -1);
-        if (muccolors) {
-          typetmp = g_hash_table_lookup(muccolors, mucjid);
-          if (typetmp)
-            type = *typetmp;
-        }
-        g_free(mucjid);
-        // Need to generate a color for the specified nick?
-        if ((type == MC_ALL) && (!nickcolors ||
-            !g_hash_table_lookup(nickcolors, line->text))) {
-          char *snick, *mnick;
-          nickcolor *nc;
-          const char *p = line->text;
-          unsigned int nicksum = 0;
-          snick = g_strdup(line->text);
-          mnick = g_strdup(line->text);
-          nc = g_new(nickcolor, 1);
-          ensure_string_htable(&nickcolors, NULL);
-          while (*p)
-            nicksum += *p++;
-          nc->color = nickcols[nicksum % nickcolcount];
-          nc->manual = FALSE;
-          *snick = '<';
-          snick[strlen(snick)-1] = '>';
-          *mnick = '*';
-          mnick[strlen(mnick)-1] = ' ';
-          // Insert them
-          g_hash_table_insert(nickcolors, snick, nc);
-          g_hash_table_insert(nickcolors, mnick, nc);
-        }
-        if (nickcolors)
-          actual = g_hash_table_lookup(nickcolors, line->text);
-        if (actual && ((type == MC_ALL) || (actual->manual))
-            && (line->flags & HBB_PREFIX_IN) &&
-           (!(line->flags & HBB_PREFIX_HLIGHT_OUT)))
-          wattrset(win_entry->win, compose_color(actual->color));
-        wprintw(win_entry->win, "%s", line->text);
-        // Return the char
-        line->text[line->mucnicklen] = tmp;
-        // Return the color back
-        wattrset(win_entry->win, get_color(color));
-      }
-
-      // Display text line
-      wprintw(win_entry->win, "%s", line->text+line->mucnicklen);
-      wclrtoeol(win_entry->win);
-
-      // Return the color back
-      if (color != COLOR_GENERAL)
-        wattrset(win_entry->win, get_color(COLOR_GENERAL));
-
-      g_free(line->text);
-      g_free(line);
-    } else {
-      wclrtobot(win_entry->win);
-      break;
-    }
-  }
-  g_free(lines);
-}
-
-static winbuf *scr_CreateWindow(const char *winId, int special, int dont_show)
-{
-  if (special) {
-    if (!statusWindow) {
-      statusWindow = scr_new_buddy(NULL, dont_show);
-      statusWindow->bd->hbuf = statushbuf;
-    }
-    return statusWindow;
-  } else {
-    return scr_new_buddy(winId, dont_show);
-  }
-}
-
-//  scr_ShowWindow()
-// Display the chat window with the given identifier.
-// "special" must be true if this is a special buffer window.
-static void scr_ShowWindow(const char *winId, int special)
-{
-  winbuf *win_entry;
-
-  win_entry = scr_SearchWindow(winId, special);
-
-  if (!win_entry) {
-    win_entry = scr_CreateWindow(winId, special, FALSE);
-  }
-
-  top_panel(win_entry->panel);
-  currentWindow = win_entry;
-  chatmode = TRUE;
-  if (!win_entry->bd->lock)
-    roster_msg_setflag(winId, special, FALSE);
-  if (!special)
-    roster_setflags(winId, ROSTER_FLAG_LOCK, TRUE);
-  update_roster = TRUE;
-
-  // Refresh the window
-  scr_UpdateWindow(win_entry);
-
-  // Finished :)
-  update_panels();
-
-  top_panel(inputPanel);
-}
-
-//  scr_ShowBuddyWindow()
-// Display the chat window buffer for the current buddy.
-void scr_ShowBuddyWindow(void)
-{
-  const gchar *bjid;
-
-  if (!current_buddy) {
-    bjid = NULL;
-  } else {
-    bjid = CURRENT_JID;
-    if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL) {
-      scr_ShowWindow(buddy_getname(BUDDATA(current_buddy)), TRUE);
-      return;
-    }
-  }
-
-  if (!bjid) {
-    top_panel(chatPanel);
-    top_panel(inputPanel);
-    currentWindow = NULL;
-    return;
-  }
-
-  scr_ShowWindow(bjid, FALSE);
-}
-
-//  scr_UpdateBuddyWindow()
-// (Re)Display the current window.
-// If chatmode is enabled, call scr_ShowBuddyWindow(),
-// else display the chat window.
-inline void scr_UpdateBuddyWindow(void)
-{
-  if (chatmode) {
-    scr_ShowBuddyWindow();
-    return;
-  }
-
-  top_panel(chatPanel);
-  top_panel(inputPanel);
-}
-
-//  scr_WriteInWindow()
-// Write some text in the winId window (this usually is a jid).
-// Use winId == NULL for the special status buffer.
-// Lines are splitted when they are too long to fit in the chat window.
-// If this window doesn't exist, it is created.
-void scr_WriteInWindow(const char *winId, const char *text, time_t timestamp,
-                       unsigned int prefix_flags, int force_show,
-                       unsigned mucnicklen, gpointer xep184)
-{
-  winbuf *win_entry;
-  char *text_locale;
-  int dont_show = FALSE;
-  int special;
-  guint num_history_blocks;
-  bool setmsgflg = FALSE;
-  char *nicktmp, *nicklocaltmp;
-
-  // Look for the window entry.
-  special = (winId == NULL);
-  win_entry = scr_SearchWindow(winId, special);
-
-  // Do we have to really show the window?
-  if (!chatmode)
-    dont_show = TRUE;
-  else if ((!force_show) && ((!currentWindow || (currentWindow != win_entry))))
-    dont_show = TRUE;
-
-  // If the window entry doesn't exist yet, let's create it.
-  if (!win_entry) {
-    win_entry = scr_CreateWindow(winId, special, dont_show);
-  }
-
-  // The message must be displayed -> update top pointer
-  if (win_entry->bd->cleared)
-    win_entry->bd->top = g_list_last(win_entry->bd->hbuf);
-
-  // Make sure we do not free the buffer while it's locked or when
-  // top is set.
-  if (win_entry->bd->lock || win_entry->bd->top)
-    num_history_blocks = 0U;
-  else
-    num_history_blocks = get_max_history_blocks();
-
-  text_locale = from_utf8(text);
-  //Convert the nick alone and compute its length
-  if (mucnicklen) {
-    nicktmp = g_strndup(text, mucnicklen);
-    nicklocaltmp = from_utf8(nicktmp);
-    mucnicklen = strlen(nicklocaltmp);
-    g_free(nicklocaltmp);
-    g_free(nicktmp);
-  }
-  hbuf_add_line(&win_entry->bd->hbuf, text_locale, timestamp, prefix_flags,
-                maxX - Roster_Width - scr_getprefixwidth(), num_history_blocks,
-                mucnicklen, xep184);
-  g_free(text_locale);
-
-  if (win_entry->bd->cleared) {
-    win_entry->bd->cleared = FALSE;
-    if (g_list_next(win_entry->bd->top))
-      win_entry->bd->top = g_list_next(win_entry->bd->top);
-  }
-
-  // Make sure the last line appears in the window; update top if necessary
-  if (!win_entry->bd->lock && win_entry->bd->top) {
-    int dist;
-    GList *first = g_list_first(win_entry->bd->hbuf);
-    dist = g_list_position(first, g_list_last(win_entry->bd->hbuf)) -
-           g_list_position(first, win_entry->bd->top);
-    if (dist >= CHAT_WIN_HEIGHT)
-      win_entry->bd->top = NULL;
-  }
-
-  if (!dont_show) {
-    if (win_entry->bd->lock)
-      setmsgflg = TRUE;
-    // Show and refresh the window
-    top_panel(win_entry->panel);
-    scr_UpdateWindow(win_entry);
-    top_panel(inputPanel);
-    update_panels();
-  } else if (!(prefix_flags & HBB_PREFIX_NOFLAG)) {
-    setmsgflg = TRUE;
-  }
-  if (setmsgflg && !special) {
-    if (special && !winId)
-      winId = SPECIAL_BUFFER_STATUS_ID;
-    roster_msg_setflag(winId, special, TRUE);
-    update_roster = TRUE;
-  }
-}
-
-//  scr_UpdateMainStatus()
-// Redraw the main (bottom) status line.
-void scr_UpdateMainStatus(int forceupdate)
-{
-  char *sm = from_utf8(xmpp_getstatusmsg());
-  const char *info = settings_opt_get("info");
-
-  werase(mainstatusWnd);
-  if (info) {
-    char *info_locale = from_utf8(info);
-    mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s: %s",
-              (unread_msg(NULL) ? '#' : ' '),
-              imstatus2char[xmpp_getstatus()],
-              info_locale, (sm ? sm : ""));
-    g_free(info_locale);
-  } else
-    mvwprintw(mainstatusWnd, 0, 0, "%c[%c] %s",
-              (unread_msg(NULL) ? '#' : ' '),
-              imstatus2char[xmpp_getstatus()], (sm ? sm : ""));
-  if (forceupdate) {
-    top_panel(inputPanel);
-    update_panels();
-  }
-  g_free(sm);
-}
-
-//  scr_DrawMainWindow()
-// Set fullinit to TRUE to also create panels.  Set it to FALSE for a resize.
-//
-// I think it could be improved a _lot_ but I'm really not an ncurses
-// expert... :-\   Mikael.
-//
-void scr_DrawMainWindow(unsigned int fullinit)
-{
-  int requested_size;
-  gchar *ver, *message;
-  int chat_y_pos, chatstatus_y_pos, log_y_pos;
-  int roster_x_pos, chat_x_pos;
-
-  Log_Win_Height = DEFAULT_LOG_WIN_HEIGHT;
-  requested_size = settings_opt_get_int("log_win_height");
-  if (requested_size > 0) {
-    if (maxY > requested_size + 3)
-      Log_Win_Height = requested_size + 2;
-    else
-      Log_Win_Height = ((maxY > 5) ? (maxY - 2) : 3);
-  } else if (requested_size < 0) {
-    Log_Win_Height = 3;
-  }
-
-  if (maxY < Log_Win_Height+2) {
-    if (maxY < 5) {
-      Log_Win_Height = 3;
-      maxY = Log_Win_Height+2;
-    } else {
-      Log_Win_Height = maxY - 2;
-    }
-  }
-
-  if (roster_hidden) {
-    Roster_Width = 0;
-  } else {
-    requested_size = settings_opt_get_int("roster_width");
-    if (requested_size > 1)
-      Roster_Width = requested_size;
-    else if (requested_size == 1)
-      Roster_Width = 2;
-    else
-      Roster_Width = DEFAULT_ROSTER_WIDTH;
-  }
-
-  log_win_on_top = (settings_opt_get_int("log_win_on_top") == 1);
-  roster_win_on_right = (settings_opt_get_int("roster_win_on_right") == 1);
-
-  if (log_win_on_top) {
-    chat_y_pos = Log_Win_Height-1;
-    log_y_pos = 0;
-    chatstatus_y_pos = Log_Win_Height-2;
-  } else {
-    chat_y_pos = 0;
-    log_y_pos = CHAT_WIN_HEIGHT+1;
-    chatstatus_y_pos = CHAT_WIN_HEIGHT;
-  }
-
-  if (roster_win_on_right) {
-    roster_x_pos = maxX - Roster_Width;
-    chat_x_pos = 0;
-  } else {
-    roster_x_pos = 0;
-    chat_x_pos = Roster_Width;
-  }
-
-  if (fullinit) {
-    if (!winbufhash)
-      winbufhash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
-    /* Create windows */
-    rosterWnd = newwin(CHAT_WIN_HEIGHT, Roster_Width, chat_y_pos, roster_x_pos);
-    chatWnd   = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
-                       chat_x_pos);
-    activechatWnd = newwin(CHAT_WIN_HEIGHT, maxX - Roster_Width, chat_y_pos,
-                           chat_x_pos);
-    logWnd    = newwin(Log_Win_Height-2, maxX, log_y_pos, 0);
-    chatstatusWnd = newwin(1, maxX, chatstatus_y_pos, 0);
-    mainstatusWnd = newwin(1, maxX, maxY-2, 0);
-    inputWnd  = newwin(1, maxX, maxY-1, 0);
-    if (!rosterWnd || !chatWnd || !logWnd || !inputWnd) {
-      scr_TerminateCurses();
-      fprintf(stderr, "Cannot create windows!\n");
-      exit(EXIT_FAILURE);
-    }
-    wbkgd(rosterWnd,      get_color(COLOR_GENERAL));
-    wbkgd(chatWnd,        get_color(COLOR_GENERAL));
-    wbkgd(activechatWnd,  get_color(COLOR_GENERAL));
-    wbkgd(logWnd,         get_color(COLOR_GENERAL));
-    wbkgd(chatstatusWnd,  get_color(COLOR_STATUS));
-    wbkgd(mainstatusWnd,  get_color(COLOR_STATUS));
-  } else {
-    /* Resize/move windows */
-    wresize(rosterWnd, CHAT_WIN_HEIGHT, Roster_Width);
-    wresize(chatWnd, CHAT_WIN_HEIGHT, maxX - Roster_Width);
-    wresize(logWnd, Log_Win_Height-2, maxX);
-
-    mvwin(chatWnd, chat_y_pos, chat_x_pos);
-    mvwin(rosterWnd, chat_y_pos, roster_x_pos);
-    mvwin(logWnd, log_y_pos, 0);
-
-    // Resize & move chat status window
-    wresize(chatstatusWnd, 1, maxX);
-    mvwin(chatstatusWnd, chatstatus_y_pos, 0);
-    // Resize & move main status window
-    wresize(mainstatusWnd, 1, maxX);
-    mvwin(mainstatusWnd, maxY-2, 0);
-    // Resize & move input line window
-    wresize(inputWnd, 1, maxX);
-    mvwin(inputWnd, maxY-1, 0);
-
-    werase(chatWnd);
-  }
-
-  /* Draw/init windows */
-
-  ver = mcabber_version();
-  message = g_strdup_printf("MCabber version %s.\n", ver);
-  mvwprintw(chatWnd, 0, 0, message);
-  mvwprintw(chatWnd, 1, 0, "http://mcabber.com/");
-  g_free(ver);
-  g_free(message);
-
-  // Auto-scrolling in log window
-  scrollok(logWnd, TRUE);
-
-
-  if (fullinit) {
-    // Enable keypad (+ special keys)
-    keypad(inputWnd, TRUE);
-#ifdef __MirBSD__
-    wtimeout(inputWnd, 50 /* ms */);
-#else
-    nodelay(inputWnd, TRUE);
-#endif
-
-    // Create panels
-    rosterPanel = new_panel(rosterWnd);
-    chatPanel   = new_panel(chatWnd);
-    activechatPanel = new_panel(activechatWnd);
-    logPanel    = new_panel(logWnd);
-    chatstatusPanel = new_panel(chatstatusWnd);
-    mainstatusPanel = new_panel(mainstatusWnd);
-    inputPanel  = new_panel(inputWnd);
-
-    // Build the buddylist at least once, to make sure the special buffer
-    // is added
-    buddylist_build();
-
-    // Init prev_chatwidth; this variable will be used to prevent us
-    // from rewrapping buffers when the width doesn't change.
-    prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
-    // Wrap existing status buffer lines
-    hbuf_rebuild(&statushbuf, prev_chatwidth);
-
-#ifndef UNICODE
-    if (utf8_mode)
-      scr_LogPrint(LPRINT_NORMAL,
-                   "WARNING: Compiled without full UTF-8 support!");
-#endif
-  } else {
-    // Update panels
-    replace_panel(rosterPanel, rosterWnd);
-    replace_panel(chatPanel, chatWnd);
-    replace_panel(logPanel, logWnd);
-    replace_panel(chatstatusPanel, chatstatusWnd);
-    replace_panel(mainstatusPanel, mainstatusWnd);
-    replace_panel(inputPanel, inputWnd);
-  }
-
-  // We'll need to redraw the roster
-  update_roster = TRUE;
-  return;
-}
-
-static void resize_win_buffer(gpointer key, gpointer value, gpointer data)
-{
-  winbuf *wbp = value;
-  struct dimensions *dim = data;
-  int chat_x_pos, chat_y_pos;
-  int new_chatwidth;
-
-  if (!(wbp && wbp->win))
-    return;
-
-  if (log_win_on_top)
-    chat_y_pos = Log_Win_Height-1;
-  else
-    chat_y_pos = 0;
-
-  if (roster_win_on_right)
-    chat_x_pos = 0;
-  else
-    chat_x_pos = Roster_Width;
-
-  // Resize/move buddy window
-  wresize(wbp->win, dim->l, dim->c);
-  mvwin(wbp->win, chat_y_pos, chat_x_pos);
-  werase(wbp->win);
-  // If a panel exists, replace the old window with the new
-  if (wbp->panel)
-    replace_panel(wbp->panel, wbp->win);
-  // Redo line wrapping
-  wbp->bd->top = hbuf_previous_persistent(wbp->bd->top);
-
-  new_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
-  if (new_chatwidth != prev_chatwidth)
-    hbuf_rebuild(&wbp->bd->hbuf, new_chatwidth);
-}
-
-//  scr_Resize()
-// Function called when the window is resized.
-// - Resize windows
-// - Rewrap lines in each buddy buffer
-void scr_Resize(void)
-{
-  struct dimensions dim;
-
-  // First, update the global variables
-  getmaxyx(stdscr, maxY, maxX);
-  // scr_DrawMainWindow() will take care of maxY and Log_Win_Height
-
-  // Make sure the cursor stays inside the window
-  check_offset(0);
-
-  // Resize windows and update panels
-  scr_DrawMainWindow(FALSE);
-
-  // Resize all buddy windows
-  dim.l = CHAT_WIN_HEIGHT;
-  dim.c = maxX - Roster_Width;
-  if (dim.c < 1)
-    dim.c = 1;
-
-  // Resize all buffers
-  g_hash_table_foreach(winbufhash, resize_win_buffer, &dim);
-
-  // Resize/move special status buffer
-  if (statusWindow)
-    resize_win_buffer(NULL, statusWindow, &dim);
-
-  // Update prev_chatwidth, now that all buffers have been resized
-  prev_chatwidth = maxX - Roster_Width - scr_getprefixwidth();
-
-  // Refresh current buddy window
-  if (chatmode)
-    scr_ShowBuddyWindow();
-}
-
-//  scr_UpdateChatStatus(forceupdate)
-// Redraw the buddy status bar.
-// Set forceupdate to TRUE if update_panels() must be called.
-void scr_UpdateChatStatus(int forceupdate)
-{
-  unsigned short btype, isgrp, ismuc, isspe;
-  const char *btypetext = "Unknown";
-  const char *fullname;
-  const char *msg = NULL;
-  char status;
-  char *buf, *buf_locale;
-
-  // Usually we need to update the bottom status line too,
-  // at least to refresh the pending message flag.
-  scr_UpdateMainStatus(FALSE);
-
-  // Clear the line
-  werase(chatstatusWnd);
-
-  if (!current_buddy) {
-    if (forceupdate) {
-      update_panels();
-    }
-    return;
-  }
-
-  fullname = buddy_getname(BUDDATA(current_buddy));
-  btype = buddy_gettype(BUDDATA(current_buddy));
-
-  isgrp = ismuc = isspe = 0;
-  if (btype & ROSTER_TYPE_USER) {
-    btypetext = "Buddy";
-  } else if (btype & ROSTER_TYPE_GROUP) {
-    btypetext = "Group";
-    isgrp = 1;
-  } else if (btype & ROSTER_TYPE_AGENT) {
-    btypetext = "Agent";
-  } else if (btype & ROSTER_TYPE_ROOM) {
-    btypetext = "Room";
-    ismuc = 1;
-  } else if (btype & ROSTER_TYPE_SPECIAL) {
-    btypetext = "Special buffer";
-    isspe = 1;
-  }
-
-  if (chatmode) {
-    wprintw(chatstatusWnd, "~");
-  } else {
-    unsigned short bflags = buddy_getflags(BUDDATA(current_buddy));
-    if (bflags & ROSTER_FLAG_MSG) {
-      // There is an unread message from the current buddy
-      wprintw(chatstatusWnd, "#");
-    }
-  }
-
-  if (chatmode && !isgrp) {
-    winbuf *win_entry;
-    win_entry = scr_SearchWindow(buddy_getjid(BUDDATA(current_buddy)), isspe);
-    if (win_entry && win_entry->bd->lock)
-      mvwprintw(chatstatusWnd, 0, 0, "*");
-  }
-
-  if (isgrp || isspe) {
-    buf_locale = from_utf8(fullname);
-    mvwprintw(chatstatusWnd, 0, 5, "%s: %s", btypetext, buf_locale);
-    g_free(buf_locale);
-    if (forceupdate) {
-      update_panels();
-    }
-    return;
-  }
-
-  status = '?';
-
-  if (ismuc) {
-    if (buddy_getinsideroom(BUDDATA(current_buddy)))
-      status = 'C';
-    else
-      status = 'x';
-  } else if (xmpp_getstatus() != offline) {
-    enum imstatus budstate;
-    budstate = buddy_getstatus(BUDDATA(current_buddy), NULL);
-    if (budstate < imstatus_size)
-      status = imstatus2char[budstate];
-  }
-
-  // No status message for MUC rooms
-  if (!ismuc) {
-    GSList *resources, *p_res, *p_next_res;
-    resources = buddy_getresources(BUDDATA(current_buddy));
-
-    for (p_res = resources ; p_res ; p_res = p_next_res) {
-      p_next_res = g_slist_next(p_res);
-      // Store the status message of the latest resource (highest priority)
-      if (!p_next_res)
-        msg = buddy_getstatusmsg(BUDDATA(current_buddy), p_res->data);
-      g_free(p_res->data);
-    }
-    g_slist_free(resources);
-  } else {
-    msg = buddy_gettopic(BUDDATA(current_buddy));
-  }
-
-  if (msg)
-    buf = g_strdup_printf("[%c] %s: %s -- %s", status, btypetext, fullname, msg);
-  else
-    buf = g_strdup_printf("[%c] %s: %s", status, btypetext, fullname);
-  replace_nl_with_dots(buf);
-  buf_locale = from_utf8(buf);
-  mvwprintw(chatstatusWnd, 0, 1, "%s", buf_locale);
-  g_free(buf_locale);
-  g_free(buf);
-
-  // Display chatstates of the contact, if available.
-  if (btype & ROSTER_TYPE_USER) {
-    char eventchar = 0;
-    guint event;
-
-    // We do not specify the resource here, so one of the resources with the
-    // highest priority will be used.
-    event = buddy_resource_getevents(BUDDATA(current_buddy), NULL);
-
-    if (event == ROSTER_EVENT_ACTIVE)
-      eventchar = 'A';
-    else if (event == ROSTER_EVENT_COMPOSING)
-      eventchar = 'C';
-    else if (event == ROSTER_EVENT_PAUSED)
-      eventchar = 'P';
-    else if (event == ROSTER_EVENT_INACTIVE)
-      eventchar = 'I';
-    else if (event == ROSTER_EVENT_GONE)
-      eventchar = 'G';
-
-    if (eventchar)
-      mvwprintw(chatstatusWnd, 0, maxX-3, "[%c]", eventchar);
-  }
-
-
-  if (forceupdate) {
-    update_panels();
-  }
-}
-
-void increment_if_buddy_not_filtered(gpointer rosterdata, void *param)
-{
-  int *p = param;
-  if (buddylist_is_status_filtered(buddy_getstatus(rosterdata, NULL)))
-    *p=*p+1;
-}
-
-//  scr_DrawRoster()
-// Display the buddylist (not really the roster) on the screen
-void scr_DrawRoster(void)
-{
-  static int offset = 0;
-  char *name, *rline;
-  int maxx, maxy;
-  GList *buddy;
-  int i, n;
-  int rOffset;
-  int cursor_backup;
-  char status, pending;
-  enum imstatus currentstatus = xmpp_getstatus();
-  int x_pos;
-
-  // We can reset update_roster
-  update_roster = FALSE;
-
-  getmaxyx(rosterWnd, maxy, maxx);
-  maxx--;  // Last char is for vertical border
-
-  cursor_backup = curs_set(0);
-
-  if (!buddylist)
-    offset = 0;
-  else
-    scr_UpdateChatStatus(FALSE);
-
-  // Cleanup of roster window
-  werase(rosterWnd);
-
-  if (Roster_Width) {
-    int line_x_pos = roster_win_on_right ? 0 : Roster_Width-1;
-    // Redraw the vertical line (not very good...)
-    wattrset(rosterWnd, get_color(COLOR_GENERAL));
-    for (i=0 ; i < CHAT_WIN_HEIGHT ; i++)
-      mvwaddch(rosterWnd, i, line_x_pos, ACS_VLINE);
-  }
-
-  // Leave now if buddylist is empty or the roster is hidden
-  if (!buddylist || !Roster_Width) {
-    update_panels();
-    curs_set(cursor_backup);
-    return;
-  }
-
-  // Update offset if necessary
-  // a) Try to show as many buddylist items as possible
-  i = g_list_length(buddylist) - maxy;
-  if (i < 0)
-    i = 0;
-  if (i < offset)
-    offset = i;
-  // b) Make sure the current_buddy is visible
-  i = g_list_position(buddylist, current_buddy);
-  if (i == -1) { // This is bad
-    scr_LogPrint(LPRINT_NORMAL, "Doh! Can't find current selected buddy!!");
-    curs_set(cursor_backup);
-    return;
-  } else if (i < offset) {
-    offset = i;
-  } else if (i+1 > offset + maxy) {
-    offset = i + 1 - maxy;
-  }
-
-  if (roster_win_on_right)
-    x_pos = 1; // 1 char offset (vertical line)
-  else
-    x_pos = 0;
-
-  name = g_new0(char, 4*Roster_Width);
-  rline = g_new0(char, 4*Roster_Width+1);
-
-  buddy = buddylist;
-  rOffset = offset;
-
-  for (i=0; i<maxy && buddy; buddy = g_list_next(buddy)) {
-    unsigned short bflags, btype, ismsg, isgrp, ismuc, ishid, isspe;
-    gchar *rline_locale;
-    GSList *resources, *p_res;
-
-    bflags = buddy_getflags(BUDDATA(buddy));
-    btype = buddy_gettype(BUDDATA(buddy));
-
-    ismsg = bflags & ROSTER_FLAG_MSG;
-    ishid = bflags & ROSTER_FLAG_HIDE;
-    isgrp = btype  & ROSTER_TYPE_GROUP;
-    ismuc = btype  & ROSTER_TYPE_ROOM;
-    isspe = btype  & ROSTER_TYPE_SPECIAL;
-
-    if (rOffset > 0) {
-      rOffset--;
-      continue;
-    }
-
-    status = '?';
-    pending = ' ';
-
-    resources = buddy_getresources(BUDDATA(buddy));
-    for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
-      guint events = buddy_resource_getevents(BUDDATA(buddy),
-                                              p_res ? p_res->data : "");
-      if ((events & ROSTER_EVENT_PAUSED) && pending != '+')
-        pending = '.';
-      if (events & ROSTER_EVENT_COMPOSING)
-        pending = '+';
-      g_free(p_res->data);
-    }
-    g_slist_free(resources);
-
-    // Display message notice if there is a message flag, but not
-    // for unfolded groups.
-    if (ismsg && (!isgrp || ishid)) {
-      pending = '#';
-    }
-
-    if (ismuc) {
-      if (buddy_getinsideroom(BUDDATA(buddy)))
-        status = 'C';
-      else
-        status = 'x';
-    } else if (currentstatus != offline) {
-      enum imstatus budstate;
-      budstate = buddy_getstatus(BUDDATA(buddy), NULL);
-      if (budstate < imstatus_size)
-        status = imstatus2char[budstate];
-    }
-    if (buddy == current_buddy) {
-      if (pending == '#')
-        wattrset(rosterWnd, get_color(COLOR_ROSTERSELNMSG));
-      else
-        wattrset(rosterWnd, get_color(COLOR_ROSTERSEL));
-      // The 3 following lines aim at coloring the whole line
-      wmove(rosterWnd, i, x_pos);
-      for (n = 0; n < maxx; n++)
-        waddch(rosterWnd, ' ');
-    } else {
-      if (pending == '#')
-        wattrset(rosterWnd, get_color(COLOR_ROSTERNMSG));
-      else {
-        int color = get_color(COLOR_ROSTER);
-        if ((!isspe) && (!isgrp)) {//Look for color rules
-          GSList *head;
-          const char *jid = buddy_getjid(BUDDATA(buddy));
-          for (head = rostercolrules; head; head = g_slist_next(head)) {
-            rostercolor *rc = head->data;
-            if (g_pattern_match_string(rc->compiled, jid) &&
-                (!strcmp("*", rc->status) || strchr(rc->status, status))) {
-              color = compose_color(rc->color);
-              break;
-            }
-          }
-        }
-        wattrset(rosterWnd, color);
-      }
-    }
-
-    if (Roster_Width > 7)
-      g_utf8_strncpy(name, buddy_getname(BUDDATA(buddy)), Roster_Width-7);
-    else
-      name[0] = 0;
-
-    if (isgrp) {
-      if (ishid) {
-        int group_count = 0;
-        foreach_group_member(BUDDATA(buddy), increment_if_buddy_not_filtered,
-                             &group_count);
-        snprintf(rline, 4*Roster_Width, " %c+++ %s (%i)", pending, name,
-                 group_count);
-        /* Do not display the item count if there isn't enough space */
-        if (g_utf8_strlen(rline, 4*Roster_Width) >= Roster_Width)
-          snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name);
-      }
-      else
-        snprintf(rline, 4*Roster_Width, " %c--- %s", pending, name);
-    } else if (isspe) {
-      snprintf(rline, 4*Roster_Width, " %c%s", pending, name);
-    } else {
-      char sepleft  = '[';
-      char sepright = ']';
-      if (btype & ROSTER_TYPE_USER) {
-        guint subtype = buddy_getsubscription(BUDDATA(buddy));
-        if (status == '_' && !(subtype & sub_to))
-          status = '?';
-        if (!(subtype & sub_from)) {
-          sepleft  = '{';
-          sepright = '}';
-        }
-      }
-
-      snprintf(rline, 4*Roster_Width,
-               " %c%c%c%c %s", pending, sepleft, status, sepright, name);
-    }
-
-    rline_locale = from_utf8(rline);
-    mvwprintw(rosterWnd, i, x_pos, "%s", rline_locale);
-    g_free(rline_locale);
-    i++;
-  }
-
-  g_free(rline);
-  g_free(name);
-  top_panel(inputPanel);
-  update_panels();
-  curs_set(cursor_backup);
-}
-
-//  scr_RosterVisibility(status)
-// Set the roster visibility:
-// status=1   Show roster
-// status=0   Hide roster
-// status=-1  Toggle roster status
-void scr_RosterVisibility(int status)
-{
-  int old_roster_status = roster_hidden;
-
-  if (status > 0)
-    roster_hidden = FALSE;
-  else if (status == 0)
-    roster_hidden = TRUE;
-  else
-    roster_hidden = !roster_hidden;
-
-  if (roster_hidden != old_roster_status) {
-    // Recalculate windows size and redraw
-    scr_Resize();
-    redrawwin(stdscr);
-  }
-}
-
-#ifdef HAVE_GLIB_REGEX
-static inline void scr_LogUrls(const gchar *string)
-{
-  GMatchInfo *match_info;
-
-  g_regex_match_full(url_regex, string, -1, 0, 0, &match_info, NULL);
-  while (g_match_info_matches(match_info)) {
-    gchar *url = g_match_info_fetch(match_info, 0);
-    scr_print_logwindow(url);
-    g_free(url);
-    g_match_info_next(match_info, NULL);
-  }
-  g_match_info_free(match_info);
-}
-#endif
-
-void scr_WriteMessage(const char *bjid, const char *text,
-                      time_t timestamp, guint prefix_flags,
-                      unsigned mucnicklen, gpointer xep184)
-{
-  char *xtext;
-
-  if (!timestamp) timestamp = time(NULL);
-
-  xtext = ut_expand_tabs(text); // Expand tabs and filter out some chars
-
-  scr_WriteInWindow(bjid, xtext, timestamp, prefix_flags, FALSE, mucnicklen,
-                    xep184);
-
-  if (xtext != (char*)text)
-    g_free(xtext);
-}
-
-// If prefix is NULL, HBB_PREFIX_IN is supposed.
-void scr_WriteIncomingMessage(const char *jidfrom, const char *text,
-        time_t timestamp, guint prefix, unsigned mucnicklen)
-{
-  if (!(prefix &
-        ~HBB_PREFIX_NOFLAG & ~HBB_PREFIX_HLIGHT & ~HBB_PREFIX_HLIGHT_OUT &
-        ~HBB_PREFIX_PGPCRYPT & ~HBB_PREFIX_OTRCRYPT))
-    prefix |= HBB_PREFIX_IN;
-
-#ifdef HAVE_GLIB_REGEX
-  if (url_regex)
-    scr_LogUrls(text);
-#endif
-  scr_WriteMessage(jidfrom, text, timestamp, prefix, mucnicklen, NULL);
-}
-
-void scr_WriteOutgoingMessage(const char *jidto, const char *text, guint prefix,
-                              gpointer xep184)
-{
-  GSList *roster_elt;
-  roster_elt = roster_find(jidto, jidsearch,
-                           ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
-
-  scr_WriteMessage(jidto, text,
-                   0, prefix|HBB_PREFIX_OUT|HBB_PREFIX_HLIGHT_OUT, 0, xep184);
-
-  // Show jidto's buffer unless the buddy is not in the buddylist
-  if (roster_elt && g_list_position(buddylist, roster_elt->data) != -1)
-    scr_ShowWindow(jidto, FALSE);
-}
-
-void scr_RemoveReceiptFlag(const char *bjid, gpointer xep184)
-{
-  winbuf *win_entry = scr_SearchWindow(bjid, FALSE);
-  if (win_entry) {
-    hbuf_remove_receipt(win_entry->bd->hbuf, xep184);
-    if (chatmode && (buddy_search_jid(bjid) == current_buddy))
-      scr_UpdateBuddyWindow();
-  }
-}
-
-static inline void set_autoaway(bool setaway)
-{
-  static enum imstatus oldstatus;
-  static char *oldmsg;
-  Autoaway = setaway;
-
-  if (setaway) {
-    const char *msg, *prevmsg;
-    oldstatus = xmpp_getstatus();
-    if (oldmsg) {
-      g_free(oldmsg);
-      oldmsg = NULL;
-    }
-    prevmsg = xmpp_getstatusmsg();
-    msg = settings_opt_get("message_autoaway");
-    if (!msg)
-      msg = prevmsg;
-    if (prevmsg)
-      oldmsg = g_strdup(prevmsg);
-    xmpp_setstatus(away, NULL, msg, FALSE);
-  } else {
-    // Back
-    xmpp_setstatus(oldstatus, NULL, (oldmsg ? oldmsg : ""), FALSE);
-    if (oldmsg) {
-      g_free(oldmsg);
-      oldmsg = NULL;
-    }
-  }
-}
-
-//  set_chatstate(state)
-// Set the current chat state (0=active, 1=composing, 2=paused)
-// If the chat state has changed, call xmpp_send_chatstate()
-static inline void set_chatstate(int state)
-{
-#if defined JEP0022 || defined JEP0085
-  if (chatstates_disabled)
-    return;
-  if (!chatmode)
-    state = 0;
-  if (state != chatstate) {
-    chatstate = state;
-    if (current_buddy &&
-        buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_USER) {
-      guint jep_state;
-      if (chatstate == 1) {
-        if (chatstate_timeout_id == 0)
-          chatstate_timeout_id = g_timeout_add_seconds(1,
-                                                       scr_ChatStatesTimeout,
-                                                       NULL);
-        jep_state = ROSTER_EVENT_COMPOSING;
-      }
-      else if (chatstate == 2)
-        jep_state = ROSTER_EVENT_PAUSED;
-      else
-        jep_state = ROSTER_EVENT_ACTIVE;
-      xmpp_send_chatstate(BUDDATA(current_buddy), jep_state);
-    }
-    if (!chatstate)
-      chatstate_timestamp = 0;
-  }
-#endif
-}
-
-#if defined JEP0022 || defined JEP0085
-gboolean scr_ChatStatesTimeout(void)
-{
-  time_t now;
-  time(&now);
-  // Check if we're currently composing...
-  if (chatstate != 1 || !chatstate_timestamp) {
-    chatstate_timeout_id = 0;
-    return FALSE;
-  }
-
-  // If the timeout is reached, let's change the state right now.
-  if (now >= chatstate_timestamp + COMPOSING_TIMEOUT) {
-    chatstate_timestamp = now;
-    set_chatstate(2);
-    chatstate_timeout_id = 0;
-    return FALSE;
-  }
-  return TRUE;
-}
-#endif
-
-// Check if we should enter/leave automatic away status
-void scr_CheckAutoAway(int activity)
-{
-  enum imstatus cur_st;
-  unsigned int autoaway_timeout = settings_opt_get_int("autoaway");
-
-  if (Autoaway && activity) set_autoaway(FALSE);
-  if (!autoaway_timeout) return;
-  if (!LastActivity || activity) time(&LastActivity);
-
-  cur_st = xmpp_getstatus();
-  // Auto-away is disabled for the following states
-  if ((cur_st != available) && (cur_st != freeforchat))
-    return;
-
-  if (!activity) {
-    time_t now;
-    time(&now);
-    if (!Autoaway && (now > LastActivity + (time_t)autoaway_timeout))
-      set_autoaway(TRUE);
-  }
-}
-
-//  set_current_buddy(newbuddy)
-// Set the current_buddy to newbuddy (if not NULL)
-// Lock the newbuddy, and unlock the previous current_buddy
-static void set_current_buddy(GList *newbuddy)
-{
-  enum imstatus prev_st = imstatus_size;
-  /* prev_st initialized to imstatus_size, which is used as "undef" value.
-   * We are sure prev_st will get a different status value after the
-   * buddy_getstatus() call.
-   */
-
-  if (!current_buddy || !newbuddy)  return;
-  if (newbuddy == current_buddy)    return;
-
-  // We're moving to another buddy.  We're thus inactive wrt current_buddy.
-  set_chatstate(0);
-  // We don't want the chatstate to be changed again right now.
-  lock_chatstate = TRUE;
-
-  prev_st = buddy_getstatus(BUDDATA(current_buddy), NULL);
-  buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
-  if (chatmode)
-    alternate_buddy = current_buddy;
-  current_buddy = newbuddy;
-  // Lock the buddy in the buddylist if we're in chat mode
-  if (chatmode)
-    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, TRUE);
-  // We should rebuild the buddylist but not everytime
-  // Here we check if we were locking a buddy who is actually offline,
-  // and hide_offline_buddies is TRUE.  In which case we need to rebuild.
-  if (!(buddylist_get_filter() & 1<<prev_st))
-    buddylist_build();
-  update_roster = TRUE;
-}
-
-//  scr_RosterTop()
-// Go to the first buddy in the buddylist
-void scr_RosterTop(void)
-{
-  set_current_buddy(buddylist);
-  if (chatmode)
-    scr_ShowBuddyWindow();
-}
-
-//  scr_RosterBottom()
-// Go to the last buddy in the buddylist
-void scr_RosterBottom(void)
-{
-  set_current_buddy(g_list_last(buddylist));
-  if (chatmode)
-    scr_ShowBuddyWindow();
-}
-
-//  scr_RosterUpDown(updown, n)
-// Go to the nth next buddy in the buddylist
-// (up if updown == -1, down if updown == 1)
-void scr_RosterUpDown(int updown, unsigned int n)
-{
-  unsigned int i;
-
-  if (updown < 0) {
-    for (i = 0; i < n; i++)
-      set_current_buddy(g_list_previous(current_buddy));
-  } else {
-    for (i = 0; i < n; i++)
-      set_current_buddy(g_list_next(current_buddy));
-  }
-  if (chatmode)
-    scr_ShowBuddyWindow();
-}
-
-//  scr_RosterPrevGroup()
-// Go to the previous group in the buddylist
-void scr_RosterPrevGroup(void)
-{
-  GList *bud;
-
-  for (bud = current_buddy ; bud ; ) {
-    bud = g_list_previous(bud);
-    if (!bud)
-      break;
-    if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
-      set_current_buddy(bud);
-      if (chatmode)
-        scr_ShowBuddyWindow();
-      break;
-    }
-  }
-}
-
-//  scr_RosterNextGroup()
-// Go to the next group in the buddylist
-void scr_RosterNextGroup(void)
-{
-  GList *bud;
-
-  for (bud = current_buddy ; bud ; ) {
-    bud = g_list_next(bud);
-    if (!bud)
-      break;
-    if (buddy_gettype(BUDDATA(bud)) & ROSTER_TYPE_GROUP) {
-      set_current_buddy(bud);
-      if (chatmode)
-        scr_ShowBuddyWindow();
-      break;
-    }
-  }
-}
-
-//  scr_RosterSearch(str)
-// Look forward for a buddy with jid/name containing str.
-void scr_RosterSearch(char *str)
-{
-  set_current_buddy(buddy_search(str));
-  if (chatmode)
-    scr_ShowBuddyWindow();
-}
-
-//  scr_RosterJumpJid(bjid)
-// Jump to buddy bjid.
-// NOTE: With this function, the buddy is added to the roster if doesn't exist.
-void scr_RosterJumpJid(char *barejid)
-{
-  GSList *roster_elt;
-  // Look for an existing buddy
-  roster_elt = roster_find(barejid, jidsearch,
-                 ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
-  // Create it if necessary
-  if (!roster_elt)
-    roster_elt = roster_add_user(barejid, NULL, NULL, ROSTER_TYPE_USER,
-                                 sub_none, -1);
-  // Set a lock to see it in the buddylist
-  buddy_setflags(BUDDATA(roster_elt), ROSTER_FLAG_LOCK, TRUE);
-  buddylist_build();
-  // Jump to the buddy
-  set_current_buddy(buddy_search_jid(barejid));
-  if (chatmode)
-    scr_ShowBuddyWindow();
-}
-
-//  scr_RosterUnreadMessage(next)
-// Go to a new message.  If next is not null, try to go to the next new
-// message.  If it is not possible or if next is NULL, go to the first new
-// message from unread_list.
-void scr_RosterUnreadMessage(int next)
-{
-  gpointer unread_ptr;
-  gpointer refbuddata;
-  GList *nbuddy;
-
-  if (!current_buddy) return;
-
-  if (next) refbuddata = BUDDATA(current_buddy);
-  else      refbuddata = NULL;
-
-  unread_ptr = unread_msg(refbuddata);
-  if (!unread_ptr) return;
-
-  if (!(buddy_gettype(unread_ptr) & ROSTER_TYPE_SPECIAL)) {
-    gpointer ngroup;
-    // If buddy is in a folded group, we need to expand it
-    ngroup = buddy_getgroup(unread_ptr);
-    if (buddy_getflags(ngroup) & ROSTER_FLAG_HIDE) {
-      buddy_setflags(ngroup, ROSTER_FLAG_HIDE, FALSE);
-      buddylist_build();
-    }
-  }
-
-  nbuddy = g_list_find(buddylist, unread_ptr);
-  if (nbuddy) {
-    set_current_buddy(nbuddy);
-    if (chatmode) scr_ShowBuddyWindow();
-  } else
-    scr_LogPrint(LPRINT_LOGNORM, "Error: nbuddy == NULL"); // should not happen
-}
-
-//  scr_RosterJumpAlternate()
-// Try to jump to alternate (== previous) buddy
-void scr_RosterJumpAlternate(void)
-{
-  if (!alternate_buddy || g_list_position(buddylist, alternate_buddy) == -1)
-    return;
-  set_current_buddy(alternate_buddy);
-  if (chatmode)
-    scr_ShowBuddyWindow();
-}
-
-//  scr_RosterDisplay(filter)
-// Set the roster filter mask.  If filter is null/empty, the current
-// mask is displayed.
-void scr_RosterDisplay(const char *filter)
-{
-  guchar status;
-  enum imstatus budstate;
-  char strfilter[imstatus_size+1];
-  char *psfilter;
-
-  if (filter && *filter) {
-    int show_all = (*filter == '*');
-    status = 0;
-    for (budstate = 0; budstate < imstatus_size-1; budstate++)
-      if (strchr(filter, imstatus2char[budstate]) || show_all)
-        status |= 1<<budstate;
-    buddylist_set_filter(status);
-    buddylist_build();
-    update_roster = TRUE;
-    return;
-  }
-
-  // Display current filter
-  psfilter = strfilter;
-  status = buddylist_get_filter();
-  for (budstate = 0; budstate < imstatus_size-1; budstate++)
-    if (status & 1<<budstate)
-      *psfilter++ = imstatus2char[budstate];
-  *psfilter = '\0';
-  scr_LogPrint(LPRINT_NORMAL, "Roster status filter: %s", strfilter);
-}
-
-//  scr_BufferScrollUpDown()
-// Scroll up/down the current buddy window,
-// - half a screen if nblines is 0,
-// - up if updown == -1, down if updown == 1
-void scr_BufferScrollUpDown(int updown, unsigned int nblines)
-{
-  winbuf *win_entry;
-  int n, nbl;
-  GList *hbuf_top;
-  guint isspe;
-
-  // Get win_entry
-  if (!current_buddy) return;
-
-  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  win_entry  = scr_SearchWindow(CURRENT_JID, isspe);
-  if (!win_entry) return;
-
-  if (!nblines) {
-    // Scroll half a screen (or less)
-    nbl = CHAT_WIN_HEIGHT/2;
-  } else {
-    nbl = nblines;
-  }
-  hbuf_top = win_entry->bd->top;
-
-  if (updown == -1) {   // UP
-    if (!hbuf_top) {
-      hbuf_top = g_list_last(win_entry->bd->hbuf);
-      if (!win_entry->bd->cleared) {
-        if (!nblines) nbl = nbl*3 - 1;
-        else nbl += CHAT_WIN_HEIGHT - 1;
-      } else {
-        win_entry->bd->cleared = FALSE;
-      }
-    }
-    for (n=0 ; hbuf_top && n < nbl && g_list_previous(hbuf_top) ; n++)
-      hbuf_top = g_list_previous(hbuf_top);
-    win_entry->bd->top = hbuf_top;
-  } else {              // DOWN
-    for (n=0 ; hbuf_top && n < nbl ; n++)
-      hbuf_top = g_list_next(hbuf_top);
-    win_entry->bd->top = hbuf_top;
-    // Check if we are at the bottom
-    for (n=0 ; hbuf_top && n < CHAT_WIN_HEIGHT-1 ; n++)
-      hbuf_top = g_list_next(hbuf_top);
-    if (!hbuf_top)
-      win_entry->bd->top = NULL; // End reached
-  }
-
-  // Refresh the window
-  scr_UpdateWindow(win_entry);
-
-  // Finished :)
-  update_panels();
-}
-
-//  scr_BufferClear()
-// Clear the current buddy window (used for the /clear command)
-void scr_BufferClear(void)
-{
-  winbuf *win_entry;
-  guint isspe;
-
-  // Get win_entry
-  if (!current_buddy) return;
-  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
-  if (!win_entry) return;
-
-  win_entry->bd->cleared = TRUE;
-  win_entry->bd->top = NULL;
-
-  // Refresh the window
-  scr_UpdateWindow(win_entry);
-
-  // Finished :)
-  update_panels();
-}
-
-//  buffer_purge()
-// key: winId/jid
-// value: winbuf structure
-// data: int, set to 1 if the buffer should be closed.
-// NOTE: does not work for special buffers.
-static void buffer_purge(gpointer key, gpointer value, gpointer data)
-{
-  int *p_closebuf = data;
-  winbuf *win_entry = value;
-
-  // Delete the current hbuf
-  hbuf_free(&win_entry->bd->hbuf);
-
-  if (*p_closebuf) {
-    g_hash_table_remove(winbufhash, key);
-  } else {
-    win_entry->bd->cleared = FALSE;
-    win_entry->bd->top = NULL;
-  }
-}
-
-//  scr_BufferPurge(closebuf, jid)
-// Purge/Drop the current buddy buffer or jid's buffer if jid != NULL.
-// If closebuf is 1, close the buffer.
-void scr_BufferPurge(int closebuf, const char *jid)
-{
-  winbuf *win_entry;
-  guint isspe;
-  guint *p_closebuf;
-  const char *cjid;
-  guint hold_chatmode = FALSE;
-
-  if (jid) {
-    cjid = jid;
-    isspe = FALSE;
-    // If closebuf is TRUE, it's probably better not to leave chat mode
-    // if the change isn't related to the current buffer.
-    if (closebuf && current_buddy) {
-      if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL ||
-          strcasecmp(jid, CURRENT_JID))
-        hold_chatmode = TRUE;
-    }
-  } else {
-    // Get win_entry
-    if (!current_buddy) return;
-    cjid = CURRENT_JID;
-    isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  }
-  win_entry = scr_SearchWindow(cjid, isspe);
-  if (!win_entry) return;
-
-  if (!isspe) {
-    p_closebuf = g_new(guint, 1);
-    *p_closebuf = closebuf;
-    buffer_purge((gpointer)cjid, win_entry, p_closebuf);
-    g_free(p_closebuf);
-    if (closebuf && !hold_chatmode) {
-      scr_set_chatmode(FALSE);
-      currentWindow = NULL;
-    }
-  } else {
-    // (Special buffer)
-    // Reset the current hbuf
-    hbuf_free(&win_entry->bd->hbuf);
-    // Currently it can only be the status buffer
-    statushbuf = NULL;
-
-    win_entry->bd->cleared = FALSE;
-    win_entry->bd->top = NULL;
-  }
-
-  // Refresh the window
-  scr_UpdateBuddyWindow();
-
-  // Finished :)
-  update_panels();
-}
-
-void scr_BufferPurgeAll(int closebuf)
-{
-  guint *p_closebuf;
-  p_closebuf = g_new(guint, 1);
-
-  *p_closebuf = closebuf;
-  g_hash_table_foreach(winbufhash, buffer_purge, p_closebuf);
-  g_free(p_closebuf);
-
-  if (closebuf) {
-    scr_set_chatmode(FALSE);
-    currentWindow = NULL;
-  }
-
-  // Refresh the window
-  scr_UpdateBuddyWindow();
-
-  // Finished :)
-  update_panels();
-}
-
-//  scr_BufferScrollLock(lock)
-// Lock/unlock the current buddy buffer
-// lock = 1 : lock
-// lock = 0 : unlock
-// lock = -1: toggle lock status
-void scr_BufferScrollLock(int lock)
-{
-  winbuf *win_entry;
-  guint isspe;
-
-  // Get win_entry
-  if (!current_buddy) return;
-  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
-  if (!win_entry) return;
-
-  if (lock == -1)
-    lock = !win_entry->bd->lock;
-
-  if (lock) {
-    win_entry->bd->lock = TRUE;
-  } else {
-    win_entry->bd->lock = FALSE;
-    //win_entry->bd->cleared = FALSE;
-    if (isspe || (buddy_getflags(BUDDATA(current_buddy)) & ROSTER_FLAG_MSG))
-      win_entry->bd->top = NULL;
-  }
-
-  // If chatmode is disabled and we're at the bottom of the buffer,
-  // we need to set the "top" line, so we need to call scr_ShowBuddyWindow()
-  // at least once.  (Maybe it will cause a double refresh...)
-  if (!chatmode && !win_entry->bd->top) {
-    chatmode = TRUE;
-    scr_ShowBuddyWindow();
-    chatmode = FALSE;
-  }
-
-  // Refresh the window
-  scr_UpdateBuddyWindow();
-
-  // Finished :)
-  update_panels();
-}
-
-//  scr_BufferTopBottom()
-// Jump to the head/tail of the current buddy window
-// (top if topbottom == -1, bottom topbottom == 1)
-void scr_BufferTopBottom(int topbottom)
-{
-  winbuf *win_entry;
-  guint isspe;
-
-  // Get win_entry
-  if (!current_buddy) return;
-  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
-  if (!win_entry) return;
-
-  win_entry->bd->cleared = FALSE;
-  if (topbottom == 1)
-    win_entry->bd->top = NULL;
-  else
-    win_entry->bd->top = g_list_first(win_entry->bd->hbuf);
-
-  // Refresh the window
-  scr_UpdateWindow(win_entry);
-
-  // Finished :)
-  update_panels();
-}
-
-//  scr_BufferSearch(direction, text)
-// Jump to the next line containing text
-// (backward search if direction == -1, forward if topbottom == 1)
-void scr_BufferSearch(int direction, const char *text)
-{
-  winbuf *win_entry;
-  GList *current_line, *search_res;
-  guint isspe;
-
-  // Get win_entry
-  if (!current_buddy) return;
-  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
-  if (!win_entry) return;
-
-  if (win_entry->bd->top)
-    current_line = win_entry->bd->top;
-  else
-    current_line = g_list_last(win_entry->bd->hbuf);
-
-  search_res = hbuf_search(current_line, direction, text);
-
-  if (search_res) {
-    win_entry->bd->cleared = FALSE;
-    win_entry->bd->top = search_res;
-
-    // Refresh the window
-    scr_UpdateWindow(win_entry);
-
-    // Finished :)
-    update_panels();
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Search string not found");
-}
-
-//  scr_BufferPercent(n)
-// Jump to the specified position in the buffer, in %
-void scr_BufferPercent(int pc)
-{
-  winbuf *win_entry;
-  GList *search_res;
-  guint isspe;
-
-  // Get win_entry
-  if (!current_buddy) return;
-  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
-  if (!win_entry) return;
-
-  if (pc < 0 || pc > 100) {
-    scr_LogPrint(LPRINT_NORMAL, "Bad % value");
-    return;
-  }
-
-  search_res = hbuf_jump_percent(win_entry->bd->hbuf, pc);
-
-  win_entry->bd->cleared = FALSE;
-  win_entry->bd->top = search_res;
-
-  // Refresh the window
-  scr_UpdateWindow(win_entry);
-
-  // Finished :)
-  update_panels();
-}
-
-//  scr_BufferDate(t)
-// Jump to the first line after date t in the buffer
-// t is a date in seconds since `00:00:00 1970-01-01 UTC'
-void scr_BufferDate(time_t t)
-{
-  winbuf *win_entry;
-  GList *search_res;
-  guint isspe;
-
-  // Get win_entry
-  if (!current_buddy) return;
-  isspe = buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_SPECIAL;
-  win_entry = scr_SearchWindow(CURRENT_JID, isspe);
-  if (!win_entry) return;
-
-  search_res = hbuf_jump_date(win_entry->bd->hbuf, t);
-
-  win_entry->bd->cleared = FALSE;
-  win_entry->bd->top = search_res;
-
-  // Refresh the window
-  scr_UpdateWindow(win_entry);
-
-  // Finished :)
-  update_panels();
-}
-
-void scr_BufferDump(const char *file)
-{
-  char *extfname;
-
-  if (!currentWindow) {
-    scr_LogPrint(LPRINT_NORMAL, "No current buffer!");
-    return;
-  }
-
-  if (!file || !*file) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter (file name)!");
-    return;
-  }
-
-  extfname = expand_filename(file);
-  hbuf_dump_to_file(currentWindow->bd->hbuf, extfname);
-  g_free(extfname);
-}
-
-//  buffer_list()
-// key: winId/jid
-// value: winbuf structure
-// data: none.
-static void buffer_list(gpointer key, gpointer value, gpointer data)
-{
-  GList *head;
-  winbuf *win_entry = value;
-
-  head = g_list_first(win_entry->bd->hbuf);
-
-  scr_LogPrint(LPRINT_NORMAL, " %s  (%u/%u)", key,
-               g_list_length(head), hbuf_get_blocks_number(head));
-}
-
-void scr_BufferList(void)
-{
-  scr_LogPrint(LPRINT_NORMAL, "Buffer list:");
-  buffer_list("[status]", statusWindow, NULL);
-  g_hash_table_foreach(winbufhash, buffer_list, NULL);
-  scr_LogPrint(LPRINT_NORMAL, "End of buffer list.");
-  scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
-  update_roster = TRUE;
-}
-
-//  scr_set_chatmode()
-// Public function to (un)set chatmode...
-inline void scr_set_chatmode(int enable)
-{
-  chatmode = enable;
-  scr_UpdateChatStatus(TRUE);
-}
-
-//  scr_get_chatmode()
-// Public function to get chatmode state.
-inline int scr_get_chatmode(void)
-{
-  return chatmode;
-}
-
-//  scr_get_multimode()
-// Public function to get multimode status...
-inline int scr_get_multimode(void)
-{
-  return multimode;
-}
-
-//  scr_setmsgflag_if_needed(jid)
-// Set the message flag unless we're already in the jid buffer window
-void scr_setmsgflag_if_needed(const char *bjid, int special)
-{
-  const char *current_id;
-  bool iscurrentlocked = FALSE;
-
-  if (!bjid)
-    return;
-
-  if (current_buddy) {
-    if (special)
-      current_id = buddy_getname(BUDDATA(current_buddy));
-    else
-      current_id = buddy_getjid(BUDDATA(current_buddy));
-    if (current_id) {
-      winbuf *win_entry = scr_SearchWindow(current_id, special);
-      if (!win_entry) return;
-      iscurrentlocked = win_entry->bd->lock;
-    }
-  } else {
-    current_id = NULL;
-  }
-  if (!chatmode || !current_id || strcmp(bjid, current_id) || iscurrentlocked)
-    roster_msg_setflag(bjid, special, TRUE);
-}
-
-//  scr_set_multimode()
-// Public function to (un)set multimode...
-// Convention:
-//  0 = disabled / 1 = multimode / 2 = multimode verbatim (commands disabled)
-void scr_set_multimode(int enable, char *subject)
-{
-  g_free(multiline);
-  multiline = NULL;
-
-  g_free(multimode_subj);
-  if (enable && subject)
-    multimode_subj = g_strdup(subject);
-  else
-    multimode_subj = NULL;
-
-  multimode = enable;
-}
-
-//  scr_get_multiline()
-// Public function to get the current multi-line.
-const char *scr_get_multiline(void)
-{
-  if (multimode && multiline)
-    return multiline;
-  return NULL;
-}
-
-//  scr_get_multimode_subj()
-// Public function to get the multi-line subject, if any.
-const char *scr_get_multimode_subj(void)
-{
-  if (multimode)
-    return multimode_subj;
-  return NULL;
-}
-
-//  scr_append_multiline(line)
-// Public function to append a line to the current multi-line message.
-// Skip empty leading lines.
-void scr_append_multiline(const char *line)
-{
-  static int num;
-
-  if (!multimode) {
-    scr_LogPrint(LPRINT_NORMAL, "Error: Not in multi-line message mode!");
-    return;
-  }
-  if (multiline) {
-    int len = strlen(multiline)+strlen(line)+2;
-    if (len >= HBB_BLOCKSIZE - 1) {
-      // We don't handle single messages with size > HBB_BLOCKSIZE
-      // (see hbuf)
-      scr_LogPrint(LPRINT_NORMAL, "Your multi-line message is too big, "
-                   "this line has not been added.");
-      scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
-      return;
-    }
-    if (num >= MULTILINE_MAX_LINE_NUMBER) {
-      // We don't allow too many lines; however the maximum is arbitrary
-      // (It should be < 1000 yet)
-      scr_LogPrint(LPRINT_NORMAL, "Your message has too many lines, "
-                   "this one has not been added.");
-      scr_LogPrint(LPRINT_NORMAL, "Please send this part now...");
-      return;
-    }
-    multiline = g_renew(char, multiline, len);
-    strcat(multiline, "\n");
-    strcat(multiline, line);
-    num++;
-  } else {
-    // First message line (we skip leading empty lines)
-    num = 0;
-    if (line[0]) {
-      multiline = g_strdup(line);
-      num++;
-    } else
-      return;
-  }
-  scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-               "Multi-line mode: line #%d added  [%.25s...", num, line);
-}
-
-//  scr_cmdhisto_addline()
-// Add a line to the inputLine history
-static inline void scr_cmdhisto_addline(char *line)
-{
-  int max_histo_lines;
-
-  if (!line || !*line)
-    return;
-
-  max_histo_lines = settings_opt_get_int("cmdhistory_lines");
-
-  if (max_histo_lines < 0)
-    max_histo_lines = 1;
-
-  if (max_histo_lines)
-    while (cmdhisto_nblines >= (guint)max_histo_lines) {
-      if (cmdhisto_cur && cmdhisto_cur == cmdhisto)
-        break;
-      g_free(cmdhisto->data);
-      cmdhisto = g_list_delete_link(cmdhisto, cmdhisto);
-      cmdhisto_nblines--;
-    }
-
-  cmdhisto = g_list_append(cmdhisto, g_strdup(line));
-  cmdhisto_nblines++;
-}
-
-//  scr_cmdhisto_prev()
-// Look for previous line beginning w/ the given mask in the inputLine history
-// Returns NULL if none found
-static const char *scr_cmdhisto_prev(char *mask, guint len)
-{
-  GList *hl;
-  if (!cmdhisto_cur) {
-    hl = g_list_last(cmdhisto);
-    if (hl) { // backup current line
-      strncpy(cmdhisto_backup, mask, INPUTLINE_LENGTH);
-    }
-  } else {
-    hl = g_list_previous(cmdhisto_cur);
-  }
-  while (hl) {
-    if (!strncmp((char*)hl->data, mask, len)) {
-      // Found a match
-      cmdhisto_cur = hl;
-      return (const char*)hl->data;
-    }
-    hl = g_list_previous(hl);
-  }
-  return NULL;
-}
-
-//  scr_cmdhisto_next()
-// Look for next line beginning w/ the given mask in the inputLine history
-// Returns NULL if none found
-static const char *scr_cmdhisto_next(char *mask, guint len)
-{
-  GList *hl;
-  if (!cmdhisto_cur) return NULL;
-  hl = cmdhisto_cur;
-  while ((hl = g_list_next(hl)) != NULL)
-    if (!strncmp((char*)hl->data, mask, len)) {
-      // Found a match
-      cmdhisto_cur = hl;
-      return (const char*)hl->data;
-    }
-  // If the "backuped" line matches, we'll use it
-  if (strncmp(cmdhisto_backup, mask, len)) return NULL; // No match
-  cmdhisto_cur = NULL;
-  return cmdhisto_backup;
-}
-
-//  readline_transpose_chars()
-// Drag  the  character  before point forward over the character at
-// point, moving point forward as well.  If point is at the end  of
-// the  line, then this transposes the two characters before point.
-void readline_transpose_chars(void)
-{
-  char *c1, *c2;
-  unsigned a, b;
-
-  if (ptr_inputline == inputLine) return;
-
-  if (!*ptr_inputline) { // We're at EOL
-    // If line is only 1 char long, nothing to do...
-    if (ptr_inputline == prev_char(ptr_inputline, inputLine)) return;
-    // Transpose the two previous characters
-    c2 = prev_char(ptr_inputline, inputLine);
-    c1 = prev_char(c2, inputLine);
-    a = get_char(c1);
-    b = get_char(c2);
-    put_char(put_char(c1, b), a);
-  } else {
-    // Swap the two characters before the cursor and move right.
-    c2 = ptr_inputline;
-    c1 = prev_char(c2, inputLine);
-    a = get_char(c1);
-    b = get_char(c2);
-    put_char(put_char(c1, b), a);
-    check_offset(1);
-  }
-}
-
-void readline_forward_kill_word(void)
-{
-  char *c, *old = ptr_inputline;
-  int spaceallowed = 1;
-
-  if (! *ptr_inputline) return;
-
-  for (c = ptr_inputline ; *c ; c = next_char(c)) {
-    if (!iswalnum(get_char(c))) {
-      if (iswblank(get_char(c))) {
-        if (!spaceallowed) break;
-      } else spaceallowed = 0;
-    } else spaceallowed = 0;
-  }
-
-  // Modify the line
-  for (;;) {
-    *old = *c++;
-    if (!*old++) break;
-  }
-}
-
-//  readline_backward_kill_word()
-// Kill the word before the cursor, in input line
-void readline_backward_kill_word(void)
-{
-  char *c, *old = ptr_inputline;
-  int spaceallowed = 1;
-
-  if (ptr_inputline == inputLine) return;
-
-  c = prev_char(ptr_inputline, inputLine);
-  for ( ; c > inputLine ; c = prev_char(c, inputLine)) {
-    if (!iswalnum(get_char(c))) {
-      if (iswblank(get_char(c))) {
-        if (!spaceallowed) break;
-      } else spaceallowed = 0;
-    } else spaceallowed = 0;
-  }
-
-  if (c == inputLine && *c == COMMAND_CHAR && old != c+1) {
-      c = next_char(c);
-  } else if (c != inputLine || iswblank(get_char(c))) {
-    if ((c < prev_char(ptr_inputline, inputLine)) && (!iswalnum(get_char(c))))
-      c = next_char(c);
-  }
-
-  // Modify the line
-  ptr_inputline = c;
-  for (;;) {
-    *c = *old++;
-    if (!*c++) break;
-  }
-  check_offset(-1);
-}
-
-//  readline_backward_word()
-// Move  back  to the start of the current or previous word
-void readline_backward_word(void)
-{
-  int i = 0;
-
-  if (ptr_inputline == inputLine) return;
-
-  if (iswalnum(get_char(ptr_inputline)) &&
-      !iswalnum(get_char(prev_char(ptr_inputline, inputLine))))
-    i--;
-
-  for ( ;
-       ptr_inputline > inputLine;
-       ptr_inputline = prev_char(ptr_inputline, inputLine)) {
-    if (!iswalnum(get_char(ptr_inputline))) {
-      if (i) {
-        ptr_inputline = next_char(ptr_inputline);
-        break;
-      }
-    } else i++;
-  }
-
-  check_offset(-1);
-}
-
-//  readline_forward_word()
-// Move forward to the end of the next word
-void readline_forward_word(void)
-{
-  int stopsymbol_allowed = 1;
-
-  while (*ptr_inputline) {
-    if (!iswalnum(get_char(ptr_inputline))) {
-      if (!stopsymbol_allowed) break;
-    } else stopsymbol_allowed = 0;
-    ptr_inputline = next_char(ptr_inputline);
-  }
-
-  check_offset(1);
-}
-
-void readline_updowncase_word(int upcase)
-{
-  int stopsymbol_allowed = 1;
-
-  while (*ptr_inputline) {
-    if (!iswalnum(get_char(ptr_inputline))) {
-      if (!stopsymbol_allowed) break;
-    } else {
-      stopsymbol_allowed = 0;
-      if (upcase)
-        *ptr_inputline = towupper(get_char(ptr_inputline));
-      else
-        *ptr_inputline = towlower(get_char(ptr_inputline));
-    }
-    ptr_inputline = next_char(ptr_inputline);
-  }
-
-  check_offset(1);
-}
-
-void readline_capitalize_word(void)
-{
-  int stopsymbol_allowed = 1;
-  int upcased = 0;
-
-  while (*ptr_inputline) {
-    if (!iswalnum(get_char(ptr_inputline))) {
-      if (!stopsymbol_allowed) break;
-    } else {
-      stopsymbol_allowed = 0;
-      if (!upcased) {
-        *ptr_inputline = towupper(get_char(ptr_inputline));
-        upcased = 1;
-      } else *ptr_inputline = towlower(get_char(ptr_inputline));
-    }
-    ptr_inputline = next_char(ptr_inputline);
-  }
-
-  check_offset(1);
-}
-
-void readline_backward_char(void)
-{
-  if (ptr_inputline == (char*)&inputLine) return;
-
-  ptr_inputline = prev_char(ptr_inputline, inputLine);
-  check_offset(-1);
-}
-
-void readline_forward_char(void)
-{
-  if (!*ptr_inputline) return;
-
-  ptr_inputline = next_char(ptr_inputline);
-  check_offset(1);
-}
-
-//  readline_accept_line(down_history)
-// Validate current command line.
-// If down_history is true, load the next history line.
-int readline_accept_line(int down_history)
-{
-  scr_CheckAutoAway(TRUE);
-  if (process_line(inputLine))
-    return 255;
-  // Add line to history
-  scr_cmdhisto_addline(inputLine);
-  // Reset the line
-  ptr_inputline = inputLine;
-  *ptr_inputline = 0;
-  inputline_offset = 0;
-
-  if (down_history) {
-    // Use next history line instead of a blank line
-    const char *l = scr_cmdhisto_next("", 0);
-    if (l) strcpy(inputLine, l);
-    // Reset backup history line
-    cmdhisto_backup[0] = 0;
-  } else {
-    // Reset history line pointer
-    cmdhisto_cur = NULL;
-  }
-  return 0;
-}
-
-void readline_cancel_completion(void)
-{
-  scr_cancel_current_completion();
-  scr_end_current_completion();
-  check_offset(-1);
-}
-
-void readline_do_completion(void)
-{
-  int i, n;
-
-  if (multimode != 2) {
-    // Not in verbatim multi-line mode
-    scr_handle_tab();
-  } else {
-    // Verbatim multi-line mode: expand tab
-    char tabstr[9];
-    n = 8 - (ptr_inputline - inputLine) % 8;
-    for (i = 0; i < n; i++)
-      tabstr[i] = ' ';
-    tabstr[i] = '\0';
-    scr_insert_text(tabstr);
-  }
-  check_offset(0);
-}
-
-void readline_refresh_screen(void)
-{
-  scr_CheckAutoAway(TRUE);
-  ParseColors();
-  scr_Resize();
-  redrawwin(stdscr);
-}
-
-void readline_disable_chat_mode(guint show_roster)
-{
-  scr_CheckAutoAway(TRUE);
-  currentWindow = NULL;
-  chatmode = FALSE;
-  if (current_buddy)
-    buddy_setflags(BUDDATA(current_buddy), ROSTER_FLAG_LOCK, FALSE);
-  if (show_roster)
-    scr_RosterVisibility(1);
-  scr_UpdateChatStatus(FALSE);
-  top_panel(chatPanel);
-  top_panel(inputPanel);
-  update_panels();
-}
-
-void readline_hist_beginning_search_bwd(void)
-{
-  const char *l = scr_cmdhisto_prev(inputLine, ptr_inputline-inputLine);
-  if (l) strcpy(inputLine, l);
-}
-
-void readline_hist_beginning_search_fwd(void)
-{
-  const char *l = scr_cmdhisto_next(inputLine, ptr_inputline-inputLine);
-  if (l) strcpy(inputLine, l);
-}
-
-void readline_hist_prev(void)
-{
-  const char *l = scr_cmdhisto_prev(inputLine, 0);
-  if (l) {
-    strcpy(inputLine, l);
-    // Set the pointer at the EOL.
-    // We have to move it to BOL first, because we could be too far already.
-    readline_iline_start();
-    readline_iline_end();
-  }
-}
-
-void readline_hist_next(void)
-{
-  const char *l = scr_cmdhisto_next(inputLine, 0);
-  if (l) {
-    strcpy(inputLine, l);
-    // Set the pointer at the EOL.
-    // We have to move it to BOL first, because we could be too far already.
-    readline_iline_start();
-    readline_iline_end();
-  }
-}
-
-void readline_backward_kill_char(void)
-{
-  char *src, *c;
-
-  if (ptr_inputline == (char*)&inputLine)
-    return;
-
-  src = ptr_inputline;
-  c = prev_char(ptr_inputline, inputLine);
-  ptr_inputline = c;
-  for ( ; *src ; )
-    *c++ = *src++;
-  *c = 0;
-  check_offset(-1);
-}
-
-void readline_forward_kill_char(void)
-{
-  if (!*ptr_inputline)
-    return;
-
-  strcpy(ptr_inputline, next_char(ptr_inputline));
-}
-
-void readline_iline_start(void)
-{
-  ptr_inputline = inputLine;
-  inputline_offset = 0;
-}
-
-void readline_iline_end(void)
-{
-  for (; *ptr_inputline; ptr_inputline++) ;
-  check_offset(1);
-}
-
-void readline_backward_kill_iline(void)
-{
-  strcpy(inputLine, ptr_inputline);
-  ptr_inputline = inputLine;
-  inputline_offset = 0;
-}
-
-void readline_forward_kill_iline(void)
-{
-  *ptr_inputline = 0;
-}
-
-void readline_send_multiline(void)
-{
-  // Validate current multi-line
-  if (multimode)
-    process_command(mkcmdstr("msay send"), TRUE);
-}
-
-//  which_row()
-// Tells which row our cursor is in, in the command line.
-// -2 -> normal text
-// -1 -> room: nickname completion
-//  0 -> command
-//  1 -> parameter 1 (etc.)
-//  If > 0, then *p_row is set to the beginning of the row
-static int which_row(const char **p_row)
-{
-  int row = -1;
-  char *p;
-  int quote = FALSE;
-
-  // Not a command?
-  if ((ptr_inputline == inputLine) || (inputLine[0] != COMMAND_CHAR)) {
-    if (!current_buddy) return -2;
-    if (buddy_gettype(BUDDATA(current_buddy)) == ROSTER_TYPE_ROOM) {
-      *p_row = inputLine;
-      return -1;
-    }
-    return -2;
-  }
-
-  // This is a command
-  row = 0;
-  for (p = inputLine ; p < ptr_inputline ; p = next_char(p)) {
-    if (quote) {
-      if (*p == '"' && *(p-1) != '\\')
-        quote = FALSE;
-      continue;
-    }
-    if (*p == '"' && *(p-1) != '\\') {
-      quote = TRUE;
-    } else if (*p == ' ') {
-      if (*(p-1) != ' ')
-        row++;
-      *p_row = p+1;
-    }
-  }
-  return row;
-}
-
-//  scr_insert_text()
-// Insert the given text at the current cursor position.
-// The cursor is moved.  We don't check if the cursor still is in the screen
-// after, the caller should do that.
-static void scr_insert_text(const char *text)
-{
-  char tmpLine[INPUTLINE_LENGTH+1];
-  int len = strlen(text);
-  // Check the line isn't too long
-  if (strlen(inputLine) + len >= INPUTLINE_LENGTH) {
-    scr_LogPrint(LPRINT_LOGNORM, "Cannot insert text, line too long.");
-    return;
-  }
-
-  strcpy(tmpLine, ptr_inputline);
-  strcpy(ptr_inputline, text);
-  ptr_inputline += len;
-  strcpy(ptr_inputline, tmpLine);
-}
-
-static void scr_cancel_current_completion(void);
-
-//  scr_handle_tab()
-// Function called when tab is pressed.
-// Initiate or continue a completion...
-static void scr_handle_tab(void)
-{
-  int nrow;
-  const char *row;
-  const char *cchar;
-  guint compl_categ;
-
-  row = inputLine; // (Kills a GCC warning)
-  nrow = which_row(&row);
-
-  // a) No completion if no leading slash ('cause not a command),
-  //    unless this is a room (then, it is a nickname completion)
-  // b) We can't have more than 2 parameters (we use 2 flags)
-  if ((nrow == -2) || (nrow == 3 && !completion_started) || nrow > 3)
-    return;
-
-  if (nrow == 0) {          // Command completion
-    row = next_char(inputLine);
-    compl_categ = COMPL_CMD;
-  } else if (nrow == -1) {  // Nickname completion
-    compl_categ = COMPL_RESOURCE;
-  } else {                  // Other completion, depending on the command
-    int alias = FALSE;
-    cmd *com;
-    char *xpline = expandalias(inputLine);
-    com = cmd_get(xpline);
-    if (xpline != inputLine) {
-      // This is an alias, so we can't complete rows > 0
-      alias = TRUE;
-      g_free(xpline);
-    }
-    if ((!com && (!alias || !completion_started)) || !row) {
-      scr_LogPrint(LPRINT_NORMAL, "I cannot complete that...");
-      return;
-    }
-    if (!alias)
-      compl_categ = com->completion_flags[nrow-1];
-    else
-      compl_categ = 0;
-  }
-
-  if (!completion_started) {
-    guint dynlist;
-    GSList *list = compl_get_category_list(compl_categ, &dynlist);
-    if (list) {
-      guint n;
-      char *prefix = g_strndup(row, ptr_inputline-row);
-      // Init completion
-      n = new_completion(prefix, list);
-      g_free(prefix);
-      if (n == 0 && nrow == -1) {
-        // This is a MUC room and we can't complete from the beginning of the
-        // line.  Let's try a bit harder and complete the current word.
-        row = prev_char(ptr_inputline, inputLine);
-        while (row >= inputLine) {
-          if (iswspace(get_char(row)) || get_char(row) == '(') {
-              row = next_char((char*)row);
-              break;
-          }
-          if (row == inputLine)
-            break;
-          row = prev_char((char*)row, inputLine);
-        }
-        // There's no need to try again if row == inputLine
-        if (row > inputLine) {
-          prefix = g_strndup(row, ptr_inputline-row);
-          new_completion(prefix, list);
-          g_free(prefix);
-        }
-      }
-      // Free the list if it's a dynamic one
-      if (dynlist) {
-        GSList *slp;
-        for (slp = list; slp; slp = g_slist_next(slp))
-          g_free(slp->data);
-        g_slist_free(list);
-      }
-      // Now complete
-      cchar = complete();
-      if (cchar)
-        scr_insert_text(cchar);
-      completion_started = TRUE;
-    }
-  } else {      // Completion already initialized
-    scr_cancel_current_completion();
-    // Now complete again
-    cchar = complete();
-    if (cchar)
-      scr_insert_text(cchar);
-  }
-}
-
-static void scr_cancel_current_completion(void)
-{
-  char *c;
-  char *src = ptr_inputline;
-  guint back = cancel_completion();
-  guint i;
-  // Remove $back chars
-  for (i = 0; i < back; i++)
-    ptr_inputline = prev_char(ptr_inputline, inputLine);
-  c = ptr_inputline;
-  for ( ; *src ; )
-    *c++ = *src++;
-  *c = 0;
-}
-
-static void scr_end_current_completion(void)
-{
-  done_completion();
-  completion_started = FALSE;
-}
-
-//  check_offset(int direction)
-// Check inputline_offset value, and make sure the cursor is inside the
-// screen.
-static inline void check_offset(int direction)
-{
-  int i;
-  char *c = &inputLine[inputline_offset];
-  // Left side
-  if (inputline_offset && direction <= 0) {
-    while (ptr_inputline <= c) {
-      for (i = 0; i < 5; i++)
-        c = prev_char(c, inputLine);
-      if (c == inputLine)
-        break;
-    }
-  }
-  // Right side
-  if (direction >= 0) {
-    int delta = get_char_width(c);
-    while (ptr_inputline > c) {
-      c = next_char(c);
-      delta += get_char_width(c);
-    }
-    c = &inputLine[inputline_offset];
-    while (delta >= maxX) {
-      for (i = 0; i < 5; i++) {
-        delta -= get_char_width(c);
-        c = next_char(c);
-      }
-    }
-  }
-  inputline_offset = c - inputLine;
-}
-
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-// prints inputLine with underlined words when misspelled
-static inline void print_checked_line(void)
-{
-  char *wprint_char_fmt = "%c";
-  int point;
-  int nrchar = maxX;
-  char *ptrCur = inputLine + inputline_offset;
-
-#ifdef UNICODE
-  // We need this to display a single UTF-8 char... Any better solution?
-  if (utf8_mode)
-    wprint_char_fmt = "%lc";
-#endif
-
-  wmove(inputWnd, 0, 0); // problem with backspace
-
-  while (*ptrCur && nrchar-- > 0) {
-    point = ptrCur - inputLine;
-    if (maskLine[point])
-      wattrset(inputWnd, A_UNDERLINE);
-    wprintw(inputWnd, wprint_char_fmt, get_char(ptrCur));
-    wattrset(inputWnd, A_NORMAL);
-    ptrCur = next_char(ptrCur);
-  }
-}
-#endif
-
-static inline void refresh_inputline(void)
-{
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-  if (settings_opt_get_int("spell_enable")) {
-    memset(maskLine, 0, INPUTLINE_LENGTH+1);
-    spellcheck(inputLine, maskLine);
-  }
-  print_checked_line();
-  wclrtoeol(inputWnd);
-  if (*ptr_inputline) {
-    // hack to set cursor pos. Characters can have different width,
-    // so I know of no better way.
-    char c = *ptr_inputline;
-    *ptr_inputline = 0;
-    print_checked_line();
-    *ptr_inputline = c;
-  }
-#else
-  mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
-  wclrtoeol(inputWnd);
-  if (*ptr_inputline) {
-    // hack to set cursor pos. Characters can have different width,
-    // so I know of no better way.
-    char c = *ptr_inputline;
-    *ptr_inputline = 0;
-    mvwprintw(inputWnd, 0, 0, "%s", inputLine + inputline_offset);
-    *ptr_inputline = c;
-  }
-#endif
-}
-
-void scr_handle_CtrlC(void)
-{
-  if (!Curses) return;
-  // Leave multi-line mode
-  process_command(mkcmdstr("msay abort"), TRUE);
-  // Same as Ctrl-g, now
-  scr_cancel_current_completion();
-  scr_end_current_completion();
-  check_offset(-1);
-  refresh_inputline();
-}
-
-static void add_keyseq(char *seqstr, guint mkeycode, gint value)
-{
-  keyseq *ks;
-
-  // Let's make sure the length is correct
-  if (strlen(seqstr) > MAX_KEYSEQ_LENGTH) {
-    scr_LogPrint(LPRINT_LOGNORM, "add_keyseq(): key sequence is too long!");
-    return;
-  }
-
-  ks = g_new0(keyseq, 1);
-  ks->seqstr = g_strdup(seqstr);
-  ks->mkeycode = mkeycode;
-  ks->value = value;
-  keyseqlist = g_slist_append(keyseqlist, ks);
-}
-
-//  match_keyseq(iseq, &ret)
-// Check if "iseq" is a known key escape sequence.
-// Return value:
-// -1  if "seq" matches no known sequence
-//  0  if "seq" could match 1 or more known sequences
-// >0  if "seq" matches a key sequence; the mkey code is returned
-//     and *ret is set to the matching keyseq structure.
-static inline gint match_keyseq(int *iseq, keyseq **ret)
-{
-  GSList *ksl;
-  keyseq *ksp;
-  char *p, c;
-  int *i;
-  int needmore = FALSE;
-
-  for (ksl = keyseqlist; ksl; ksl = g_slist_next(ksl)) {
-    ksp = ksl->data;
-    p = ksp->seqstr;
-    i = iseq;
-    while (1) {
-      c = (unsigned char)*i;
-      if (!*p && !c) { // Match
-        (*ret) = ksp;
-        return ksp->mkeycode;
-      }
-      if (!c) {
-        // iseq is too short
-        needmore = TRUE;
-        break;
-      } else if (!*p || c != *p) {
-        // This isn't a match
-        break;
-      }
-      p++; i++;
-    }
-  }
-
-  if (needmore)
-    return 0;
-  return -1;
-}
-
-static inline int match_utf8_keyseq(int *iseq)
-{
-  int *strp = iseq;
-  unsigned c = *strp++;
-  unsigned mask = 0x80;
-  int len = -1;
-  while (c & mask) {
-    mask >>= 1;
-    len++;
-  }
-  if (len <= 0 || len > 4)
-    return -1;
-  c &= mask - 1;
-  while ((*strp & 0xc0) == 0x80) {
-    if (len-- <= 0) // can't happen
-      return -1;
-    c = (c << 6) | (*strp++ & 0x3f);
-  }
-  if (len)
-    return 0;
-  return c;
-}
-
-void scr_Getch(keycode *kcode)
-{
-  keyseq *mks = NULL;
-  int  ks[MAX_KEYSEQ_LENGTH+1];
-  int i;
-
-  memset(kcode, 0, sizeof(keycode));
-  memset(ks,  0, sizeof(ks));
-
-  kcode->value = wgetch(inputWnd);
-  if (utf8_mode) {
-    bool ismeta = (kcode->value == 27);
-#ifdef NCURSES_MOUSE_VERSION
-    bool ismouse = (kcode->value == KEY_MOUSE);
-
-    if (ismouse) {
-      MEVENT mouse;
-      getmouse(&mouse);
-      kcode->value = mouse.bstate;
-      kcode->mcode = MKEY_MOUSE;
-      return;
-    } else if (ismeta)
-#else
-    if (ismeta)
-#endif
-      ks[0] = wgetch(inputWnd);
-    else
-      ks[0] = kcode->value;
-
-    for (i = 0; i < MAX_KEYSEQ_LENGTH - 1; i++) {
-      int match = match_utf8_keyseq(ks);
-      if (match == -1)
-        break;
-      if (match > 0) {
-        kcode->value = match;
-        kcode->utf8 = 1;
-        if (ismeta)
-          kcode->mcode = MKEY_META;
-        return;
-      }
-      ks[i + 1] = wgetch(inputWnd);
-      if (ks[i + 1] == ERR)
-        break;
-    }
-    while (i > 0)
-      ungetch(ks[i--]);
-    if (ismeta)
-      ungetch(ks[0]);
-    memset(ks,  0, sizeof(ks));
-  }
-  if (kcode->value != 27)
-    return;
-
-  // Check for escape key sequence
-  for (i=0; i < MAX_KEYSEQ_LENGTH; i++) {
-    int match;
-    ks[i] = wgetch(inputWnd);
-    if (ks[i] == ERR) break;
-    match = match_keyseq(ks, &mks);
-    if (match == -1) {
-      // No such key sequence.  Let's increment i as it is a valid key.
-      i++;
-      break;
-    }
-    if (match > 0) {
-      // We have a matching sequence
-      kcode->mcode = mks->mkeycode;
-      kcode->value = mks->value;
-      return;
-    }
-  }
-
-  // No match.  Let's return a meta-key.
-  if (i > 0) {
-    kcode->mcode = MKEY_META;
-    kcode->value = ks[0];
-  }
-  if (i > 1) {
-    // We need to push some keys back to the keyboard buffer
-    while (i-- > 1)
-      ungetch(ks[i]);
-  }
-  return;
-}
-
-void scr_DoUpdate(void)
-{
-  doupdate();
-}
-
-static int bindcommand(keycode kcode)
-{
-  gchar asciikey[16], asciicode[16];
-  const gchar *boundcmd;
-
-  if (kcode.utf8)
-    g_snprintf(asciicode, 15, "U%d", kcode.value);
-  else
-    g_snprintf(asciicode, 15, "%d", kcode.value);
-
-  if (!kcode.mcode || kcode.mcode == MKEY_EQUIV)
-    g_snprintf(asciikey, 15, "%s", asciicode);
-  else if (kcode.mcode == MKEY_META)
-    g_snprintf(asciikey, 15, "M%s", asciicode);
-  else if (kcode.mcode == MKEY_MOUSE)
-    g_snprintf(asciikey, 15, "p%s", asciicode);
-  else
-    g_snprintf(asciikey, 15, "MK%d", kcode.mcode);
-
-  boundcmd = settings_get(SETTINGS_TYPE_BINDING, asciikey);
-
-  if (boundcmd) {
-    gchar *cmdline = from_utf8(boundcmd);
-    scr_CheckAutoAway(TRUE);
-    if (process_command(cmdline, TRUE))
-      return 255; // Quit
-    g_free(cmdline);
-    return 0;
-  }
-
-  scr_LogPrint(LPRINT_NORMAL, "Unknown key=%s", asciikey);
-#ifndef UNICODE
-  if (utf8_mode)
-    scr_LogPrint(LPRINT_NORMAL,
-                 "WARNING: Compiled without full UTF-8 support!");
-#endif
-  return -1;
-}
-
-//  process_key(key)
-// Handle the pressed key, in the command line (bottom).
-void process_key(keycode kcode)
-{
-  int key = kcode.value;
-  int display_char = FALSE;
-
-  lock_chatstate = FALSE;
-
-  switch (kcode.mcode) {
-    case 0:
-        break;
-    case MKEY_EQUIV:
-        key = kcode.value;
-        break;
-    case MKEY_META:
-    default:
-        if (bindcommand(kcode) == 255) {
-          mcabber_set_terminate_ui();
-          return;
-        }
-        key = ERR; // Do not process any further
-  }
-
-  if (kcode.utf8) {
-    if (key != ERR && !kcode.mcode)
-      display_char = TRUE;
-    goto display;
-  }
-
-  switch (key) {
-    case 0:
-    case ERR:
-        break;
-    case 9:     // Tab
-        readline_do_completion();
-        break;
-    case 13:    // Enter
-        if (readline_accept_line(FALSE) == 255) {
-          mcabber_set_terminate_ui();
-          return;
-        }
-        break;
-    case 3:     // Ctrl-C
-        scr_handle_CtrlC();
-        break;
-    case KEY_RESIZE:
-#ifdef USE_SIGWINCH
-        {
-          struct winsize size;
-          if (ioctl(STDIN_FILENO, TIOCGWINSZ, &size) != -1)
-            resizeterm(size.ws_row, size.ws_col);
-        }
-#endif
-        scr_Resize();
-        break;
-    default:
-        display_char = TRUE;
-  } // switch
-
-display:
-  if (display_char) {
-    guint printable;
-
-    if (kcode.utf8) {
-      printable = iswprint(key);
-    } else {
-#ifdef __CYGWIN__
-      printable = (isprint(key) || (key >= 161 && key <= 255))
-                  && !is_speckey(key);
-#else
-      printable = isprint(key) && !is_speckey(key);
-#endif
-    }
-    if (printable) {
-      char tmpLine[INPUTLINE_LENGTH+1];
-
-      // Check the line isn't too long
-      if (strlen(inputLine) + 4 > INPUTLINE_LENGTH)
-        return;
-
-      // Insert char
-      strcpy(tmpLine, ptr_inputline);
-      ptr_inputline = put_char(ptr_inputline, key);
-      strcpy(ptr_inputline, tmpLine);
-      check_offset(1);
-    } else {
-      // Look for a key binding.
-      if (!kcode.utf8 && (bindcommand(kcode) == 255)) {
-          mcabber_set_terminate_ui();
-          return;
-        }
-    }
-  }
-
-  if (completion_started && key != 9 && key != KEY_RESIZE)
-    scr_end_current_completion();
-  refresh_inputline();
-
-  if (!lock_chatstate) {
-    // Set chat state to composing (1) if the user is currently composing,
-    // i.e. not an empty line and not a command line.
-    if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
-      set_chatstate(0);
-    else
-      set_chatstate(1);
-    if (chatstate)
-      time(&chatstate_timestamp);
-  }
-  return;
-}
-
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-// initialization
-void spellcheck_init(void)
-{
-  int spell_enable            = settings_opt_get_int("spell_enable");
-  const char *spell_lang     = settings_opt_get("spell_lang");
-#ifdef WITH_ASPELL
-  const char *spell_encoding = settings_opt_get("spell_encoding");
-  AspellCanHaveError *possible_err;
-#endif
-
-  if (!spell_enable)
-    return;
-
-#ifdef WITH_ENCHANT
-  if (spell_checker) {
-     enchant_broker_free_dict(spell_broker, spell_checker);
-     enchant_broker_free(spell_broker);
-     spell_checker = NULL;
-     spell_broker = NULL;
-  }
-
-  spell_broker = enchant_broker_init();
-  spell_checker = enchant_broker_request_dict(spell_broker, spell_lang);
-#endif
-#ifdef WITH_ASPELL
-  if (spell_checker) {
-    delete_aspell_speller(spell_checker);
-    delete_aspell_config(spell_config);
-    spell_checker = NULL;
-    spell_config = NULL;
-  }
-
-  spell_config = new_aspell_config();
-  aspell_config_replace(spell_config, "encoding", spell_encoding);
-  aspell_config_replace(spell_config, "lang", spell_lang);
-  possible_err = new_aspell_speller(spell_config);
-
-  if (aspell_error_number(possible_err) != 0) {
-    spell_checker = NULL;
-    delete_aspell_config(spell_config);
-    spell_config = NULL;
-  } else {
-    spell_checker = to_aspell_speller(possible_err);
-  }
-#endif
-}
-
-// Deinitialization of spellchecker
-void spellcheck_deinit(void)
-{
-  if (spell_checker) {
-#ifdef WITH_ENCHANT
-    enchant_broker_free_dict(spell_broker, spell_checker);
-#endif
-#ifdef WITH_ASPELL
-    delete_aspell_speller(spell_checker);
-#endif
-    spell_checker = NULL;
-  }
-
-#ifdef WITH_ENCHANT
-  if (spell_broker) {
-    enchant_broker_free(spell_broker);
-    spell_broker = NULL;
-  }
-#endif
-#ifdef WITH_ASPELL
-  if (spell_config) {
-    delete_aspell_config(spell_config);
-    spell_config = NULL;
-  }
-#endif
-}
-
-#define spell_isalpha(c) (utf8_mode ? iswalpha(get_char(c)) : isalpha(*c))
-
-// Spell checking function
-static void spellcheck(char *line, char *checked)
-{
-  const char *start, *line_start;
-
-  if (inputLine[0] == 0 || inputLine[0] == COMMAND_CHAR)
-    return;
-
-  line_start = line;
-
-  while (*line) {
-
-    if (!spell_isalpha(line)) {
-      line = next_char(line);
-      continue;
-    }
-
-    if (!strncmp(line, "http://", 7)) {
-      line += 7; // : and / characters are 1 byte long in utf8, right?
-
-      while (!strchr(" \t\r\n", *line))
-        line = next_char(line); // i think line++ would be fine here?
-
-      continue;
-    }
-
-    if (!strncmp(line, "ftp://", 6)) {
-      line += 6;
-
-      while (!strchr(" \t\r\n", *line))
-        line = next_char(line);
-
-      continue;
-    }
-
-    start = line;
-
-    while (spell_isalpha(line))
-      line = next_char(line);
-
-    if (spell_checker &&
-#ifdef WITH_ENCHANT
-        enchant_dict_check(spell_checker, start, line - start) != 0
-#endif
-#ifdef WITH_ASPELL
-        aspell_speller_check(spell_checker, start, line - start) == 0
-#endif
-    )
-      memset(&checked[start - line_start], SPELLBADCHAR, line - start);
-  }
-}
-#endif
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/screen.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
-#ifndef __SCREEN_H__
-#define __SCREEN_H__ 1
-
-#include <glib.h>
-
-#if HAVE_NCURSESW_NCURSES_H
-# include <ncursesw/ncurses.h>
-# include <ncursesw/panel.h>
-#elif HAVE_NCURSES_NCURSES_H
-# include <ncurses/ncurses.h>
-# include <ncurses/panel.h>
-#else
-# include <ncurses.h>
-# include <panel.h>
-#endif
-
-#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
-void spellcheck_init(void);
-void spellcheck_deinit(void);
-//static void spellcheck(char*, char*);
-#endif
-
-#include "hbuf.h"
-#include "logprint.h"
-#include "roster.h"
-
-#define INPUTLINE_LENGTH  1024
-
-// Only used in screen.c; this is the maximum line number
-// in a multi-line message.  Should be < 1000
-// Note: message length is limited by the HBB_BLOCKSIZE size too
-#define MULTILINE_MAX_LINE_NUMBER 299
-
-// When chatstates are enabled, timeout (in seconds) before "composing"
-// becomes "paused" because of user inactivity.
-// Warning: setting this very low will cause more network traffic.
-#define COMPOSING_TIMEOUT 6L
-
-enum colors {
-  COLOR_GENERAL = 3,
-  COLOR_MSGOUT,
-  COLOR_MSGHL,
-  COLOR_STATUS,
-  COLOR_ROSTER,
-  COLOR_ROSTERSEL,
-  COLOR_ROSTERSELNMSG,
-  COLOR_ROSTERNMSG,
-  COLOR_INFO,
-  COLOR_MSGIN,
-  COLOR_max
-};
-
-int COLOR_ATTRIB[COLOR_max];
-
-extern int update_roster;
-
-typedef struct {
-  int value;
-  int utf8;
-  enum {
-    MKEY_META = 1,
-    MKEY_EQUIV,
-    MKEY_CTRL_PGUP,
-    MKEY_CTRL_PGDOWN,
-    MKEY_SHIFT_PGUP,
-    MKEY_SHIFT_PGDOWN,
-    MKEY_CTRL_SHIFT_PGUP,
-    MKEY_CTRL_SHIFT_PGDOWN,
-    MKEY_CTRL_HOME,
-    MKEY_CTRL_END,
-    MKEY_CTRL_INS,
-    MKEY_CTRL_DEL,
-    MKEY_CTRL_SHIFT_HOME,
-    MKEY_CTRL_SHIFT_END,
-    MKEY_MOUSE
-  } mcode;
-} keycode;
-
-typedef enum {
-  MC_ALL,
-  MC_PRESET,
-  MC_OFF,
-  MC_REMOVE
-} muccoltype;
-
-void scr_init_bindings(void);
-
-void scr_Getch(keycode *kcode);
-void process_key(keycode kcode);
-
-void scr_InitLocaleCharSet(void);
-void scr_InitCurses(void);
-void scr_TerminateCurses(void);
-void scr_DrawMainWindow(unsigned int fullinit);
-void scr_DrawRoster(void);
-void scr_UpdateMainStatus(int forceupdate);
-void scr_UpdateChatStatus(int forceupdate);
-void scr_RosterVisibility(int status);
-void scr_WriteIncomingMessage(const char *jidfrom, const char *text,
-                              time_t timestamp, guint prefix,
-                              unsigned mucnicklen);
-void scr_WriteOutgoingMessage(const char *jidto,   const char *text,
-                              guint prefix, gpointer xep184);
-void scr_RemoveReceiptFlag(const char *jidto, gpointer xep184);
-void scr_ShowBuddyWindow(void);
-int  scr_BuddyBufferExists(const char *jid);
-void scr_UpdateBuddyWindow(void);
-void scr_set_chatmode(int enable);
-int  scr_get_chatmode(void);
-void scr_set_multimode(int enable, char *subject);
-int  scr_get_multimode(void);
-void scr_setmsgflag_if_needed(const char *jid, int special);
-void scr_append_multiline(const char *line);
-const char *scr_get_multiline(void);
-const char *scr_get_multimode_subj(void);
-
-guint scr_getprefixwidth(void);
-void  scr_line_prefix(hbb_line *line, char *prefix, guint preflen);
-
-void scr_Beep(void);
-
-bool Autoaway;
-
-void scr_CheckAutoAway(int activity);
-
-#if defined JEP0022 || defined JEP0085
-gboolean scr_ChatStatesTimeout();
-#endif
-int chatstates_disabled;
-
-// For commands...
-void scr_RosterTop(void);
-void scr_RosterBottom(void);
-void scr_RosterUpDown(int updown, unsigned int n);
-void scr_RosterPrevGroup(void);
-void scr_RosterNextGroup(void);
-void scr_RosterSearch(char *);
-void scr_RosterJumpJid(char *);
-void scr_RosterDisplay(const char *);
-void scr_BufferTopBottom(int topbottom);
-void scr_BufferClear(void);
-void scr_BufferScrollLock(int lock);
-void scr_BufferPurge(int, const char*);
-void scr_BufferPurgeAll(int);
-void scr_BufferSearch(int direction, const char *text);
-void scr_BufferPercent(int pc);
-void scr_BufferDate(time_t t);
-void scr_BufferDump(const char *file);
-void scr_RosterUnreadMessage(int);
-void scr_RosterJumpAlternate(void);
-void scr_BufferScrollUpDown(int updown, unsigned int nblines);
-bool scr_RosterColor(const char *status, const char *wildcard,
-                     const char *color);
-void scr_RosterClearColor(void);
-void scr_MucColor(const char *muc, muccoltype type);
-void scr_MucNickColor(const char *nick, const char *color);
-void scr_BufferList(void);
-
-void readline_transpose_chars(void);
-void readline_forward_kill_word(void);
-void readline_backward_kill_word(void);
-void readline_backward_word(void);
-void readline_forward_word(void);
-void readline_updowncase_word(int);
-void readline_capitalize_word(void);
-void readline_backward_char(void);
-void readline_forward_char(void);
-int  readline_accept_line(int down_history);
-void readline_cancel_completion(void);
-void readline_do_completion(void);
-void readline_refresh_screen(void);
-void readline_disable_chat_mode(guint show_roster);
-void readline_hist_beginning_search_bwd(void);
-void readline_hist_beginning_search_fwd(void);
-void readline_hist_prev(void);
-void readline_hist_next(void);
-void readline_backward_kill_char(void);
-void readline_forward_kill_char(void);
-void readline_iline_start(void);
-void readline_iline_end(void);
-void readline_backward_kill_iline(void);
-void readline_forward_kill_iline(void);
-void readline_send_multiline(void);
-
-#endif
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/settings.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,609 +0,0 @@
-/*
- * settings.c   -- Configuration stuff
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "config.h"
-#include "settings.h"
-#include "commands.h"
-#include "logprint.h"
-#include "otr.h"
-#include "utils.h"
-#include "xmpp.h"
-#include "main.h"
-
-// Maximum line length
-// (probably best to use the same value as INPUTLINE_LENGTH)
-#define CONFLINE_LENGTH  1024
-
-static GHashTable *option;
-static GHashTable *alias;
-static GHashTable *binding;
-
-#ifdef HAVE_GPGME     /* PGP settings */
-static GHashTable *pgpopt;
-
-typedef struct {
-  gchar *pgp_keyid;   /* KeyId the contact is supposed to use */
-  guint pgp_disabled; /* If TRUE, PGP is disabled for outgoing messages */
-  guint pgp_force;    /* If TRUE, PGP is used w/o negotiation */
-} T_pgpopt;
-#endif
-
-#ifdef HAVE_LIBOTR
-static GHashTable *otrpolicy;
-static enum otr_policy default_policy;
-#endif
-
-static inline GHashTable *get_hash(guint type)
-{
-  if      (type == SETTINGS_TYPE_OPTION)  return option;
-  else if (type == SETTINGS_TYPE_ALIAS)   return alias;
-  else if (type == SETTINGS_TYPE_BINDING) return binding;
-#ifdef HAVE_LIBOTR
-  else if (type == SETTINGS_TYPE_OTR)     return otrpolicy;
-#endif
-  return NULL;
-}
-
-/* -- */
-
-void settings_init(void)
-{
-  option  = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free);
-  alias   = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free);
-  binding = g_hash_table_new_full(&g_str_hash, &g_str_equal, &g_free, &g_free);
-#ifdef HAVE_GPGME
-  pgpopt = g_hash_table_new(&g_str_hash, &g_str_equal);
-#endif
-#ifdef HAVE_LIBOTR
-  otrpolicy = g_hash_table_new(&g_str_hash, &g_str_equal);
-#endif
-}
-
-//  cfg_read_file(filename, mainfile)
-// Read and parse config file "filename".  If filename is NULL,
-// try to open the configuration file at the default locations.
-// mainfile must be set to TRUE for the startup config file.
-// If mainfile is TRUE, the permissions of the configuration file will
-// be fixed if they're insecure.
-//
-int cfg_read_file(char *filename, guint mainfile)
-{
-  static unsigned int runtime;
-  FILE *fp;
-  char *buf;
-  char *line, *eol;
-  unsigned int ln = 0;
-  int err = 0;
-
-  if (!filename) {
-    // Use default config file locations
-    char *home;
-    GString *sfilename;
-
-    if (!mainfile) {
-      scr_LogPrint(LPRINT_LOGNORM, "No file name provided");
-      return -1;
-    }
-
-    home = getenv("HOME");
-    if (!home) {
-      scr_LogPrint(LPRINT_LOG, "Can't find home dir!");
-      fprintf(stderr, "Can't find home dir!\n");
-      err = -1;
-      goto cfg_read_file_return;
-    }
-    sfilename = g_string_new("");
-    g_string_printf(sfilename, "%s/.mcabber/mcabberrc", home);
-    if ((fp = fopen(sfilename->str, "r")) == NULL) {
-      // 2nd try...
-      g_string_printf(sfilename, "%s/.mcabberrc", home);
-      if ((fp = fopen(sfilename->str, "r")) == NULL) {
-        fprintf(stderr, "Cannot open config file!\n");
-        g_string_free(sfilename, TRUE);
-        err = -1;
-        goto cfg_read_file_return;
-      }
-    }
-    // Check configuration file permissions
-    // As it could contain sensitive data, we make it user-readable only.
-    checkset_perm(sfilename->str, TRUE);
-    scr_LogPrint(LPRINT_LOGNORM, "Reading %s", sfilename->str);
-    // Check mcabber dir.  Here we just warn, we don't change the modes.
-    g_string_printf(sfilename, "%s/.mcabber/", home);
-    checkset_perm(sfilename->str, FALSE);
-    g_string_free(sfilename, TRUE);
-  } else {
-    // filename was specified
-    if ((fp = fopen(filename, "r")) == NULL) {
-      const char *msg = "Cannot open configuration file";
-      if (mainfile)
-        perror(msg);
-      else
-        scr_LogPrint(LPRINT_LOGNORM, "%s (%s).", msg, filename);
-      err = -2;
-      goto cfg_read_file_return;
-    }
-    // Check configuration file permissions (see above)
-    // We don't change the permissions if that's not the main file.
-    if (mainfile)
-      checkset_perm(filename, TRUE);
-    scr_LogPrint(LPRINT_LOGNORM, "Reading %s", filename);
-  }
-
-  buf = g_new(char, CONFLINE_LENGTH+1);
-
-  while (fgets(buf+1, CONFLINE_LENGTH, fp) != NULL) {
-    // The first char is reserved to add a '/', to make a command line
-    line = buf+1;
-    ln++;
-
-    // Strip leading spaces
-    while (isspace(*line))
-      line++;
-
-    // Make eol point to the last char of the line
-    for (eol = line ; *eol ; eol++)
-      ;
-    if (eol > line)
-      eol--;
-
-    // Strip trailing spaces
-    while (eol > line && isspace(*eol))
-      *eol-- = 0;
-
-    // Ignore empty lines and comments
-    if ((*line == '\n') || (*line == '\0') || (*line == '#'))
-      continue;
-
-    // We only allow assignments line, except for commands "pgp", "source",
-    // "color", "load" and "otrpolicy", unless we're in runtime (i.e. not startup).
-    if (runtime ||
-        (strchr(line, '=') != NULL)        ||
-        startswith(line, "pgp ", FALSE)    ||
-        startswith(line, "source ", FALSE) ||
-        startswith(line, "color ", FALSE)  ||
-#ifdef MODULES_ENABLE
-        startswith(line, "load ", FALSE)   ||
-#endif
-        startswith(line, "otrpolicy", FALSE)) {
-      // Only accept a few "safe" commands
-      if (!runtime &&
-          !startswith(line, "set ", FALSE)    &&
-          !startswith(line, "bind ", FALSE)   &&
-          !startswith(line, "alias ", FALSE)  &&
-          !startswith(line, "pgp ", FALSE)    &&
-          !startswith(line, "source ", FALSE) &&
-          !startswith(line, "color ", FALSE)  &&
-#ifdef MODULES_ENABLE
-          !startswith(line, "load ", FALSE)   &&
-#endif
-          !startswith(line, "otrpolicy ", FALSE)) {
-        scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): "
-                     "this command can't be used here", ln);
-        err++;
-        continue;
-      }
-      // Set the leading COMMAND_CHAR to build a command line
-      // and process the command
-      *(--line) = COMMAND_CHAR;
-      if (process_command(line, TRUE) == 255)
-        mcabber_set_terminate_ui();
-    } else {
-      scr_LogPrint(LPRINT_LOGNORM, "Error in configuration file (l. %d): "
-                   "this is not an assignment", ln);
-      err++;
-    }
-  }
-  g_free(buf);
-  fclose(fp);
-
-  if (filename)
-    scr_LogPrint(LPRINT_LOGNORM, "Loaded %s.", filename);
-
-cfg_read_file_return:
-  // If we're done with the main file parsing, we can assume that
-  // the next time this function is called will be at run time.
-  if (mainfile)
-    runtime = TRUE;
-  return err;
-}
-
-//  parse_assigment(assignment, pkey, pval)
-// Read assignment and split it to key, value
-//
-// If this is an assignment, the function will return TRUE and
-// set *pkey and *pval (*pval is set to NULL if value field is empty).
-//
-// If this isn't a assignment (no = char), the function will set *pval
-// to NULL and return FALSE.
-//
-// The caller should g_free() *pkey and *pval (if not NULL) after use.
-guint parse_assigment(gchar *assignment, gchar **pkey, gchar **pval)
-{
-  char *key, *val, *t, *p;
-
-  *pkey = *pval = NULL;
-
-  key = assignment;
-  // Remove leading spaces in option name
-  while ((!isalnum(*key)) && (*key != '=') && *key) {
-    //if (!isblank(*key))
-    //  scr_LogPrint("Error in assignment parsing!");
-    key++;
-  }
-  if (!*key) return FALSE; // Empty assignment
-
-  if (*key == '=') {
-    //scr_LogPrint("Cannot parse assignment!");
-    return FALSE;
-  }
-  // Ok, key points to the option name
-
-  for (val = key+1 ; *val && (*val != '=') ; val++)
-    if (!isalnum(*val) && !isblank(*val) && (*val != '_') && (*val != '-')) {
-      // Key should only have alnum chars...
-      //scr_LogPrint("Error in assignment parsing!");
-      return FALSE;
-    }
-  // Remove trailing spaces in option name:
-  for (t = val-1 ; t > key && isblank(*t) ; t--)
-    ;
-  // Check for embedded whitespace characters
-  for (p = key; p < t; p++) {
-    if (isblank(*p)) {
-      //scr_LogPrint("Error in assignment parsing!"
-      //             " (Name should not contain space chars)");
-      return FALSE;
-    }
-  }
-
-  *pkey = g_strndup(key, t+1-key);
-
-  if (!*val) return FALSE; // Not an assignment
-
-  // Remove leading and trailing spaces in option value:
-  for (val++; *val && isblank(*val) ; val++) ;
-  for (t = val ; *t ; t++) ;
-  for (t-- ; t >= val && isblank(*t) ; t--) ;
-
-  if (t < val) return TRUE; // no value (variable reset for example)
-
-  // If the value begins and ends with quotes ("), these quotes are
-  // removed and whitespace is not stripped
-  if ((t>val) && (*val == '"' && *t == '"')) {
-    val++;
-    t--;
-  }
-  *pval = g_strndup(val, t+1-val);
-  return TRUE;
-}
-
-void settings_set(guint type, const gchar *key, const gchar *value)
-{
-  GHashTable *hash;
-
-  hash = get_hash(type);
-  if (!hash)
-    return;
-
-  if (!value) {
-    g_hash_table_remove(hash, key);
-  } else {
-    g_hash_table_insert(hash, g_strdup(key), g_strdup(value));
-  }
-}
-
-void settings_del(guint type, const gchar *key)
-{
-  settings_set(type, key, NULL);
-}
-
-const gchar *settings_get(guint type, const gchar *key)
-{
-  GHashTable *hash;
-
-  hash = get_hash(type);
-  if (!hash)
-    return NULL;
-
-  return g_hash_table_lookup(hash, key);
-}
-
-int settings_get_int(guint type, const gchar *key)
-{
-  const gchar *setval = settings_get(type, key);
-
-  if (setval) return atoi(setval);
-  return 0;
-}
-
-//  settings_get_status_msg(status)
-// Return a string with the current status message:
-// - if there is a user-defined message ("message" option),
-//   return this message
-// - if there is a user-defined message for the given status (and no
-//   generic user message), it is returned
-// - if no message is found, return NULL
-const gchar *settings_get_status_msg(enum imstatus status)
-{
-  const gchar *rstatus = settings_opt_get("message");
-
-  if (rstatus) return rstatus;
-
-  switch(status) {
-    case available:
-        rstatus = settings_opt_get("message_avail");
-        break;
-
-    case freeforchat:
-        rstatus = settings_opt_get("message_free");
-        break;
-
-    case dontdisturb:
-        rstatus = settings_opt_get("message_dnd");
-        break;
-
-    case notavail:
-        rstatus = settings_opt_get("message_notavail");
-        break;
-
-    case away:
-        rstatus = settings_opt_get("message_away");
-        break;
-
-    default: // offline, invisible
-        break;
-  }
-  return rstatus;
-}
-
-//  settings_foreach(type, pfunction, param)
-// Call pfunction(key, value, param) for each setting with requested type.
-void settings_foreach(guint type, void (*pfunc)(char *k, char *v, void *param),
-                      void *param)
-{
-  GHashTable *hash;
-
-  hash = get_hash(type);
-  if (!hash)
-    return;
-
-  g_hash_table_foreach(hash, (GHFunc)pfunc, param);
-}
-
-
-//  default_muc_nickname()
-// Return the user's default nickname
-// The caller should free the string after use
-char *default_muc_nickname(const char *roomid)
-{
-  char *nick;
-
-  nick = (char*)xmpp_get_bookmark_nick(roomid);
-  if (nick)
-    return g_strdup(nick);
-
-  // We try the "nickname" option, then the username part of the jid.
-  nick = (char*)settings_opt_get("nickname");
-  if (nick)
-    return g_strdup(nick);
-
-  nick = jid_get_username(settings_opt_get("jid"));
-  return nick;
-}
-
-
-/* PGP settings */
-
-//  settings_pgp_setdisabled(jid, value)
-// Enable/disable PGP encryption for jid.
-// (Set value to TRUE to disable encryption)
-void settings_pgp_setdisabled(const char *bjid, guint value)
-{
-#ifdef HAVE_GPGME
-  T_pgpopt *pgpdata;
-  pgpdata = g_hash_table_lookup(pgpopt, bjid);
-  if (!pgpdata) {
-    // If value is 0, we do not need to create a structure (that's
-    // the default value).
-    if (value) {
-      pgpdata = g_new0(T_pgpopt, 1);
-      pgpdata->pgp_disabled = value;
-      g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
-    }
-  } else {
-    pgpdata->pgp_disabled = value;
-    // We could remove the key/value if pgp_disabled is 0 and
-    // pgp_keyid is NULL, actually.
-  }
-#endif
-}
-
-//  settings_pgp_getdisabled(jid)
-// Return TRUE if PGP encryption should be disabled for jid.
-guint settings_pgp_getdisabled(const char *bjid)
-{
-#ifdef HAVE_GPGME
-  T_pgpopt *pgpdata;
-  pgpdata = g_hash_table_lookup(pgpopt, bjid);
-  if (pgpdata)
-    return pgpdata->pgp_disabled;
-  else
-    return FALSE; // Default: not disabled
-#else
-  return TRUE;    // No PGP support, let's say it's disabled.
-#endif
-}
-
-//  settings_pgp_setforce(jid, value)
-// Force (or not) PGP encryption for jid.
-// When value is TRUE, PGP support will be assumed for the remote client.
-void settings_pgp_setforce(const char *bjid, guint value)
-{
-#ifdef HAVE_GPGME
-  T_pgpopt *pgpdata;
-  pgpdata = g_hash_table_lookup(pgpopt, bjid);
-  if (!pgpdata) {
-    // If value is 0, we do not need to create a structure (that's
-    // the default value).
-    if (value) {
-      pgpdata = g_new0(T_pgpopt, 1);
-      pgpdata->pgp_force = value;
-      g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
-    }
-  } else {
-    pgpdata->pgp_force = value;
-  }
-  if (value && pgpdata && !pgpdata->pgp_keyid)
-    scr_LogPrint(LPRINT_NORMAL, "Warning: the Key Id is not set!");
-#endif
-}
-
-//  settings_pgp_getforce(jid)
-// Return TRUE if PGP enforcement is set for jid.
-guint settings_pgp_getforce(const char *bjid)
-{
-#ifdef HAVE_GPGME
-  T_pgpopt *pgpdata;
-  pgpdata = g_hash_table_lookup(pgpopt, bjid);
-  if (pgpdata)
-    return pgpdata->pgp_force;
-  else
-    return FALSE; // Default
-#else
-  return FALSE;   // No PGP support
-#endif
-}
-
-//  settings_pgp_setkeyid(jid, keyid)
-// Set the PGP KeyId for user jid.
-// Use keyid = NULL to erase the previous KeyId.
-void settings_pgp_setkeyid(const char *bjid, const char *keyid)
-{
-#ifdef HAVE_GPGME
-  T_pgpopt *pgpdata;
-  pgpdata = g_hash_table_lookup(pgpopt, bjid);
-  if (!pgpdata) {
-    // If keyid is NULL, we do not need to create a structure (that's
-    // the default value).
-    if (keyid) {
-      pgpdata = g_new0(T_pgpopt, 1);
-      pgpdata->pgp_keyid = g_strdup(keyid);
-      g_hash_table_insert(pgpopt, g_strdup(bjid), pgpdata);
-    }
-  } else {
-    g_free(pgpdata->pgp_keyid);
-    if (keyid)
-      pgpdata->pgp_keyid = g_strdup(keyid);
-    else
-      pgpdata->pgp_keyid = NULL;
-    // We could remove the key/value if pgp_disabled is 0 and
-    // pgp_keyid is NULL, actually.
-  }
-#endif
-}
-
-//  settings_pgp_getkeyid(jid)
-// Get the PGP KeyId for user jid.
-const char *settings_pgp_getkeyid(const char *bjid)
-{
-#ifdef HAVE_GPGME
-  T_pgpopt *pgpdata;
-  pgpdata = g_hash_table_lookup(pgpopt, bjid);
-  if (pgpdata)
-    return pgpdata->pgp_keyid;
-#endif
-  return NULL;
-}
-
-/* otr settings */
-
-#ifdef HAVE_LIBOTR
-static void remove_default_policies(char *k, char *policy, void *defaultp)
-{
-  if (*(enum otr_policy *)policy == *(enum otr_policy *)defaultp) {
-    g_free((enum otr_policy *) policy);
-    g_hash_table_remove(otrpolicy, k);
-  }
-}
-#endif
-
-void settings_otr_setpolicy(const char *bjid, guint value)
-{
-#ifdef HAVE_LIBOTR
-  enum otr_policy *otrdata;
-
-  if (!bjid) {
-    default_policy = value;
-    /* refresh hash */
-    settings_foreach(SETTINGS_TYPE_OTR, &remove_default_policies, &value);
-    return;
-  }
-
-  otrdata = g_hash_table_lookup(otrpolicy, bjid);
-
-  if (value == default_policy) {
-    if (otrdata) {
-      g_free(otrdata);
-      g_hash_table_remove(otrpolicy, bjid);
-    }
-  } else if (otrdata) {
-    *otrdata = value;
-  } else {
-    otrdata = g_new(enum otr_policy, 1);
-    *otrdata = value;
-    g_hash_table_insert(otrpolicy, g_strdup(bjid), otrdata);
-  }
-#endif
-}
-
-guint settings_otr_getpolicy(const char *bjid)
-{
-#ifdef HAVE_LIBOTR
-  enum otr_policy *otrdata;
-  if (!bjid)
-    return default_policy;
-
-  otrdata = g_hash_table_lookup(otrpolicy, bjid);
-  if (otrdata)
-    return *otrdata;
-  else
-    return default_policy;
-#else
-  return 0;
-#endif
-}
-
-guint get_max_history_blocks(void)
-{
-  int max_num_of_blocks = settings_opt_get_int("max_history_blocks");
-  if (max_num_of_blocks < 0)
-    max_num_of_blocks = 0;
-  else if (max_num_of_blocks == 1)
-    max_num_of_blocks = 2;
-  return (guint)max_num_of_blocks;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/settings.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-#ifndef __SETTINGS_H__
-#define __SETTINGS_H__ 1
-
-#include <ctype.h>
-#include <glib.h>
-
-#include "roster.h"
-
-#ifndef isblank
-# define isblank(c)  ((c) == 0x20 || (c) == 0x09)
-#endif
-
-
-#define SETTINGS_TYPE_OPTION    1
-#define SETTINGS_TYPE_ALIAS     2
-#define SETTINGS_TYPE_BINDING   3
-#ifdef HAVE_LIBOTR
-#define SETTINGS_TYPE_OTR       4
-#endif
-
-#define COMMAND_CHAR    '/'
-#define COMMAND_CHARSTR "/"
-
-#define settings_opt_get(k)     settings_get(SETTINGS_TYPE_OPTION, k)
-#define settings_opt_get_int(k) settings_get_int(SETTINGS_TYPE_OPTION, k)
-
-#define mkcmdstr(cmd) COMMAND_CHARSTR cmd
-
-void    settings_init(void);
-int     cfg_read_file(char *filename, guint mainfile);
-guint   parse_assigment(gchar *assignment, gchar **pkey, gchar **pval);
-void    settings_set(guint type, const gchar *key, const gchar *value);
-void    settings_del(guint type, const gchar *key);
-const gchar *settings_get(guint type, const gchar *key);
-int     settings_get_int(guint type, const gchar *key);
-const gchar *settings_get_status_msg(enum imstatus status);
-void    settings_foreach(guint type,
-                         void (*pfunc)(char *k, char *v, void *param),
-                         void *param);
-
-void    settings_pgp_setdisabled(const char *bjid, guint value);
-guint   settings_pgp_getdisabled(const char *bjid);
-void    settings_pgp_setforce(const char *bjid, guint value);
-guint   settings_pgp_getforce(const char *bjid);
-void    settings_pgp_setkeyid(const char *bjid, const char *keyid);
-const char *settings_pgp_getkeyid(const char *bjid);
-
-#ifdef HAVE_LIBOTR
-guint   settings_otr_getpolicy(const char *bjid);
-void    settings_otr_setpolicy(const char *bjid, guint value);
-#endif
-
-guint get_max_history_blocks(void);
-
-char *default_muc_nickname(const char *roomid);
-
-const gchar *isbound(int key);
-
-#endif /* __SETTINGS_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/utf8.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-/*
- * utf8.c       -- UTF-8 routines
- *
- * Copyright (C) 2006 Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include "utf8.h"
-
-char *prev_char(char *str, const char *limit)
-{
-  if (str <= limit)
-    return str;
-  str--;
-  if (utf8_mode)
-    while ((str > limit) && ((*str & 0xc0) == 0x80))
-      str--;
-  return str;
-}
-
-char *next_char(char *str)
-{
-  if (!*str)
-    return str;
-  str++;
-  if (utf8_mode)
-    while ((*str & 0xc0) == 0x80)
-      str++;
-  return str;
-}
-
-unsigned get_char(const char *str)
-{
-  unsigned char *strp = (unsigned char *)str;
-  unsigned c = *strp++;
-  unsigned mask = 0x80;
-  int len = -1;
-  if (!utf8_mode)
-    return c;
-  while (c & mask) {
-    mask >>= 1;
-    len++;
-  }
-  if (len <= 0 || len > 4)
-    goto no_utf8;
-  c &= mask - 1;
-  while ((*strp & 0xc0) == 0x80) {
-    if (len-- <= 0)
-      goto no_utf8;
-    c = (c << 6) | (*strp++ & 0x3f);
-  }
-  if (len)
-    goto no_utf8;
-  return c;
-
-no_utf8:
-  return *str;
-}
-
-char *put_char(char *str, unsigned c)
-{
-  int mask = 0xffffffc0;
-  int i = 4;
-  char code[5];
-  if (!utf8_mode || c < 128) {
-    *str++ = c;
-    return str;
-  }
-  while (c & mask) {
-    code[i--] = 0x80 | (c & 0x3f);
-    c >>= 6;
-    mask >>= 1;
-    if (i < 0) {
-      *str++ = '?';
-      return str;
-    }
-  }
-  code[i] = (mask << 1) | c;
-  for (; i < 5; i++)
-    *str++ = code[i];
-  return str;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/utf8.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-#ifndef __UTF8_H__
-#define __UTF8_H__ 1
-
-#include <config.h>
-
-#if defined HAVE_UNICODE && defined HAVE_WCHAR_H && defined HAVE_WCTYPE_H
-# define UNICODE
-#endif
-
-#ifdef HAVE_WCHAR_H
-# include <wchar.h>
-# define get_char_width(c) (utf8_mode ? wcwidth(get_char(c)) : 1)
-#else
-# define wcwidth(c) 1
-# define get_char_width(c) 1
-#endif
-
-#ifdef HAVE_WCTYPE_H
-# include <wctype.h>
-
-/* The following bit is a hack for Solaris 8&9 systems that don't have
- * iswblank().
- * For now i made sure it comes after wctype.h so it doesn't create
- * problems (wctype.h has calls to iswblank() before wctype() is declared).
- * (Sebastian Kayser)
- */
-# ifndef HAVE_ISWBLANK
-#  define iswblank(wc) iswctype(wc, wctype("blank"))
-# endif
-
-#else
-# define iswblank(c) (c == ' ')
-# define iswalnum(c) isalnum(c)
-# define iswprint(c) isprint(c)
-# define towupper(c) toupper(c)
-# define towlower(c) tolower(c)
-# define iswalpha(c) isalpha(c)
-#endif
-
-extern int utf8_mode;
-
-char *prev_char(char *str, const char *limit);
-char *next_char(char *str);
-unsigned get_char(const char *str);
-char *put_char(char *str, unsigned c);
-
-#endif /* __UTF8_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/utils.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,789 +0,0 @@
-/*
- * utils.c      -- Various utility functions
- *
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- * Some of the ut_* functions are derived from Cabber debug/log code.
- * from_iso8601() comes from the Pidgin (libpurple) project.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <config.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-
-#ifdef HAVE_LIBIDN
-#include <idna.h>
-#include <stringprep.h>
-static char idnprep[1024];
-#endif
-
-#include <glib.h>
-#include <glib/gprintf.h>
-
-/* For Cygwin (thanks go to Yitzchak Scott-Thoennes) */
-#ifdef __CYGWIN__
-#  define timezonevar
-   extern long timezone;
-#endif
-#include <time.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <ctype.h>
-
-#include "utils.h"
-#include "logprint.h"
-
-static int DebugEnabled;
-static char *FName;
-
-//  jidtodisp(jid)
-// Strips the resource part from the jid
-// The caller should g_free the result after use.
-char *jidtodisp(const char *fjid)
-{
-  char *ptr;
-  char *alias;
-
-  alias = g_strdup(fjid);
-
-  if ((ptr = strchr(alias, JID_RESOURCE_SEPARATOR)) != NULL) {
-    *ptr = 0;
-  }
-  return alias;
-}
-
-char *jid_get_username(const char *fjid)
-{
-  char *ptr;
-  char *username;
-
-  username = g_strdup(fjid);
-  if ((ptr = strchr(username, JID_DOMAIN_SEPARATOR)) != NULL) {
-    *ptr = 0;
-  }
-  return username;
-}
-
-char *compose_jid(const char *username, const char *servername,
-                  const char *resource)
-{
-  char *fjid;
-
-  if (!strchr(username, JID_DOMAIN_SEPARATOR)) {
-    fjid = g_strdup_printf("%s%c%s%c%s", username,
-                           JID_DOMAIN_SEPARATOR, servername,
-                           JID_RESOURCE_SEPARATOR, resource);
-  } else {
-    fjid = g_strdup_printf("%s%c%s", username,
-                           JID_RESOURCE_SEPARATOR, resource);
-  }
-  return fjid;
-}
-
-gboolean jid_equal(const char *jid1, const char *jid2)
-{
-  char *a,*b;
-  int ret;
-  if (!jid1 && !jid2)
-    return TRUE;
-  if (!jid1 || !jid2)
-    return FALSE;
-
-  a = jidtodisp(jid1);
-  b = jidtodisp(jid2);
-  ret = strcasecmp(a, b);
-  g_free(a);
-  g_free(b);
-  return (ret == 0) ? TRUE : FALSE;
-}
-
-//  expand_filename(filename)
-// Expand "~/" with the $HOME env. variable in a file name.
-// The caller must free the string after use.
-char *expand_filename(const char *fname)
-{
-  if (!fname)
-    return NULL;
-  if (!strncmp(fname, "~/", 2)) {
-    char *homedir = getenv("HOME");
-    if (homedir)
-      return g_strdup_printf("%s%s", homedir, fname+1);
-  }
-  return g_strdup(fname);
-}
-
-void fingerprint_to_hex(const unsigned char *fpr, char hex[49])
-{
-  int i;
-  char *p;
-
-  for (p = hex, i = 0; i < 15; i++, p+=3)
-    g_sprintf(p, "%02X:", fpr[i]);
-  g_sprintf(p, "%02X", fpr[i]);
-  hex[48] = '\0';
-}
-
-gboolean hex_to_fingerprint(const char *hex, char fpr[16])
-{
-  int i;
-  char *p;
-
-  if (strlen(hex) != 47)
-    return FALSE;
-  for (i = 0, p = (char*)hex; *p && *(p+1); i++, p += 3)
-    fpr[i] = (char) g_ascii_strtoull (p, NULL, 16);
-  return TRUE;
-}
-
-void ut_InitDebug(int level, const char *filename)
-{
-  FILE *fp;
-  struct stat buf;
-  int err;
-
-  if (level < 1) {
-    DebugEnabled = 0;
-    FName = NULL;
-    return;
-  }
-
-  if (filename)
-    FName = expand_filename(filename);
-  else {
-    FName = getenv("HOME");
-    if (!FName)
-      FName = g_strdup("/tmp/mcabberlog");
-    else {
-      FName = g_strdup_printf("%s/mcabberlog", FName);
-    }
-  }
-
-  DebugEnabled = level;
-
-  fp = fopen(FName, "a");
-  if (!fp) {
-    fprintf(stderr, "ERROR: Cannot open tracelog file\n");
-    return;
-  }
-
-  err = fstat(fileno(fp), &buf);
-  if (err || buf.st_uid != geteuid()) {
-    fclose(fp);
-    DebugEnabled = 0;
-    FName = NULL;
-    if (err) {
-      fprintf(stderr, "ERROR: cannot stat the tracelog file!\n");
-    } else {
-      fprintf(stderr, "ERROR: tracelog file does not belong to you!\n");
-    }
-    return;
-  }
-  fchmod(fileno(fp), S_IRUSR|S_IWUSR);
-
-  fprintf(fp, "New trace log started.\n----------------------\n");
-  fclose(fp);
-}
-
-void ut_WriteLog(unsigned int flag, const char *data)
-{
-  if (!DebugEnabled || !FName) return;
-
-  if (((DebugEnabled >= 2) && (flag & (LPRINT_LOG|LPRINT_DEBUG))) ||
-      ((DebugEnabled == 1) && (flag & LPRINT_LOG))) {
-    FILE *fp = fopen(FName, "a+");
-    if (!fp) {
-      scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot open tracelog file");
-      return;
-    }
-    if (fputs(data, fp) == EOF)
-      scr_LogPrint(LPRINT_NORMAL, "ERROR: Cannot write to tracelog file");
-    fclose(fp);
-  }
-}
-
-//  checkset_perm(name, setmode)
-// Check the permissions of the "name" file/dir
-// If setmode is true, correct the permissions if they are wrong
-// Return values: -1 == bad file/dir, 0 == success, 1 == cannot correct
-int checkset_perm(const char *name, unsigned int setmode)
-{
-  int fd;
-  struct stat buf;
-
-#ifdef __CYGWIN__
-  // Permission checking isn't efficient on Cygwin
-  return 0;
-#endif
-
-  fd = stat(name, &buf);
-  if (fd == -1) return -1;
-
-  if (buf.st_uid != geteuid()) {
-    scr_LogPrint(LPRINT_LOGNORM, "Wrong file owner [%s]", name);
-    return 1;
-  }
-
-  if (buf.st_mode & (S_IRGRP | S_IWGRP | S_IXGRP) ||
-      buf.st_mode & (S_IROTH | S_IWOTH | S_IXOTH)) {
-    if (setmode) {
-      mode_t newmode = 0;
-      scr_LogPrint(LPRINT_LOGNORM, "Bad permissions [%s]", name);
-      if (S_ISDIR(buf.st_mode))
-        newmode |= S_IXUSR;
-      newmode |= S_IRUSR | S_IWUSR;
-      if (chmod(name, newmode)) {
-        scr_LogPrint(LPRINT_LOGNORM, "WARNING: Failed to correct permissions!");
-        return 1;
-      }
-      scr_LogPrint(LPRINT_LOGNORM, "Permissions have been corrected");
-    } else {
-      scr_LogPrint(LPRINT_LOGNORM, "WARNING: Bad permissions [%s]", name);
-      return 1;
-    }
-  }
-
-  return 0;
-}
-
-const char *ut_get_tmpdir(void)
-{
-  static const char *tmpdir;
-  const char *tmpvars[] = { "MCABBERTMPDIR", "TMP", "TMPDIR", "TEMP" };
-  unsigned int i;
-
-  if (tmpdir)
-    return tmpdir;
-
-  for (i = 0; i < (sizeof(tmpvars) / sizeof(const char *)); i++) {
-    tmpdir = getenv(tmpvars[i]);
-    if (tmpdir && tmpdir[0] && tmpdir[0] == '/' && tmpdir[1]) {
-      // Looks ok.
-      return tmpdir;
-    }
-  }
-
-  // Default temporary directory
-  tmpdir = "/tmp";
-  return tmpdir;
-}
-
-//  to_iso8601(dststr, timestamp)
-// Convert timestamp to iso8601 format, and store it in dststr.
-// NOTE: dststr should be at last 19 chars long.
-// Return should be 0
-int to_iso8601(char *dststr, time_t timestamp)
-{
-  struct tm *tm_time;
-  int ret;
-
-  tm_time = gmtime(&timestamp);
-
-  ret = snprintf(dststr, 19, "%.4d%02d%02dT%02d:%02d:%02dZ",
-        (int)(1900+tm_time->tm_year), tm_time->tm_mon+1, tm_time->tm_mday,
-        tm_time->tm_hour, tm_time->tm_min, tm_time->tm_sec);
-
-  return ((ret == -1) ? -1 : 0);
-}
-
-//  from_iso8601(timestamp, utc)
-// This function came from the Pidgin project, gaim_str_to_time().
-// (Actually date may not be pure iso-8601)
-// Thanks, guys!
-// ** Modified by somian 10 Apr 2006 with advice from ysth.
-time_t from_iso8601(const char *timestamp, int utc)
-{
-  struct tm t;
-  time_t retval = 0;
-  char buf[32];
-  char *c;
-  int tzoff = 0;
-  int hms_succ = 0;
-  int tmpyear;
-
-  time(&retval);
-  localtime_r(&retval, &t);
-
-  /* Reset time to midnight (00:00:00) */
-  t.tm_hour = t.tm_min = t.tm_sec = 0;
-
-  snprintf(buf, sizeof(buf), "%s", timestamp);
-  c = buf;
-
-  /* 4 digit year */
-  if (!sscanf(c, "%04d", &tmpyear)) return 0;
-  t.tm_year = tmpyear;
-  c+=4;
-  if (*c == '-')
-    c++;
-
-  t.tm_year -= 1900;
-
-  /* 2 digit month */
-  if (!sscanf(c, "%02d", &t.tm_mon)) return 0;
-  c+=2;
-  if (*c == '-')
-    c++;
-
-  t.tm_mon -= 1;
-
-  /* 2 digit day */
-  if (!sscanf(c, "%02d", &t.tm_mday)) return 0;
-  c+=2;
-  if (*c == 'T' || *c == '.') { /* we have more than a date, keep going */
-    c++; /* skip the "T" */
-
-    /* 2 digit hour */
-    if (sscanf(c, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3)
-    {
-      hms_succ = 1;
-      c += 8;
-    }
-    else if (sscanf(c, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3)
-    {
-       hms_succ = 1;
-       c += 6;
-    }
-
-    if (hms_succ) {
-      int tzhrs, tzmins;
-
-      if (*c == '.') /* dealing with precision we don't care about */
-        c += 4;
-
-      if ((*c == '+' || *c == '-') &&
-          sscanf(c+1, "%02d:%02d", &tzhrs, &tzmins)) {
-        tzoff = tzhrs*60*60 + tzmins*60;
-        if (*c == '+')
-          tzoff *= -1;
-      }
-
-      if (tzoff || utc) {
-#ifdef HAVE_TM_GMTOFF
-        tzoff += t.tm_gmtoff;
-#else
-#  ifdef HAVE_TIMEZONE
-        tzset();    /* making sure */
-        tzoff -= timezone;
-#  endif
-#endif
-      }
-    }
-  }
-
-  t.tm_isdst = -1;
-
-  retval = mktime(&t);
-
-  retval += tzoff;
-
-  return retval;
-}
-
-/**
- * Derived from libjabber/jid.c, because the libjabber version is not
- * really convenient for our usage.
- *
- * Check if the full JID is valid
- * Return 0 if it is valid, non zero otherwise
- */
-int check_jid_syntax(const char *fjid)
-{
-  const char *str;
-  const char *domain, *resource;
-  int domlen;
-#ifdef HAVE_LIBIDN
-  char *idnpp;
-  int r;
-#endif
-
-  if (!fjid) return 1;
-
-  domain = strchr(fjid, JID_DOMAIN_SEPARATOR);
-
-  /* the username is optional */
-  if (!domain) {
-    domain = fjid;
-  } else {
-    /* node identifiers may not be longer than 1023 bytes */
-    if ((domain == fjid) || (domain-fjid > 1023))
-      return 1;
-    domain++;
-
-#ifdef HAVE_LIBIDN
-    idnpp = idnprep;
-    str = fjid;
-    while (*str != JID_DOMAIN_SEPARATOR)
-      *idnpp++ = *str++;
-    *idnpp = 0;
-
-    r = stringprep(idnprep, 1023, 0, stringprep_xmpp_nodeprep);
-    if (r != STRINGPREP_OK || !idnprep[0])
-      return 1;
-    /* the username looks okay */
-#else
-    /* check for low and invalid ascii characters in the username */
-    for (str = fjid; *str != JID_DOMAIN_SEPARATOR; str++) {
-      if (*str <= ' ' || *str == ':' || *str == JID_DOMAIN_SEPARATOR ||
-              *str == '<' || *str == '>' || *str == '\'' ||
-              *str == '"' || *str == '&') {
-        return 1;
-      }
-    }
-    /* the username is okay as far as we can tell without LIBIDN */
-#endif
-  }
-
-  resource = strchr(domain, JID_RESOURCE_SEPARATOR);
-
-  /* the resource is optional */
-  if (resource) {
-    domlen = resource - domain;
-    resource++;
-    /* resources may not be longer than 1023 bytes */
-    if ((*resource == '\0') || strlen(resource) > 1023)
-      return 1;
-#ifdef HAVE_LIBIDN
-    strncpy(idnprep, resource, sizeof(idnprep));
-    r = stringprep(idnprep, 1023, 0, stringprep_xmpp_resourceprep);
-    if (r != STRINGPREP_OK || !idnprep[0])
-      return 1;
-#endif
-  } else {
-    domlen = strlen(domain);
-  }
-
-  /* there must be a domain identifier */
-  if (domlen == 0) return 1;
-
-  /* and it must not be longer than 1023 bytes */
-  if (domlen > 1023) return 1;
-
-#ifdef HAVE_LIBIDN
-  idnpp = idnprep;
-  str = domain;
-  while (*str != '\0' && *str != JID_RESOURCE_SEPARATOR)
-    *idnpp++ = *str++;
-  *idnpp = 0;
-
-  r = stringprep_nameprep(idnprep, 1023);
-  if (r != STRINGPREP_OK || !idnprep[0])
-    return 1;
-
-  if (idna_to_ascii_8z(idnprep, &idnpp, IDNA_USE_STD3_ASCII_RULES) !=
-      IDNA_SUCCESS)
-    return 1;
-  else
-    free(idnpp);
-#else
-  /* make sure the hostname is valid characters */
-  for (str = domain; *str != '\0' && *str != JID_RESOURCE_SEPARATOR; str++) {
-    if (!(isalnum(*str) || *str == '.' || *str == '-' || *str == '_'))
-      return 1;
-  }
-#endif
-
-  /* it's okay as far as we can tell */
-  return 0;
-}
-
-
-inline void mc_strtolower(char *str)
-{
-  if (!str) return;
-  for ( ; *str; str++)
-    *str = tolower(*str);
-}
-
-//  strip_arg_special_chars(string)
-// Remove quotes and backslashes before an escaped quote
-// Only quotes need a backslash
-// Ex.: ["a b"] -> [a b]; [a\"b] -> [a"b]
-void strip_arg_special_chars(char *s)
-{
-  int instring = FALSE;
-  int escape = FALSE;
-  char *p;
-
-  if (!s) return;
-
-  for (p = s; *p; p++) {
-    if (*p == '"') {
-      if (!escape) {
-        instring = !instring;
-        strcpy(p, p+1);
-        p--;
-      } else
-        escape = FALSE;
-    } else if (*p == '\\') {
-      if (!escape) {
-        strcpy(p, p+1);
-        p--;
-      }
-      escape = !escape;
-    } else
-      escape = FALSE;
-  }
-}
-
-//  split_arg(arg, n, preservelast)
-// Split the string arg into a maximum of n pieces, taking care of
-// double quotes.
-// Return a null-terminated array of strings.  This array should be freed
-// by the caller after use, for example with free_arg_lst().
-// If dontstriplast is true, the Nth argument isn't stripped (i.e. no
-// processing of quote chars)
-char **split_arg(const char *arg, unsigned int n, int dontstriplast)
-{
-  char **arglst;
-  const char *p, *start, *end;
-  unsigned int i = 0;
-  int instring = FALSE;
-  int escape = FALSE;
-
-  arglst = g_new0(char*, n+1);
-
-  if (!arg || !n) return arglst;
-
-  // Skip leading space
-  for (start = arg; *start && *start == ' '; start++) ;
-  // End of string pointer
-  for (end = start; *end; end++) ;
-  // Skip trailing space
-  while (end > start+1 && *(end-1) == ' ')
-    end--;
-
-  for (p = start; p < end; p++) {
-    if (*p == '"' && !escape)
-      instring = !instring;
-    if (*p == '\\' && !escape)
-      escape = TRUE;
-    else if (escape)
-      escape = FALSE;
-    if (*p == ' ' && !instring && i+1 < n) {
-      // end of parameter
-      *(arglst+i) = g_strndup(start, p-start);
-      strip_arg_special_chars(*(arglst+i));
-      for (start = p+1; *start && *start == ' '; start++) ;
-      p = start-1;
-      i++;
-    }
-  }
-
-  if (start < end) {
-    *(arglst+i) = g_strndup(start, end-start);
-    if (!dontstriplast || i+1 < n)
-      strip_arg_special_chars(*(arglst+i));
-  }
-
-  return arglst;
-}
-
-//  free_arg_lst(arglst)
-// Free an array allocated by split_arg()
-void free_arg_lst(char **arglst)
-{
-  char **arg_elt;
-
-  for (arg_elt = arglst; *arg_elt; arg_elt++)
-    g_free(*arg_elt);
-  g_free(arglst);
-}
-
-//  replace_nl_with_dots(bufstr)
-// Replace '\n' with "(...)" (or with a NUL if the string is too short)
-void replace_nl_with_dots(char *bufstr)
-{
-  char *p = strchr(bufstr, '\n');
-  if (p) {
-    if (strlen(p) >= 5)
-      strcpy(p, "(...)");
-    else
-      *p = 0;
-  }
-}
-
-//  ut_expand_tabs(text)
-// Expand tabs and filter out some bad chars in string text.
-// If there is no tab and no bad chars in the string, a pointer to text
-// is returned (be careful _not_ to free the pointer in this case).
-// If there are some tabs or bad chars, a new string with expanded chars
-// and no bad chars is returned; this is up to the caller to free this
-// string after use.
-char *ut_expand_tabs(const char *text)
-{
-  char *xtext, *linestart;
-  char *p, *q;
-  guint n = 0, bc = 0;
-
-  xtext = (char*)text;
-  for (p=xtext; *p; p++)
-    if (*p == '\t')
-      n++;
-    else if (*p == '\x0d')
-      bc++;
-  // XXX Are there other special chars we should filter out?
-
-  if (!n && !bc)
-    return (char*)text;
-
-  xtext = g_new(char, strlen(text) + 1 + 8*n);
-  p = (char*)text;
-  q = linestart = xtext;
-  do {
-    if (*p == '\t') {
-      do { *q++ = ' '; } while ((q-linestart)%8);
-    } else if (*p != '\x0d') {
-      *q++ = *p;
-      if (*p =='\n')
-        linestart = q;
-    }
-  } while (*p++);
-
-  return xtext;
-}
-
-
-/* Cygwin's newlib does not have strcasestr() */
-/* The author of the code before the endif is
- *    Jeffrey Stedfast <fejj@ximian.com>
- * and this code is reusable in compliance with the GPL v2. -- somian */
-
-#if !defined(HAVE_STRCASESTR)
-
-#  define lowercase(c)  (isupper ((int) (c)) ? tolower ((int) (c)) : (int) (c))
-#  define bm_index(c, icase)      ((icase) ? lowercase (c) : (int) (c))
-#  define bm_equal(c1, c2, icase) ((icase) ? lowercase (c1) == lowercase (c2) : (c1) == (c2))
-
-/* FIXME: this is just a guess... should really do some performace tests to get an accurate measure */
-#  define bm_optimal(hlen, nlen)  (((hlen) ? (hlen) > 20 : 1) && (nlen) > 10 ? 1 : 0)
-
-static unsigned char *
-__boyer_moore (const unsigned char *haystack, size_t haystacklen,
-               const unsigned char *needle, size_t needlelen, int icase)
-{
-  register unsigned char *hc_ptr, *nc_ptr;
-  unsigned char *he_ptr, *ne_ptr, *h_ptr;
-  size_t skiptable[256], n;
-  register int i;
-
-#ifdef BOYER_MOORE_CHECKS
-  /* we don't need to do these checks since memmem/strstr/etc do it already */
-  /* if the haystack is shorter than the needle then we can't possibly match */
-  if (haystacklen < needlelen)
-    return NULL;
-
-  /* instant match if the pattern buffer is 0-length */
-  if (needlelen == 0)
-    return (unsigned char *) haystack;
-#endif /* BOYER_MOORE_CHECKS */
-
-  /* set a pointer at the end of each string */
-  ne_ptr = (unsigned char *) needle + needlelen - 1;
-  he_ptr = (unsigned char *) haystack + haystacklen - 1;
-
-  /* create our skip table */
-  for (i = 0; i < 256; i++)
-    skiptable[i] = needlelen;
-  for (nc_ptr = (unsigned char *) needle; nc_ptr < ne_ptr; nc_ptr++)
-    skiptable[bm_index (*nc_ptr, icase)] = (size_t) (ne_ptr - nc_ptr);
-
-  h_ptr = (unsigned char *) haystack;
-  while (haystacklen >= needlelen) {
-    hc_ptr = h_ptr + needlelen - 1;   /* set the haystack compare pointer */
-    nc_ptr = ne_ptr;                  /* set the needle compare pointer */
-
-    /* work our way backwards till they don't match */
-    for (i = 0; nc_ptr > (unsigned char *) needle; nc_ptr--, hc_ptr--, i++)
-      if (!bm_equal (*nc_ptr, *hc_ptr, icase))
-        break;
-
-    if (!bm_equal (*nc_ptr, *hc_ptr, icase)) {
-      n = skiptable[bm_index (*hc_ptr, icase)];
-      if (n == needlelen && i)
-        if (bm_equal (*ne_ptr, ((unsigned char *) needle)[0], icase))
-          n--;
-      h_ptr += n;
-      haystacklen -= n;
-    } else
-      return (unsigned char *) h_ptr;
-  }
-
-  return NULL;
-}
-
-/*
- * strcasestr:
- * @haystack: string to search
- * @needle: substring to search for
- *
- * Finds the first occurence of the substring @needle within the
- * string @haystack ignoring case.
- *
- * Returns a pointer to the beginning of the substring match within
- * @haystack, or NULL if the substring is not found.
- **/
-char *
-strcasestr (const char *haystack, const char *needle)
-{
-  register unsigned char *h, *n, *hc, *nc;
-  size_t needlelen;
-
-  needlelen = strlen (needle);
-
-  if (needlelen == 0) {
-    return (char *) haystack;
-  } else if (bm_optimal (0, needlelen)) {
-    return (char *) __boyer_moore ((const unsigned char *) haystack,
-                                   strlen (haystack),
-                                   (const unsigned char *) needle,
-                                   needlelen, 1);
-  }
-
-  h = (unsigned char *) haystack;
-  n = (unsigned char *) needle;
-
-  while (*(h + needlelen - 1)) {
-    if (lowercase (*h) == lowercase (*n)) {
-      for (hc = h + 1, nc = n + 1; *hc && *nc; hc++, nc++)
-        if (lowercase (*hc) != lowercase (*nc))
-          break;
-
-      if (!*nc)
-        return (char *) h;
-    }
-    h++;
-  }
-  return NULL;
-}
-#endif /* !HAVE_STRCASESTR */
-
-//  startswith(str, word, ignore_case)
-// Returns TRUE if string str starts with word.
-int startswith(const char *str, const char *word, guint ignore_case)
-{
-  if (ignore_case && !strncasecmp(str, word, strlen(word)))
-    return TRUE;
-  else if (!ignore_case && !strncmp(str, word, strlen(word)))
-    return TRUE;
-  return FALSE;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/utils.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-#ifndef __UTILS_H__
-#define __UTILS_H__ 1
-
-#include <config.h>
-
-extern const char *LocaleCharSet;
-
-#define to_utf8(s)   ((s) ? g_locale_to_utf8((s),   -1, NULL,NULL,NULL) : NULL)
-#define from_utf8(s) ((s) ? g_convert_with_fallback((s), -1, LocaleCharSet, \
-                                        "UTF-8", NULL,NULL,NULL,NULL) : NULL)
-
-#define JID_RESOURCE_SEPARATOR      '/'
-#define JID_RESOURCE_SEPARATORSTR   "/"
-#define JID_DOMAIN_SEPARATOR        '@'
-#define JID_DOMAIN_SEPARATORSTR     "@"
-
-char *jidtodisp(const char *fjid);
-char *jid_get_username(const char *fjid);
-char *compose_jid(const char *username, const char *servername,
-                  const char *resource);
-gboolean jid_equal(const char *jid1, const char *jid2);
-
-void fingerprint_to_hex(const unsigned char *fpr, char hex[49]);
-gboolean hex_to_fingerprint(const char * hex, char fpr[16]);
-
-void ut_InitDebug(int level, const char *file);
-void ut_WriteLog(unsigned int flag, const char *data);
-
-char *expand_filename(const char *fname);
-
-int checkset_perm(const char *name, unsigned int setmode);
-
-const char *ut_get_tmpdir(void);
-
-int    to_iso8601(char *dststr, time_t timestamp);
-time_t from_iso8601(const char *timestamp, int utc);
-
-int check_jid_syntax(const char *fjid);
-
-void mc_strtolower(char *str);
-
-void strip_arg_special_chars(char *s);
-char **split_arg(const char *arg, unsigned int n, int dontstriplast);
-void free_arg_lst(char **arglst);
-
-void replace_nl_with_dots(char *bufstr);
-char *ut_expand_tabs(const char *text);
-
-#if !defined (HAVE_STRCASESTR)
-char *strcasestr(const char *haystack, const char *needle);
-#endif
-
-int startswith(const char *str, const char *word, guint ignore_case);
-
-#endif // __UTILS_H__
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2200 +0,0 @@
-/*
- * xmpp.c       -- Jabber protocol handling
- *
- * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- * Parts come from the centericq project:
- * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-#include <stdlib.h>
-#include <string.h>
-
-#include "xmpp.h"
-#include "xmpp_helper.h"
-#include "xmpp_iq.h"
-#include "xmpp_iqrequest.h"
-#include "xmpp_muc.h"
-#include "xmpp_s10n.h"
-#include "caps.h"
-#include "events.h"
-#include "histolog.h"
-#include "hooks.h"
-#include "otr.h"
-#include "roster.h"
-#include "screen.h"
-#include "settings.h"
-#include "utils.h"
-#include "main.h"
-
-#define RECONNECTION_TIMEOUT    60L
-
-LmConnection* lconnection;
-static guint AutoConnection;
-
-inline void update_last_use(void);
-inline gboolean xmpp_reconnect();
-
-enum imstatus mystatus = offline;
-static enum imstatus mywantedstatus = available;
-gchar *mystatusmsg;
-
-char imstatus2char[imstatus_size+1] = {
-    '_', 'o', 'f', 'd', 'n', 'a', 'i', '\0'
-};
-
-char *imstatus_showmap[] = {
-  "",
-  "",
-  "chat",
-  "dnd",
-  "xa",
-  "away",
-  ""
-};
-
-LmMessageNode *bookmarks = NULL;
-LmMessageNode *rosternotes = NULL;
-
-static struct IqHandlers
-{
-  const gchar *xmlns;
-  LmHandleMessageFunction handler;
-} iq_handlers[] = {
-  {NS_PING,       &handle_iq_ping},
-  {NS_VERSION,    &handle_iq_version},
-  {NS_TIME,       &handle_iq_time},
-  {NS_ROSTER,     &handle_iq_roster},
-  {NS_XMPP_TIME,  &handle_iq_time202},
-  {NS_LAST,       &handle_iq_last},
-  {NS_DISCO_INFO, &handle_iq_disco_info},
-  {NS_DISCO_ITEMS,&handle_iq_disco_items},
-  {NS_COMMANDS,   &handle_iq_commands},
-  {NS_VCARD,      &handle_iq_vcard},
-  {NULL, NULL}
-};
-
-void update_last_use(void)
-{
-  iqlast = time(NULL);
-}
-
-// Note: the caller should check the jid is correct
-void xmpp_addbuddy(const char *bjid, const char *name, const char *group)
-{
-  LmMessageNode *query, *y;
-  LmMessage *iq;
-  char *cleanjid;
-
-  if (!lm_connection_is_authenticated(lconnection)) return;
-
-  cleanjid = jidtodisp(bjid); // Stripping resource, just in case...
-
-  // We don't check if the jabber user already exists in the roster,
-  // because it allows to re-ask for notification.
-
-  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_SET);
-  query = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_ROSTER);
-  y = lm_message_node_add_child(query, "item", NULL);
-  lm_message_node_set_attribute(y, "jid", cleanjid);
-
-  if (name)
-    lm_message_node_set_attribute(y, "name", name);
-
-  if (group)
-    lm_message_node_add_child(y, "group", group);
-
-  lm_connection_send(lconnection, iq, NULL);
-  lm_message_unref(iq);
-
-  xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_SUBSCRIBE);
-
-  roster_add_user(cleanjid, name, group, ROSTER_TYPE_USER, sub_pending, -1);
-  g_free(cleanjid);
-  buddylist_build();
-
-  update_roster = TRUE;
-}
-
-void xmpp_updatebuddy(const char *bjid, const char *name, const char *group)
-{
-  LmMessage *iq;
-  LmMessageNode *x;
-  char *cleanjid;
-
-  if (!lm_connection_is_authenticated(lconnection)) return;
-
-  // XXX We should check name's and group's correctness
-
-  cleanjid = jidtodisp(bjid); // Stripping resource, just in case...
-
-  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_SET);
-  x = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(x, "xmlns", NS_ROSTER);
-  x = lm_message_node_add_child(x, "item", NULL);
-  lm_message_node_set_attributes(x,
-                                 "jid", cleanjid,
-                                 "name", name,
-                                 NULL);
-
-  if (group)
-    lm_message_node_add_child(x, "group", group);
-
-  lm_connection_send(lconnection, iq, NULL);
-  lm_message_unref(iq);
-  g_free(cleanjid);
-}
-
-void xmpp_delbuddy(const char *bjid)
-{
-  LmMessageNode *y, *z;
-  LmMessage *iq;
-  char *cleanjid;
-
-  if (!lm_connection_is_authenticated(lconnection)) return;
-
-  cleanjid = jidtodisp(bjid); // Stripping resource, just in case...
-
-  // If the current buddy is an agent, unsubscribe from it
-  if (roster_gettype(cleanjid) == ROSTER_TYPE_AGENT) {
-    scr_LogPrint(LPRINT_LOGNORM, "Unregistering from the %s agent", cleanjid);
-
-    iq = lm_message_new_with_sub_type(cleanjid, LM_MESSAGE_TYPE_IQ,
-                                      LM_MESSAGE_SUB_TYPE_SET);
-    y = lm_message_node_add_child(iq->node, "query", NULL);
-    lm_message_node_set_attribute(y, "xmlns", NS_REGISTER);
-    lm_message_node_add_child(y, "remove", NULL);
-    lm_connection_send(lconnection, iq, NULL);
-    lm_message_unref(iq);
-  }
-
-  // Cancel the subscriptions
-  xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED); //cancel "from"
-  xmpp_send_s10n(cleanjid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE);  //cancel "to"
-
-  // Ask for removal from roster
-  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_SET);
-
-  y = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(y, "xmlns", NS_ROSTER);
-  z = lm_message_node_add_child(y, "item", NULL);
-  lm_message_node_set_attributes(z,
-                                 "jid", cleanjid,
-                                 "subscription", "remove",
-                                 NULL);
-  lm_connection_send(lconnection, iq, NULL);
-  lm_message_unref(iq);
-
-  roster_del_user(cleanjid);
-  g_free(cleanjid);
-  buddylist_build();
-
-  update_roster = TRUE;
-}
-
-void xmpp_request(const char *fjid, enum iqreq_type reqtype)
-{
-  GSList *resources, *p_res;
-  GSList *roster_elt;
-  const char *strreqtype, *xmlns;
-
-  if (reqtype == iqreq_version) {
-    xmlns = NS_VERSION;
-    strreqtype = "version";
-  } else if (reqtype == iqreq_time) {
-    xmlns = NS_TIME;
-    strreqtype = "time";
-  } else if (reqtype == iqreq_last) {
-    xmlns = NS_LAST;
-    strreqtype = "last";
-  } else if (reqtype == iqreq_vcard) {
-    xmlns = NS_VCARD;
-    strreqtype = "vCard";
-    // Special case
-  } else
-    return;
-
-  if (strchr(fjid, JID_RESOURCE_SEPARATOR)) {
-    // This is a full JID
-    xmpp_iq_request(fjid, xmlns);
-    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid);
-    return;
-  }
-
-  // The resource has not been specified
-  roster_elt = roster_find(fjid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
-  if (!roster_elt) {
-    scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid);
-    xmpp_iq_request(fjid, xmlns); // Let's send a request anyway...
-    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid);
-    return;
-  }
-
-  // Send a request to each resource
-  resources = buddy_getresources(roster_elt->data);
-  if (!resources) {
-    scr_LogPrint(LPRINT_NORMAL, "No known resource for <%s>...", fjid);
-    xmpp_iq_request(fjid, xmlns); // Let's send a request anyway...
-    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fjid);
-  }
-  for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
-    gchar *fulljid;
-    fulljid = g_strdup_printf("%s/%s", fjid, (char*)p_res->data);
-    xmpp_iq_request(fulljid, xmlns);
-    scr_LogPrint(LPRINT_NORMAL, "Sent %s request to <%s>", strreqtype, fulljid);
-    g_free(fulljid);
-    g_free(p_res->data);
-  }
-  g_slist_free(resources);
-}
-
-static LmHandlerResult cb_xep184(LmMessageHandler *h, LmConnection *c,
-                                 LmMessage *m, gpointer user_data)
-{
-  char *from = jidtodisp(lm_message_get_from(m));
-  scr_RemoveReceiptFlag(from, h);
-  g_free(from);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-//  xmpp_send_msg(jid, text, type, subject,
-//                otrinject, *encrypted, type_overwrite)
-// When encrypted is not NULL, the function set *encrypted to 1 if the
-// message has been PGP-encrypted.  If encryption enforcement is set and
-// encryption fails, *encrypted is set to -1.
-void xmpp_send_msg(const char *fjid, const char *text, int type,
-                   const char *subject, gboolean otrinject, gint *encrypted,
-                   LmMessageSubType type_overwrite, gpointer *xep184)
-{
-  LmMessage *x;
-  LmMessageSubType subtype;
-#ifdef HAVE_LIBOTR
-  int otr_msg = 0;
-#endif
-#if defined HAVE_GPGME || defined JEP0022 || defined JEP0085
-  char *rname, *barejid;
-  GSList *sl_buddy;
-#endif
-#if defined JEP0022 || defined JEP0085
-  LmMessageNode *event;
-  guint use_jep85 = 0;
-  struct jep0085 *jep85 = NULL;
-#endif
-  gchar *enc = NULL;
-
-  if (encrypted)
-    *encrypted = 0;
-
-  if (!lm_connection_is_authenticated(lconnection))
-    return;
-
-  if (!text && type == ROSTER_TYPE_USER)
-    return;
-
-  if (type_overwrite != LM_MESSAGE_SUB_TYPE_NOT_SET)
-    subtype = type_overwrite;
-  else {
-    if (type == ROSTER_TYPE_ROOM)
-      subtype = LM_MESSAGE_SUB_TYPE_GROUPCHAT;
-    else
-      subtype = LM_MESSAGE_SUB_TYPE_CHAT;
-  }
-
-#if defined HAVE_GPGME || defined HAVE_LIBOTR || \
-    defined JEP0022 || defined JEP0085
-  rname = strchr(fjid, JID_RESOURCE_SEPARATOR);
-  barejid = jidtodisp(fjid);
-  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
-
-  // If we can get a resource name, we use it.  Else we use NULL,
-  // which hopefully will give us the most likely resource.
-  if (rname)
-    rname++;
-
-#ifdef HAVE_LIBOTR
-  if (otr_enabled() && !otrinject) {
-    if (type == ROSTER_TYPE_USER) {
-      otr_msg = otr_send((char **)&text, barejid);
-      if (!text) {
-        g_free(barejid);
-        if (encrypted)
-          *encrypted = -1;
-        return;
-      }
-    }
-    if (otr_msg && encrypted)
-      *encrypted = ENCRYPTED_OTR;
-  }
-#endif
-
-#ifdef HAVE_GPGME
-  if (type == ROSTER_TYPE_USER && sl_buddy && gpg_enabled()) {
-    if (!settings_pgp_getdisabled(barejid)) { // not disabled for this contact?
-      guint force;
-      struct pgp_data *res_pgpdata;
-      force = settings_pgp_getforce(barejid);
-      res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname);
-      if (force || (res_pgpdata && res_pgpdata->sign_keyid)) {
-        /* Remote client has PGP support (we have a signature)
-         * OR encryption is enforced (force = TRUE).
-         * If the contact has a specific KeyId, we'll use it;
-         * if not, we'll use the key used for the signature.
-         * Both keys should match, in theory (cf. XEP-0027). */
-        const char *key;
-        key = settings_pgp_getkeyid(barejid);
-        if (!key && res_pgpdata)
-          key = res_pgpdata->sign_keyid;
-        if (key)
-          enc = gpg_encrypt(text, key);
-        if (!enc && force) {
-          if (encrypted)
-            *encrypted = -1;
-          g_free(barejid);
-          return;
-        }
-      }
-    }
-  }
-#endif // HAVE_GPGME
-
-  g_free(barejid);
-#endif // HAVE_GPGME || defined JEP0022 || defined JEP0085
-
-  x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE, subtype);
-  lm_message_node_add_child(x->node, "body",
-                            enc ? "This message is PGP-encrypted." : text);
-
-  if (subject)
-    lm_message_node_add_child(x->node, "subject", subject);
-
-  if (enc) {
-    LmMessageNode *y;
-    y = lm_message_node_add_child(x->node, "x", enc);
-    lm_message_node_set_attribute(y, "xmlns", NS_ENCRYPTED);
-    if (encrypted)
-      *encrypted = ENCRYPTED_PGP;
-    g_free(enc);
-  }
-
-  //XEP-0184: Message Receipts
-  if (sl_buddy && rname && xep184 &&
-      caps_has_feature(buddy_resource_getcaps(sl_buddy->data, rname),
-                       NS_RECEIPTS)) {
-    lm_message_node_set_attribute
-            (lm_message_node_add_child(x->node, "request", NULL),
-             "xmlns", NS_RECEIPTS);
-    *xep184 = lm_message_handler_new(cb_xep184, NULL, NULL);
-  }
-
-#if defined JEP0022 || defined JEP0085
-  // If typing notifications are disabled, we can skip all this stuff...
-  if (chatstates_disabled || type == ROSTER_TYPE_ROOM)
-    goto xmpp_send_msg_no_chatstates;
-
-  if (sl_buddy)
-    jep85 = buddy_resource_jep85(sl_buddy->data, rname);
-#endif
-
-#ifdef JEP0085
-  /* JEP-0085 5.1
-   * "Until receiving a reply to the initial content message (or a standalone
-   * notification) from the Contact, the User MUST NOT send subsequent chat
-   * state notifications to the Contact."
-   * In our implementation support is initially "unknown", then it's "probed"
-   * and can become "ok".
-   */
-  if (jep85 && (jep85->support == CHATSTATES_SUPPORT_OK ||
-                jep85->support == CHATSTATES_SUPPORT_UNKNOWN)) {
-    event = lm_message_node_add_child(x->node, "active", NULL);
-    lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES);
-    if (jep85->support == CHATSTATES_SUPPORT_UNKNOWN)
-      jep85->support = CHATSTATES_SUPPORT_PROBED;
-    else
-      use_jep85 = 1;
-    jep85->last_state_sent = ROSTER_EVENT_ACTIVE;
-  }
-#endif
-#ifdef JEP0022
-  /* JEP-22
-   * If the Contact supports JEP-0085, we do not use JEP-0022.
-   * If not, we try to fall back to JEP-0022.
-   */
-  if (!use_jep85) {
-    struct jep0022 *jep22 = NULL;
-    event = lm_message_node_add_child(x->node, "x", NULL);
-    lm_message_node_set_attribute(event, "xmlns", NS_EVENT);
-    lm_message_node_add_child(event, "composing", NULL);
-
-    if (sl_buddy)
-      jep22 = buddy_resource_jep22(sl_buddy->data, rname);
-    if (jep22)
-      jep22->last_state_sent = ROSTER_EVENT_ACTIVE;
-
-    // An id is mandatory when using JEP-0022.
-    if (text || subject) {
-      const gchar *msgid = lm_message_get_id(x);
-      // Let's update last_msgid_sent
-      if (jep22) {
-        g_free(jep22->last_msgid_sent);
-        jep22->last_msgid_sent = g_strdup(msgid);
-      }
-    }
-  }
-#endif
-
-xmpp_send_msg_no_chatstates:
-  if (mystatus != invisible)
-    update_last_use();
-  if (xep184 && *xep184) {
-    lm_connection_send_with_reply(lconnection, x, *xep184, NULL);
-    lm_message_handler_unref(*xep184);
-  } else
-    lm_connection_send(lconnection, x, NULL);
-  lm_message_unref(x);
-}
-
-#ifdef JEP0085
-//  xmpp_send_jep85_chatstate()
-// Send a JEP-85 chatstate.
-static void xmpp_send_jep85_chatstate(const char *bjid, const char *resname,
-                                      guint state)
-{
-  LmMessage *m;
-  LmMessageNode *event;
-  GSList *sl_buddy;
-  const char *chattag;
-  char *rjid, *fjid = NULL;
-  struct jep0085 *jep85 = NULL;
-
-  if (!lm_connection_is_authenticated(lconnection)) return;
-
-  sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER);
-
-  // If we have a resource name, we use it.  Else we use NULL,
-  // which hopefully will give us the most likely resource.
-  if (sl_buddy)
-    jep85 = buddy_resource_jep85(sl_buddy->data, resname);
-
-  if (!jep85 || (jep85->support != CHATSTATES_SUPPORT_OK))
-    return;
-
-  if (state == jep85->last_state_sent)
-    return;
-
-  if (state == ROSTER_EVENT_ACTIVE)
-    chattag = "active";
-  else if (state == ROSTER_EVENT_COMPOSING)
-    chattag = "composing";
-  else if (state == ROSTER_EVENT_PAUSED)
-    chattag = "paused";
-  else {
-    scr_LogPrint(LPRINT_LOGNORM, "Error: unsupported JEP-85 state (%d)", state);
-    return;
-  }
-
-  jep85->last_state_sent = state;
-
-  if (resname)
-    fjid = g_strdup_printf("%s/%s", bjid, resname);
-
-  rjid = resname ? fjid : (char*)bjid;
-  m = lm_message_new_with_sub_type(rjid, LM_MESSAGE_TYPE_MESSAGE,
-                                   LM_MESSAGE_SUB_TYPE_CHAT);
-
-  event = lm_message_node_add_child(m->node, chattag, NULL);
-  lm_message_node_set_attribute(event, "xmlns", NS_CHATSTATES);
-
-  lm_connection_send(lconnection, m, NULL);
-  lm_message_unref(m);
-
-  g_free(fjid);
-}
-#endif
-
-#ifdef JEP0022
-//  xmpp_send_jep22_event()
-// Send a JEP-22 message event (delivered, composing...).
-static void xmpp_send_jep22_event(const char *fjid, guint type)
-{
-  LmMessage *x;
-  LmMessageNode *event;
-  const char *msgid;
-  char *rname, *barejid;
-  GSList *sl_buddy;
-  struct jep0022 *jep22 = NULL;
-  guint jep22_state;
-
-  if (!lm_connection_is_authenticated(lconnection)) return;
-
-  rname = strchr(fjid, JID_RESOURCE_SEPARATOR);
-  barejid = jidtodisp(fjid);
-  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
-  g_free(barejid);
-
-  // If we can get a resource name, we use it.  Else we use NULL,
-  // which hopefully will give us the most likely resource.
-  if (rname)
-    rname++;
-  if (sl_buddy)
-    jep22 = buddy_resource_jep22(sl_buddy->data, rname);
-
-  if (!jep22)
-    return; // XXX Maybe we could try harder (other resources?)
-
-  msgid = jep22->last_msgid_rcvd;
-
-  // For composing events (composing, active, inactive, paused...),
-  // JEP22 only has 2 states; we'll use composing and active.
-  if (type == ROSTER_EVENT_COMPOSING)
-    jep22_state = ROSTER_EVENT_COMPOSING;
-  else if (type == ROSTER_EVENT_ACTIVE ||
-           type == ROSTER_EVENT_PAUSED)
-    jep22_state = ROSTER_EVENT_ACTIVE;
-  else
-    jep22_state = 0; // ROSTER_EVENT_NONE
-
-  if (jep22_state) {
-    // Do not re-send a same event
-    if (jep22_state == jep22->last_state_sent)
-      return;
-    jep22->last_state_sent = jep22_state;
-  }
-
-  x = lm_message_new_with_sub_type(fjid, LM_MESSAGE_TYPE_MESSAGE,
-                                   LM_MESSAGE_SUB_TYPE_CHAT);
-
-  event = lm_message_node_add_child(x->node, "x", NULL);
-  lm_message_node_set_attribute(event, "xmlns", NS_EVENT);
-  if (type == ROSTER_EVENT_DELIVERED)
-    lm_message_node_add_child(event, "delivered", NULL);
-  else if (type == ROSTER_EVENT_COMPOSING)
-    lm_message_node_add_child(event, "composing", NULL);
-  lm_message_node_add_child(event, "id", msgid);
-
-  lm_connection_send(lconnection, x, NULL);
-  lm_message_unref(x);
-}
-#endif
-
-//  xmpp_send_chatstate(buddy, state)
-// Send a chatstate or event (JEP-22/85) according to the buddy's capabilities.
-// The message is sent to one of the resources with the highest priority.
-#if defined JEP0022 || defined JEP0085
-void xmpp_send_chatstate(gpointer buddy, guint chatstate)
-{
-  const char *bjid;
-#ifdef JEP0085
-  GSList *resources, *p_res, *p_next;
-  struct jep0085 *jep85 = NULL;
-#endif
-#ifdef JEP0022
-  struct jep0022 *jep22;
-#endif
-
-  bjid = buddy_getjid(buddy);
-  if (!bjid) return;
-
-#ifdef JEP0085
-  /* Send the chatstate to the last resource (which should have the highest
-     priority).
-     If chatstate is "active", send an "active" state to all resources
-     which do not curently have this state.
-   */
-  resources = buddy_getresources(buddy);
-  for (p_res = resources ; p_res ; p_res = p_next) {
-    p_next = g_slist_next(p_res);
-    jep85 = buddy_resource_jep85(buddy, p_res->data);
-    if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK) {
-      // If p_next is NULL, this is the highest (prio) resource, i.e.
-      // the one we are probably writing to.
-      if (!p_next || (jep85->last_state_sent != ROSTER_EVENT_ACTIVE &&
-                      chatstate == ROSTER_EVENT_ACTIVE))
-        xmpp_send_jep85_chatstate(bjid, p_res->data, chatstate);
-    }
-    g_free(p_res->data);
-  }
-  g_slist_free(resources);
-  // If the last resource had chatstates support when can return now,
-  // we don't want to send a JEP22 event.
-  if (jep85 && jep85->support == CHATSTATES_SUPPORT_OK)
-    return;
-#endif
-#ifdef JEP0022
-  jep22 = buddy_resource_jep22(buddy, NULL);
-  if (jep22 && jep22->support == CHATSTATES_SUPPORT_OK) {
-    xmpp_send_jep22_event(bjid, chatstate);
-  }
-#endif
-}
-#endif
-
-
-//  chatstates_reset_probed(fulljid)
-// If the JEP has been probed for this contact, set it back to unknown so
-// that we probe it again.  The parameter must be a full jid (w/ resource).
-#if defined JEP0022 || defined JEP0085
-static void chatstates_reset_probed(const char *fulljid)
-{
-  char *rname, *barejid;
-  GSList *sl_buddy;
-  struct jep0085 *jep85;
-  struct jep0022 *jep22;
-
-  rname = strchr(fulljid, JID_RESOURCE_SEPARATOR);
-  if (!rname++)
-    return;
-
-  barejid = jidtodisp(fulljid);
-  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
-  g_free(barejid);
-
-  if (!sl_buddy)
-    return;
-
-  jep85 = buddy_resource_jep85(sl_buddy->data, rname);
-  jep22 = buddy_resource_jep22(sl_buddy->data, rname);
-
-  if (jep85 && jep85->support == CHATSTATES_SUPPORT_PROBED)
-    jep85->support = CHATSTATES_SUPPORT_UNKNOWN;
-  if (jep22 && jep22->support == CHATSTATES_SUPPORT_PROBED)
-    jep22->support = CHATSTATES_SUPPORT_UNKNOWN;
-}
-#endif
-
-#ifdef HAVE_GPGME
-//  keys_mismatch(key, expectedkey)
-// Return TRUE if both keys are non-null and "expectedkey" doesn't match
-// the end of "key".
-// If one of the keys is null, return FALSE.
-// If expectedkey is less than 8 bytes long, return TRUE.
-//
-// Example: keys_mismatch("C9940A9BB0B92210", "B0B92210") will return FALSE.
-static bool keys_mismatch(const char *key, const char *expectedkey)
-{
-  int lk, lek;
-
-  if (!expectedkey || !key)
-    return FALSE;
-
-  lk = strlen(key);
-  lek = strlen(expectedkey);
-
-  // If the expectedkey is less than 8 bytes long, this is probably a
-  // user mistake so we consider it's a mismatch.
-  if (lek < 8)
-    return TRUE;
-
-  if (lek < lk)
-    key += lk - lek;
-
-  return strcasecmp(key, expectedkey);
-}
-#endif
-
-//  check_signature(barejid, resourcename, xmldata, text)
-// Verify the signature (in xmldata) of "text" for the contact
-// barejid/resourcename.
-// xmldata is the 'jabber:x:signed' stanza.
-// If the key id is found, the contact's PGP data are updated.
-static void check_signature(const char *barejid, const char *rname,
-                            LmMessageNode *node, const char *text)
-{
-#ifdef HAVE_GPGME
-  const char *p, *key;
-  GSList *sl_buddy;
-  struct pgp_data *res_pgpdata;
-  gpgme_sigsum_t sigsum;
-
-  // All parameters must be valid
-  if (!(node && barejid && rname && text))
-    return;
-
-  if (!gpg_enabled())
-    return;
-
-  // Get the resource PGP data structure
-  sl_buddy = roster_find(barejid, jidsearch, ROSTER_TYPE_USER);
-  if (!sl_buddy)
-    return;
-  res_pgpdata = buddy_resource_pgp(sl_buddy->data, rname);
-  if (!res_pgpdata)
-    return;
-
-  if (!node->name || strcmp(node->name, "x")) //XXX: probably useless
-    return; // We expect "<x xmlns='jabber:x:signed'>"
-
-  // Get signature
-  p = lm_message_node_get_value(node);
-  if (!p)
-    return;
-
-  key = gpg_verify(p, text, &sigsum);
-  if (key) {
-    const char *expectedkey;
-    char *buf;
-    g_free(res_pgpdata->sign_keyid);
-    res_pgpdata->sign_keyid = (char *)key;
-    res_pgpdata->last_sigsum = sigsum;
-    if (sigsum & GPGME_SIGSUM_RED) {
-      buf = g_strdup_printf("Bad signature from <%s/%s>", barejid, rname);
-      scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0);
-      scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-      g_free(buf);
-    }
-    // Verify that the key id is the one we expect.
-    expectedkey = settings_pgp_getkeyid(barejid);
-    if (keys_mismatch(key, expectedkey)) {
-      buf = g_strdup_printf("Warning: The KeyId from <%s/%s> doesn't match "
-                            "the key you set up", barejid, rname);
-      scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0);
-      scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-      g_free(buf);
-    }
-  }
-#endif
-}
-
-static LmSSLResponse ssl_cb(LmSSL *ssl, LmSSLStatus status, gpointer ud)
-{
-  scr_LogPrint(LPRINT_LOGNORM, "SSL status:%d", status);
-
-  switch (status) {
-  case LM_SSL_STATUS_NO_CERT_FOUND:
-    scr_LogPrint(LPRINT_LOGNORM, "No certificate found!");
-    break;
-  case LM_SSL_STATUS_UNTRUSTED_CERT:
-    scr_LogPrint(LPRINT_LOGNORM, "Certificate is not trusted!");
-    break;
-  case LM_SSL_STATUS_CERT_EXPIRED:
-    scr_LogPrint(LPRINT_LOGNORM, "Certificate has expired!");
-    break;
-  case LM_SSL_STATUS_CERT_NOT_ACTIVATED:
-    scr_LogPrint(LPRINT_LOGNORM, "Certificate has not been activated!");
-    break;
-  case LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH:
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Certificate hostname does not match expected hostname!");
-    break;
-  case LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH: {
-    char fpr[49];
-    fingerprint_to_hex((const unsigned char*)lm_ssl_get_fingerprint(ssl),
-                       fpr);
-    scr_LogPrint(LPRINT_LOGNORM,
-              "Certificate fingerprint does not match expected fingerprint!");
-    scr_LogPrint(LPRINT_LOGNORM, "Remote fingerprint: %s", fpr);
-
-    scr_LogPrint(LPRINT_LOGNORM, "Expected fingerprint: %s",
-                 settings_opt_get("ssl_fingerprint"));
-
-    return LM_SSL_RESPONSE_STOP;
-    break;
-  }
-  case LM_SSL_STATUS_GENERIC_ERROR:
-    scr_LogPrint(LPRINT_LOGNORM, "Generic SSL error!");
-    break;
-  }
-
-  if (settings_opt_get_int("ssl_ignore_checks"))
-    return LM_SSL_RESPONSE_CONTINUE;
-  return LM_SSL_RESPONSE_STOP;
-}
-
-static void connection_auth_cb(LmConnection *connection, gboolean success,
-                               gpointer user_data)
-{
-  if (success) {
-    LmMessage *m;
-
-    m = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_PRESENCE,
-                                     LM_MESSAGE_SUB_TYPE_AVAILABLE);
-    lm_connection_send(connection, m, NULL);
-
-    lm_message_unref(m);
-    xmpp_setprevstatus();
-    xmpp_iq_request(NULL, NS_ROSTER);
-    xmpp_request_storage("storage:bookmarks");
-    xmpp_request_storage("storage:rosternotes");
-
-    AutoConnection = TRUE;
-  } else
-    scr_LogPrint(LPRINT_LOGNORM, "Authentication failed");
-}
-
-gboolean xmpp_reconnect()
-{
-  if (!lm_connection_is_authenticated(lconnection))
-    xmpp_connect();
-  return FALSE;
-}
-
-static void _try_to_reconnect(void)
-{
-  if (AutoConnection)
-    g_timeout_add_seconds(RECONNECTION_TIMEOUT, xmpp_reconnect, NULL);
-}
-
-static void connection_open_cb(LmConnection *connection, gboolean success,
-                               gpointer user_data)
-{
-  GError *error = NULL;
-
-  if (success) {
-    const char *password, *resource;
-    char *username;
-    username   = jid_get_username(settings_opt_get("jid"));
-    password   = settings_opt_get("password");
-    resource   = strchr(lm_connection_get_jid(connection),
-                        JID_RESOURCE_SEPARATOR);
-    if (resource)
-      resource++;
-
-    if (!lm_connection_authenticate(lconnection, username, password, resource,
-                                    connection_auth_cb, NULL, FALSE, &error)) {
-      scr_LogPrint(LPRINT_LOGNORM, "Failed to authenticate: %s\n",
-                   error->message);
-      g_error_free (error);
-      _try_to_reconnect();
-    }
-    g_free(username);
-  } else {
-    scr_LogPrint(LPRINT_LOGNORM, "There was an error while connecting.");
-    _try_to_reconnect();
-  }
-}
-
-static void connection_close_cb(LmConnection *connection,
-                                LmDisconnectReason reason,
-                                gpointer user_data)
-{
-  const char *str;
-
-  switch (reason) {
-  case LM_DISCONNECT_REASON_OK:
-          str = "LM_DISCONNECT_REASON_OK";
-          break;
-  case LM_DISCONNECT_REASON_PING_TIME_OUT:
-          str = "LM_DISCONNECT_REASON_PING_TIME_OUT";
-          break;
-  case LM_DISCONNECT_REASON_HUP:
-          str = "LM_DISCONNECT_REASON_HUP";
-          break;
-  case LM_DISCONNECT_REASON_ERROR:
-          str = "LM_DISCONNECT_REASON_ERROR";
-          break;
-  case LM_DISCONNECT_REASON_UNKNOWN:
-  default:
-          str = "LM_DISCONNECT_REASON_UNKNOWN";
-          break;
-  }
-
-  if (reason != LM_DISCONNECT_REASON_OK)
-    _try_to_reconnect();
-
-  // Free bookmarks
-  if (bookmarks)
-    lm_message_node_unref(bookmarks);
-  bookmarks = NULL;
-  // Free roster
-  roster_free();
-  if (rosternotes)
-    lm_message_node_unref(rosternotes);
-  rosternotes = NULL;
-  // Update display
-  update_roster = TRUE;
-  scr_UpdateBuddyWindow();
-
-  scr_LogPrint(LPRINT_NORMAL, "Disconnected, reason:%d->'%s'\n", reason, str);
-}
-
-static void handle_state_events(const char *from, LmMessageNode *node)
-{
-#if defined JEP0022 || defined JEP0085
-  LmMessageNode *state_ns = NULL;
-  const char *body;
-  char *rname, *bjid;
-  GSList *sl_buddy;
-  guint events;
-  struct jep0022 *jep22 = NULL;
-  struct jep0085 *jep85 = NULL;
-  enum {
-    JEP_none,
-    JEP_85,
-    JEP_22
-  } which_jep = JEP_none;
-
-  rname = strchr(from, JID_RESOURCE_SEPARATOR);
-  if (rname)
-    ++rname;
-  else
-    rname = (char *)from + strlen(from);
-  bjid  = jidtodisp(from);
-  sl_buddy = roster_find(bjid, jidsearch, ROSTER_TYPE_USER);
-  g_free(bjid);
-
-  /* XXX Actually that's wrong, since it filters out server "offline"
-     messages (for JEP-0022).  This JEP is (almost) deprecated so
-     we don't really care. */
-  if (!sl_buddy) {
-    return;
-  }
-
-  /* Let's see chich JEP the contact uses.  If possible, we'll use
-     JEP-85, if not we'll look for JEP-22 support. */
-  events = buddy_resource_getevents(sl_buddy->data, rname);
-
-  jep85 = buddy_resource_jep85(sl_buddy->data, rname);
-  if (jep85) {
-    state_ns = lm_message_node_find_xmlns(node, NS_CHATSTATES);
-    if (state_ns)
-      which_jep = JEP_85;
-  }
-
-  if (which_jep != JEP_85) { /* Fall back to JEP-0022 */
-    jep22 = buddy_resource_jep22(sl_buddy->data, rname);
-    if (jep22) {
-      state_ns = lm_message_node_find_xmlns(node, NS_EVENT);
-      if (state_ns)
-        which_jep = JEP_22;
-    }
-  }
-
-  if (!which_jep) { /* Sender does not use chat states */
-    return;
-  }
-
-  body = lm_message_node_get_child_value(node, "body");
-
-  if (which_jep == JEP_85) { /* JEP-0085 */
-    jep85->support = CHATSTATES_SUPPORT_OK;
-
-    if (!strcmp(state_ns->name, "composing")) {
-      jep85->last_state_rcvd = ROSTER_EVENT_COMPOSING;
-    } else if (!strcmp(state_ns->name, "active")) {
-      jep85->last_state_rcvd = ROSTER_EVENT_ACTIVE;
-    } else if (!strcmp(state_ns->name, "paused")) {
-      jep85->last_state_rcvd = ROSTER_EVENT_PAUSED;
-    } else if (!strcmp(state_ns->name, "inactive")) {
-      jep85->last_state_rcvd = ROSTER_EVENT_INACTIVE;
-    } else if (!strcmp(state_ns->name, "gone")) {
-      jep85->last_state_rcvd = ROSTER_EVENT_GONE;
-    }
-    events = jep85->last_state_rcvd;
-  } else {              /* JEP-0022 */
-#ifdef JEP0022
-    const char *msgid;
-    jep22->support = CHATSTATES_SUPPORT_OK;
-    jep22->last_state_rcvd = ROSTER_EVENT_NONE;
-
-    msgid = lm_message_node_get_attribute(node, "id");
-
-    if (lm_message_node_get_child(state_ns, "composing")) {
-      // Clear composing if the message contains a body
-      if (body)
-        events &= ~ROSTER_EVENT_COMPOSING;
-      else
-        events |= ROSTER_EVENT_COMPOSING;
-      jep22->last_state_rcvd |= ROSTER_EVENT_COMPOSING;
-
-    } else {
-      events &= ~ROSTER_EVENT_COMPOSING;
-    }
-
-    // Cache the message id
-    g_free(jep22->last_msgid_rcvd);
-    if (msgid)
-      jep22->last_msgid_rcvd = g_strdup(msgid);
-    else
-      jep22->last_msgid_rcvd = NULL;
-
-    if (lm_message_node_get_child(state_ns, "delivered")) {
-      jep22->last_state_rcvd |= ROSTER_EVENT_DELIVERED;
-
-      // Do we have to send back an ACK?
-      if (body)
-        xmpp_send_jep22_event(from, ROSTER_EVENT_DELIVERED);
-    }
-#endif
-  }
-
-  buddy_resource_setevents(sl_buddy->data, rname, events);
-
-  update_roster = TRUE;
-#endif
-}
-
-static void gotmessage(LmMessageSubType type, const char *from,
-                       const char *body, const char *enc, const char *subject,
-                       time_t timestamp, LmMessageNode *node_signed)
-{
-  char *bjid;
-  const char *rname, *s;
-  char *decrypted_pgp = NULL;
-  char *decrypted_otr = NULL;
-  int otr_msg = 0, free_msg = 0;
-
-  bjid = jidtodisp(from);
-
-  rname = strchr(from, JID_RESOURCE_SEPARATOR);
-  if (rname) rname++;
-
-#ifdef HAVE_GPGME
-  if (enc && gpg_enabled()) {
-    decrypted_pgp = gpg_decrypt(enc);
-    if (decrypted_pgp) {
-      body = decrypted_pgp;
-    }
-  }
-  // Check signature of an unencrypted message
-  if (node_signed && gpg_enabled())
-    check_signature(bjid, rname, node_signed, decrypted_pgp);
-#endif
-
-#ifdef HAVE_LIBOTR
-  if (otr_enabled()) {
-    decrypted_otr = (char*)body;
-    otr_msg = otr_receive(&decrypted_otr, bjid, &free_msg);
-    if (!decrypted_otr) {
-      goto gotmessage_return;
-    }
-    body = decrypted_otr;
-  }
-#endif
-
-  // Check for unexpected groupchat messages
-  // If we receive a groupchat message from a room we're not a member of,
-  // this is probably a server issue and the best we can do is to send
-  // a type unavailable.
-  if (type == LM_MESSAGE_SUB_TYPE_GROUPCHAT && !roster_getnickname(bjid)) {
-    // It shouldn't happen, probably a server issue
-    GSList *room_elt;
-    char *mbuf;
-
-    mbuf = g_strdup_printf("Unexpected groupchat packet!");
-    scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
-    scr_WriteIncomingMessage(bjid, mbuf, 0, HBB_PREFIX_INFO, 0);
-    g_free(mbuf);
-
-    // Send back an unavailable packet
-    xmpp_setstatus(offline, bjid, "", TRUE);
-
-    // MUC
-    // Make sure this is a room (it can be a conversion user->room)
-    room_elt = roster_find(bjid, jidsearch, 0);
-    if (!room_elt) {
-      roster_add_user(bjid, NULL, NULL, ROSTER_TYPE_ROOM, sub_none, -1);
-    } else {
-      buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
-    }
-
-    buddylist_build();
-    scr_DrawRoster();
-    goto gotmessage_return;
-  }
-
-  // We don't call the message_in hook if 'block_unsubscribed' is true and
-  // this is a regular message from an unsubscribed user.
-  // System messages (from our server) are allowed.
-  if ((!settings_opt_get_int("block_unsubscribed") ||
-       (roster_getsubscription(bjid) & sub_from) ||
-       (type == LM_MESSAGE_SUB_TYPE_CHAT)) ||
-      ((s = settings_opt_get("server")) != NULL && !strcasecmp(bjid, s))) {
-    gchar *fullbody = NULL;
-    guint encrypted;
-
-    if (decrypted_pgp)
-      encrypted = ENCRYPTED_PGP;
-    else if (otr_msg)
-      encrypted = ENCRYPTED_OTR;
-    else
-      encrypted = 0;
-
-    if (subject) {
-      if (body)
-        fullbody = g_strdup_printf("[%s]\n%s", subject, body);
-      else
-        fullbody = g_strdup_printf("[%s]\n", subject);
-      body = fullbody;
-    }
-    hk_message_in(bjid, rname, timestamp, body, type, encrypted);
-    g_free(fullbody);
-  } else {
-    scr_LogPrint(LPRINT_LOGNORM, "Blocked a message from <%s>", bjid);
-  }
-
-gotmessage_return:
-  // Clean up and exit
-  g_free(bjid);
-  g_free(decrypted_pgp);
-  if (free_msg)
-    g_free(decrypted_otr);
-}
-
-
-static LmHandlerResult handle_messages(LmMessageHandler *handler,
-                                       LmConnection *connection,
-                                       LmMessage *m, gpointer user_data)
-{
-  const char *p, *from=lm_message_get_from(m);
-  char *r, *s;
-  LmMessageNode *x;
-  const char *body = NULL;
-  const char *enc = NULL;
-  const char *subject = NULL;
-  time_t timestamp = 0L;
-  LmMessageSubType mstype;
-
-  mstype = lm_message_get_sub_type(m);
-
-  body = lm_message_node_get_child_value(m->node, "body");
-
-  x = lm_message_node_find_xmlns(m->node, NS_ENCRYPTED);
-  if (x && (p = lm_message_node_get_value(x)) != NULL)
-    enc = p;
-
-  p = lm_message_node_get_child_value(m->node, "subject");
-  if (p != NULL) {
-    if (mstype != LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
-      // Chat message
-      subject = p;
-    } else {                                      // Room topic
-      GSList *roombuddy;
-      gchar *mbuf;
-      const gchar *subj = p;
-      // Get the room (s) and the nickname (r)
-      s = g_strdup(lm_message_get_from(m));
-      r = strchr(s, JID_RESOURCE_SEPARATOR);
-      if (r) *r++ = 0;
-      else   r = s;
-      // Set the new topic
-      roombuddy = roster_find(s, jidsearch, 0);
-      if (roombuddy)
-        buddy_settopic(roombuddy->data, subj);
-      // Display inside the room window
-      if (r == s) {
-        // No specific resource (this is certainly history)
-        mbuf = g_strdup_printf("The topic has been set to: %s", subj);
-      } else {
-        mbuf = g_strdup_printf("%s has set the topic to: %s", r, subj);
-      }
-      scr_WriteIncomingMessage(s, mbuf, 0,
-                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
-      if (settings_opt_get_int("log_muc_conf"))
-        hlog_write_message(s, 0, -1, mbuf);
-      g_free(s);
-      g_free(mbuf);
-      // The topic is displayed in the chat status line, so refresh now.
-      scr_UpdateChatStatus(TRUE);
-    }
-  }
-
-  // Timestamp?
-  timestamp = lm_message_node_get_timestamp(m->node);
-
-  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
-    x = lm_message_node_get_child(m->node, "error");
-    display_server_error(x);
-#if defined JEP0022 || defined JEP0085
-    // If the JEP85/22 support is probed, set it back to unknown so that
-    // we probe it again.
-    chatstates_reset_probed(from);
-#endif
-  } else {
-    handle_state_events(from, m->node);
-  }
-  if (from && (body || subject))
-    gotmessage(mstype, from, body, enc, subject, timestamp,
-               lm_message_node_find_xmlns(m->node, NS_SIGNED));
-  //report received message if message receipt was requested
-  if (lm_message_node_get_child(m->node, "request")) {
-    LmMessage *rcvd = lm_message_new(from, LM_MESSAGE_TYPE_MESSAGE);
-    lm_message_node_set_attribute(rcvd->node, "id", lm_message_get_id(m));
-    lm_message_node_set_attribute
-            (lm_message_node_add_child(rcvd->node, "received", NULL),
-             "xmlns", NS_RECEIPTS);
-    lm_connection_send(connection, rcvd, NULL);
-    lm_message_unref(rcvd);
-  }
-
-  if (from) {
-    x = lm_message_node_find_xmlns(m->node,
-                                   "http://jabber.org/protocol/muc#user");
-    if (x && !strcmp(x->name, "x"))
-      got_muc_message(from, x);
-  }
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static LmHandlerResult cb_caps(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer user_data)
-{
-  char *ver = user_data;
-  LmMessageSubType mstype = lm_message_get_sub_type(m);
-
-  caps_add(ver);
-  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
-    display_server_error(lm_message_node_get_child(m->node, "error"));
-  } else if (mstype == LM_MESSAGE_SUB_TYPE_RESULT) {
-    LmMessageNode *info;
-    LmMessageNode *query = lm_message_node_get_child(m->node, "query");
-
-    info = lm_message_node_get_child(query, "identity");
-    if (info)
-      caps_set_identity(ver, lm_message_node_get_attribute(info, "category"),
-                        lm_message_node_get_attribute(info, "name"),
-                        lm_message_node_get_attribute(info, "type"));
-    info = lm_message_node_get_child(query, "feature");
-    while (info) {
-      if (!g_strcmp0(info->name, "feature"))
-        caps_add_feature(ver, lm_message_node_get_attribute(info, "var"));
-      info = info->next;
-    }
-  }
-  g_free(ver);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static LmHandlerResult handle_presence(LmMessageHandler *handler,
-                                       LmConnection *connection,
-                                       LmMessage *m, gpointer user_data)
-{
-  char *r;
-  const char *from, *rname, *p=NULL, *ustmsg=NULL;
-  enum imstatus ust;
-  char bpprio;
-  time_t timestamp = 0L;
-  LmMessageNode *muc_packet, *caps;
-  LmMessageSubType mstype;
-
-  // Check for MUC presence packet
-  muc_packet = lm_message_node_find_xmlns
-          (m->node, "http://jabber.org/protocol/muc#user");
-
-  from = lm_message_get_from(m);
-
-  rname = strchr(from, JID_RESOURCE_SEPARATOR);
-  if (rname) rname++;
-
-  if (settings_opt_get_int("ignore_self_presence")) {
-    const char *self_fjid = lm_connection_get_jid(connection);
-    if (self_fjid && !strcasecmp(self_fjid, from)) {
-      return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Ignoring self presence
-    }
-  }
-
-  r = jidtodisp(from);
-  mstype = lm_message_get_sub_type(m);
-
-  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
-    LmMessageNode *x;
-    scr_LogPrint(LPRINT_LOGNORM, "Error presence packet from <%s>", r);
-    x = lm_message_node_find_child(m->node, "error");
-    display_server_error(x);
-    // Let's check it isn't a nickname conflict.
-    // XXX Note: We should handle the <conflict/> string condition.
-    if ((p = lm_message_node_get_attribute(x, "code")) != NULL) {
-      if (atoi(p) == 409) {
-        // 409 = conflict (nickname is in use or registered by another user)
-        // If we are not inside this room, we should reset the nickname
-        GSList *room_elt = roster_find(r, jidsearch, 0);
-        if (room_elt && !buddy_getinsideroom(room_elt->data))
-          buddy_setnickname(room_elt->data, NULL);
-      }
-    }
-
-    g_free(r);
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-
-  p = lm_message_node_get_child_value(m->node, "priority");
-  if (p && *p) bpprio = (gchar)atoi(p);
-  else         bpprio = 0;
-
-  ust = available;
-
-  p = lm_message_node_get_child_value(m->node, "show");
-  if (p) {
-    if (!strcmp(p, "away"))      ust = away;
-    else if (!strcmp(p, "dnd"))  ust = dontdisturb;
-    else if (!strcmp(p, "xa"))   ust = notavail;
-    else if (!strcmp(p, "chat")) ust = freeforchat;
-  }
-
-  if (mstype == LM_MESSAGE_SUB_TYPE_UNAVAILABLE)
-    ust = offline;
-
-  ustmsg = lm_message_node_get_child_value(m->node, "status");
-
-  // Timestamp?
-  timestamp = lm_message_node_get_timestamp(m->node);
-
-  if (muc_packet) {
-    // This is a MUC presence message
-    handle_muc_presence(from, muc_packet, r, rname,
-                        ust, ustmsg, timestamp, bpprio);
-  } else {
-    // Not a MUC message, so this is a regular buddy...
-    // Call hk_statuschange() if status has changed or if the
-    // status message is different
-    const char *msg;
-    msg = roster_getstatusmsg(r, rname);
-    if ((ust != roster_getstatus(r, rname)) ||
-        (!ustmsg && msg && msg[0]) || (ustmsg && (!msg || strcmp(ustmsg, msg))))
-      hk_statuschange(r, rname, bpprio, timestamp, ust, ustmsg);
-    // Presence signature processing
-    if (!ustmsg)
-      ustmsg = ""; // Some clients omit the <status/> element :-(
-    check_signature(r, rname, lm_message_node_find_xmlns(m->node, NS_SIGNED),
-                    ustmsg);
-  }
-
-  // XEP-0115 Entity Capabilities
-  caps = lm_message_node_find_xmlns(m->node, NS_CAPS);
-  if (caps && ust != offline) {
-    const char *ver = lm_message_node_get_attribute(caps, "ver");
-    GSList *sl_buddy = NULL;
-    if (rname)
-      sl_buddy = roster_find(r, jidsearch, ROSTER_TYPE_USER);
-    // Only cache the caps if the user is on the roster
-    if (sl_buddy && buddy_getonserverflag(sl_buddy->data)) {
-      buddy_resource_setcaps(sl_buddy->data, rname, ver);
-
-      if (!caps_has_hash(ver)) {
-        char *node;
-        LmMessageHandler *handler;
-        LmMessage *iq = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ,
-                                                     LM_MESSAGE_SUB_TYPE_GET);
-        node = g_strdup_printf("%s#%s",
-                               lm_message_node_get_attribute(caps, "node"),
-                               ver);
-        lm_message_node_set_attributes
-                (lm_message_node_add_child(iq->node, "query", NULL),
-                 "xmlns", NS_DISCO_INFO,
-                 "node", node,
-                 NULL);
-        g_free(node);
-        handler = lm_message_handler_new(cb_caps, g_strdup(ver), NULL);
-        lm_connection_send_with_reply(connection, iq, handler, NULL);
-        lm_message_unref(iq);
-        lm_message_handler_unref(handler);
-      }
-    }
-  }
-
-  g_free(r);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-
-static LmHandlerResult handle_iq(LmMessageHandler *handler,
-                                 LmConnection *connection,
-                                 LmMessage *m, gpointer user_data)
-{
-  int i;
-  guint dbgflg;
-  const char *xmlns = NULL;
-  LmMessageNode *x;
-  LmMessageSubType mstype = lm_message_get_sub_type(m);
-
-  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
-    display_server_error(lm_message_node_get_child(m->node, "error"));
-    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-  }
-
-  for (x = m->node->children; x; x=x->next) {
-    xmlns = lm_message_node_get_attribute(x, "xmlns");
-    if (xmlns)
-      for (i=0; iq_handlers[i].xmlns; ++i)
-        if (!strcmp(iq_handlers[i].xmlns, xmlns))
-          return iq_handlers[i].handler(NULL, connection, m, user_data);
-    xmlns = NULL;
-  }
-
-  if ((mstype == LM_MESSAGE_SUB_TYPE_SET) ||
-      (mstype == LM_MESSAGE_SUB_TYPE_GET))
-    send_iq_error(connection, m, XMPP_ERROR_NOT_IMPLEMENTED);
-
-  if (mstype == LM_MESSAGE_SUB_TYPE_RESULT)
-    dbgflg = LPRINT_DEBUG;
-  else
-    dbgflg = LPRINT_NORMAL|LPRINT_DEBUG;
-
-  scr_LogPrint(dbgflg, "Unhandled IQ: %s", lm_message_node_to_string(m->node));
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static LmHandlerResult handle_s10n(LmMessageHandler *handler,
-                                   LmConnection *connection,
-                                   LmMessage *m, gpointer user_data)
-{
-  char *r;
-  char *buf;
-  int newbuddy;
-  const char *from = lm_message_get_from(m);
-  LmMessageSubType mstype;
-
-  r = jidtodisp(from);
-
-  newbuddy = !roster_find(r, jidsearch, 0);
-  mstype = lm_message_get_sub_type(m);
-
-  if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBE) {
-    /* The sender wishes to subscribe to our presence */
-    const char *msg;
-    eviqs *evn;
-
-    msg = lm_message_node_get_child_value(m->node, "status");
-
-    buf = g_strdup_printf("<%s> wants to subscribe to your presence updates",
-                          from);
-    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
-    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-    g_free(buf);
-
-    if (msg) {
-      buf = g_strdup_printf("<%s> said: %s", from, msg);
-      scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
-      replace_nl_with_dots(buf);
-      scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-      g_free(buf);
-    }
-
-    // Create a new event item
-    evn = evs_new(EVS_TYPE_SUBSCRIPTION, EVS_MAX_TIMEOUT);
-    if (evn) {
-      evn->callback = &evscallback_subscription;
-      evn->data = g_strdup(r);
-      evn->desc = g_strdup_printf("<%s> wants to subscribe to your "
-                                  "presence updates", r);
-      buf = g_strdup_printf("Please use /event %s accept|reject", evn->id);
-    } else {
-      buf = g_strdup_printf("Unable to create a new event!");
-    }
-    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
-    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-    g_free(buf);
-  } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE) {
-    /* The sender is unsubscribing from our presence */
-    xmpp_send_s10n(from, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);
-    buf = g_strdup_printf("<%s> is unsubscribing from your "
-                          "presence updates", from);
-    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
-    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-    g_free(buf);
-  } else if (mstype == LM_MESSAGE_SUB_TYPE_SUBSCRIBED) {
-    /* The sender has allowed us to receive their presence */
-    buf = g_strdup_printf("<%s> has allowed you to receive their "
-                          "presence updates", from);
-    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
-    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-    g_free(buf);
-  } else if (mstype == LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED) {
-    /* The subscription request has been denied or a previously-granted
-       subscription has been cancelled */
-    roster_unsubscribed(from);
-    update_roster = TRUE;
-    buf = g_strdup_printf("<%s> has cancelled your subscription to "
-                          "their presence updates", from);
-    scr_WriteIncomingMessage(r, buf, 0, HBB_PREFIX_INFO, 0);
-    scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-    g_free(buf);
-  } else {
-    g_free(r);
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-
-  if (newbuddy)
-    update_roster = TRUE;
-  g_free(r);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-//TODO: Use the enum of loudmouth, when it's included in the header...
-typedef enum {
-  LM_LOG_LEVEL_VERBOSE = 1 << (G_LOG_LEVEL_USER_SHIFT),
-  LM_LOG_LEVEL_NET     = 1 << (G_LOG_LEVEL_USER_SHIFT + 1),
-  LM_LOG_LEVEL_PARSER  = 1 << (G_LOG_LEVEL_USER_SHIFT + 2),
-  LM_LOG_LEVEL_SSL     = 1 << (G_LOG_LEVEL_USER_SHIFT + 3),
-  LM_LOG_LEVEL_SASL    = 1 << (G_LOG_LEVEL_USER_SHIFT + 4),
-  LM_LOG_LEVEL_ALL     = (LM_LOG_LEVEL_NET |
-        LM_LOG_LEVEL_VERBOSE |
-        LM_LOG_LEVEL_PARSER |
-        LM_LOG_LEVEL_SSL |
-        LM_LOG_LEVEL_SASL)
-} LmLogLevelFlags;
-
-static void lm_debug_handler (const gchar    *log_domain,
-                              GLogLevelFlags  log_level,
-                              const gchar    *message,
-                              gpointer        user_data)
-{
-  if (message && *message) {
-    char *msg;
-    int mcabber_loglevel = settings_opt_get_int("tracelog_level");
-
-    if (mcabber_loglevel < 2)
-      return;
-
-    if (message[0] == '\n')
-      msg = g_strdup(&message[1]);
-    else
-      msg = g_strdup(message);
-
-    if (msg[strlen(msg)-1] == '\n')
-      msg[strlen(msg)-1] = '\0';
-
-    if (log_level & LM_LOG_LEVEL_VERBOSE) {
-      scr_LogPrint(LPRINT_DEBUG, "LM-VERBOSE: %s", msg);
-    }
-    if (log_level & LM_LOG_LEVEL_NET) {
-      if (mcabber_loglevel > 2)
-        scr_LogPrint(LPRINT_DEBUG, "LM-NET: %s", msg);
-    } else if (log_level & LM_LOG_LEVEL_PARSER) {
-      if (mcabber_loglevel > 3)
-        scr_LogPrint(LPRINT_DEBUG, "LM-PARSER: %s", msg);
-    } else if (log_level & LM_LOG_LEVEL_SASL) {
-      scr_LogPrint(LPRINT_DEBUG, "LM-SASL: %s", msg);
-    } else if (log_level & LM_LOG_LEVEL_SSL) {
-      scr_LogPrint(LPRINT_DEBUG, "LM-SSL: %s", msg);
-    }
-    g_free(msg);
-  }
-}
-
-
-void xmpp_connect(void)
-{
-  const char *userjid, *password, *resource, *servername, *ssl_fpr;
-  char *dynresource = NULL;
-  char fpr[16];
-  const char *proxy_host;
-  const char *resource_prefix = PACKAGE_NAME;
-  char *fjid;
-  int ssl, tls;
-  LmSSL *lssl;
-  unsigned int port;
-  unsigned int ping;
-  LmMessageHandler *handler;
-  GError *error = NULL;
-
-  if (lconnection && lm_connection_is_open(lconnection))
-    xmpp_disconnect();
-
-  servername = settings_opt_get("server");
-  userjid    = settings_opt_get("jid");
-  password   = settings_opt_get("password");
-  resource   = settings_opt_get("resource");
-  proxy_host = settings_opt_get("proxy_host");
-  ssl_fpr    = settings_opt_get("ssl_fingerprint");
-
-  if (!userjid) {
-    scr_LogPrint(LPRINT_LOGNORM, "Your JID has not been specified!");
-    return;
-  }
-  if (!password) {
-    scr_LogPrint(LPRINT_LOGNORM, "Your password has not been specified!");
-    return;
-  }
-
-  lconnection = lm_connection_new_with_context(NULL, main_context);
-
-  g_log_set_handler("LM", LM_LOG_LEVEL_ALL, lm_debug_handler, NULL);
-
-  ping = 40;
-  if (settings_opt_get("pinginterval"))
-    ping = (unsigned int) settings_opt_get_int("pinginterval");
-  lm_connection_set_keep_alive_rate(lconnection, ping);
-  scr_LogPrint(LPRINT_DEBUG, "Ping interval established: %d secs", ping);
-
-  lm_connection_set_disconnect_function(lconnection, connection_close_cb,
-                                        NULL, NULL);
-
-  handler = lm_message_handler_new(handle_messages, NULL, NULL);
-  lm_connection_register_message_handler(lconnection, handler,
-                                         LM_MESSAGE_TYPE_MESSAGE,
-                                         LM_HANDLER_PRIORITY_NORMAL);
-  lm_message_handler_unref(handler);
-
-  handler = lm_message_handler_new(handle_iq, NULL, NULL);
-  lm_connection_register_message_handler(lconnection, handler,
-                                         LM_MESSAGE_TYPE_IQ,
-                                         LM_HANDLER_PRIORITY_NORMAL);
-  lm_message_handler_unref(handler);
-
-  handler = lm_message_handler_new(handle_presence, NULL, NULL);
-  lm_connection_register_message_handler(lconnection, handler,
-                                         LM_MESSAGE_TYPE_PRESENCE,
-                                         LM_HANDLER_PRIORITY_LAST);
-  lm_message_handler_unref(handler);
-
-  handler = lm_message_handler_new(handle_s10n, NULL, NULL);
-  lm_connection_register_message_handler(lconnection, handler,
-                                         LM_MESSAGE_TYPE_PRESENCE,
-                                         LM_HANDLER_PRIORITY_NORMAL);
-  lm_message_handler_unref(handler);
-
-  /* Connect to server */
-  scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Connecting to server: %s",
-               servername ? servername : "...");
-  if (!resource)
-    resource = resource_prefix;
-
-  if (!settings_opt_get("disable_random_resource")) {
-#if HAVE_ARC4RANDOM
-    dynresource = g_strdup_printf("%s.%08x", resource, arc4random());
-#else
-    unsigned int tab[2];
-    srand(time(NULL));
-    tab[0] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0)));
-    tab[1] = (unsigned int) (0xffff * (rand() / (RAND_MAX + 1.0)));
-    dynresource = g_strdup_printf("%s.%04x%04x", resource, tab[0], tab[1]);
-#endif
-    resource = dynresource;
-  }
-
-  port = (unsigned int) settings_opt_get_int("port");
-
-  if (port)
-    scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using port %d", port);
-  scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " resource %s", resource);
-
-  if (proxy_host) {
-    int proxy_port = settings_opt_get_int("proxy_port");
-    if (proxy_port <= 0 || proxy_port > 65535) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "Invalid proxy port: %d",
-                   proxy_port);
-    } else {
-      const char *proxy_user, *proxy_pass;
-      LmProxy *lproxy;
-      proxy_user = settings_opt_get("proxy_user");
-      proxy_pass = settings_opt_get("proxy_pass");
-      // Proxy initialization
-      lproxy = lm_proxy_new_with_server(LM_PROXY_TYPE_HTTP,
-                                        proxy_host, proxy_port);
-      lm_proxy_set_username(lproxy, proxy_user);
-      lm_proxy_set_password(lproxy, proxy_pass);
-      lm_connection_set_proxy(lconnection, lproxy);
-      lm_proxy_unref(lproxy);
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, " using proxy %s:%d",
-                   proxy_host, proxy_port);
-    }
-  }
-
-  fjid = compose_jid(userjid, servername, resource);
-  lm_connection_set_jid(lconnection, fjid);
-  if (servername)
-    lm_connection_set_server(lconnection, servername);
-#if defined(HAVE_LIBOTR)
-  otr_init(fjid);
-#endif
-  g_free(fjid);
-  g_free(dynresource);
-
-  ssl = settings_opt_get_int("ssl");
-  tls = settings_opt_get_int("tls");
-
-  if (!lm_ssl_is_supported()) {
-    if (ssl || tls) {
-      scr_LogPrint(LPRINT_LOGNORM, "** Error: SSL is NOT available, "
-                   "please recompile loudmouth with SSL enabled.");
-      return;
-    }
-  }
-
-  if (ssl && tls) {
-    scr_LogPrint(LPRINT_LOGNORM, "You can only set ssl or tls, not both.");
-    return;
-  }
-
-  if (!port)
-    port = (ssl ? LM_CONNECTION_DEFAULT_PORT_SSL : LM_CONNECTION_DEFAULT_PORT);
-  lm_connection_set_port(lconnection, port);
-
-  if (ssl_fpr && (!hex_to_fingerprint(ssl_fpr, fpr))) {
-    scr_LogPrint(LPRINT_LOGNORM, "** Plese set the fingerprint in the format "
-                 "97:5C:00:3F:1D:77:45:25:E2:C5:70:EC:83:C8:87:EE");
-    return;
-  }
-
-  lssl = lm_ssl_new((ssl_fpr ? fpr : NULL), ssl_cb, NULL, NULL);
-  if (lssl) {
-    lm_ssl_use_starttls(lssl, !ssl, tls);
-    lm_connection_set_ssl(lconnection, lssl);
-    lm_ssl_unref(lssl);
-  } else if (ssl || tls) {
-    scr_LogPrint(LPRINT_LOGNORM, "** Error: Couldn't create SSL struct.");
-    return;
-  }
-
-  if (!lm_connection_open(lconnection, connection_open_cb,
-                          NULL, FALSE, &error)) {
-    _try_to_reconnect();
-    scr_LogPrint(LPRINT_LOGNORM, "Failed to open: %s\n", error->message);
-    g_error_free (error);
-  }
-}
-
-//  insert_entity_capabilities(presence_stanza)
-// Entity Capabilities (XEP-0115)
-static void insert_entity_capabilities(LmMessageNode *x, enum imstatus status)
-{
-  LmMessageNode *y;
-  const char *ver = entity_version(status);
-
-  y = lm_message_node_add_child(x, "c", NULL);
-  lm_message_node_set_attribute(y, "xmlns", NS_CAPS);
-  lm_message_node_set_attribute(y, "hash", "sha-1");
-  lm_message_node_set_attribute(y, "node", MCABBER_CAPS_NODE);
-  lm_message_node_set_attribute(y, "ver", ver);
-}
-
-void xmpp_disconnect(void)
-{
-  if (!lconnection || !lm_connection_is_authenticated(lconnection))
-    return;
-
-  // Launch pre-disconnect internal hook
-  hook_execute_internal("hook-pre-disconnect");
-  // Announce it to  everyone else
-  xmpp_setstatus(offline, NULL, "", FALSE);
-  lm_connection_close(lconnection, NULL);
-}
-
-void xmpp_setstatus(enum imstatus st, const char *recipient, const char *msg,
-                  int do_not_sign)
-{
-  LmMessage *m;
-
-  if (msg) {
-    // The status message has been specified.  We'll use it, unless it is
-    // "-" which is a special case (option meaning "no status message").
-    if (!strcmp(msg, "-"))
-      msg = "";
-  } else {
-    // No status message specified; we'll use:
-    // a) the default status message (if provided by the user);
-    // b) the current status message;
-    // c) no status message (i.e. an empty one).
-    msg = settings_get_status_msg(st);
-    if (!msg) {
-      if (mystatusmsg)
-        msg = mystatusmsg;
-      else
-        msg = "";
-    }
-  }
-
-  // Only send the packet if we're online.
-  // (But we want to update internal status even when disconnected,
-  // in order to avoid some problems during network failures)
-  if (lm_connection_is_authenticated(lconnection)) {
-    const char *s_msg = (st != invisible ? msg : NULL);
-    m = lm_message_new_presence(st, recipient, s_msg);
-    insert_entity_capabilities(m->node, st); // Entity Capabilities (XEP-0115)
-#ifdef HAVE_GPGME
-    if (!do_not_sign && gpg_enabled()) {
-      char *signature;
-      signature = gpg_sign(s_msg ? s_msg : "");
-      if (signature) {
-        LmMessageNode *y;
-        y = lm_message_node_add_child(m->node, "x", signature);
-        lm_message_node_set_attribute(y, "xmlns", NS_SIGNED);
-        g_free(signature);
-      }
-    }
-#endif
-    lm_connection_send(lconnection, m, NULL);
-    lm_message_unref(m);
-  }
-
-  // If we didn't change our _global_ status, we are done
-  if (recipient) return;
-
-  if (lm_connection_is_authenticated(lconnection)) {
-    // Send presence to chatrooms
-    if (st != invisible) {
-      struct T_presence room_presence;
-      room_presence.st = st;
-      room_presence.msg = msg;
-      foreach_buddy(ROSTER_TYPE_ROOM, &roompresence, &room_presence);
-    }
-
-    // We'll have to update the roster if we switch to/from offline because
-    // we don't know the presences of buddies when offline...
-    if (mystatus == offline || st == offline)
-      update_roster = TRUE;
-
-    hk_mystatuschange(0, mystatus, st, (st != invisible ? msg : ""));
-    mystatus = st;
-  }
-
-  if (st)
-    mywantedstatus = st;
-
-  if (msg != mystatusmsg) {
-    g_free(mystatusmsg);
-    if (*msg)
-      mystatusmsg = g_strdup(msg);
-    else
-      mystatusmsg = NULL;
-  }
-
-  if (!Autoaway)
-    update_last_use();
-
-  // Update status line
-  scr_UpdateMainStatus(TRUE);
-}
-
-
-enum imstatus xmpp_getstatus(void)
-{
-  return mystatus;
-}
-
-const char *xmpp_getstatusmsg(void)
-{
-  return mystatusmsg;
-}
-
-//  xmpp_setprevstatus()
-// Set previous status.  This wrapper function is used after a disconnection.
-void xmpp_setprevstatus(void)
-{
-  xmpp_setstatus(mywantedstatus, NULL, mystatusmsg, FALSE);
-}
-
-//  send_storage(store)
-// Send the node "store" to update the server.
-// Note: the sender should check we're online.
-void send_storage(LmMessageNode *store)
-{
-  LmMessage *iq;
-  LmMessageNode *query;
-
-  if (!rosternotes) return;
-
-  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_SET);
-  query = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE);
-  lm_message_node_insert_childnode(query, store);
-
-  lm_connection_send(lconnection, iq, NULL);
-  lm_message_unref(iq);
-}
-
-
-//  xmpp_is_bookmarked(roomjid)
-// Return TRUE if there's a bookmark for the given jid.
-guint xmpp_is_bookmarked(const char *bjid)
-{
-  LmMessageNode *x;
-
-  if (!bookmarks)
-    return FALSE;
-
-  // Walk through the storage bookmark tags
-  for (x = bookmarks->children ; x; x = x->next) {
-    // If the node is a conference item, check the jid.
-    if (x->name && !strcmp(x->name, "conference")) {
-      const char *fjid = lm_message_node_get_attribute(x, "jid");
-      if (fjid && !strcasecmp(bjid, fjid))
-        return TRUE;
-    }
-  }
-  return FALSE;
-}
-
-//  xmpp_get_bookmark_nick(roomjid)
-// Return the room nickname if it is present in a bookmark.
-const char *xmpp_get_bookmark_nick(const char *bjid)
-{
-  LmMessageNode *x;
-
-  if (!bookmarks || !bjid)
-    return NULL;
-
-  // Walk through the storage bookmark tags
-  for (x = bookmarks->children ; x; x = x->next) {
-    // If the node is a conference item, check the jid.
-    if (x->name && !strcmp(x->name, "conference")) {
-      const char *fjid = lm_message_node_get_attribute(x, "jid");
-      if (fjid && !strcasecmp(bjid, fjid))
-        return lm_message_node_get_child_value(x, "nick");
-    }
-  }
-  return NULL;
-}
-
-
-//  xmpp_get_all_storage_bookmarks()
-// Return a GSList with all storage bookmarks.
-// The caller should g_free the list (not the MUC jids).
-GSList *xmpp_get_all_storage_bookmarks(void)
-{
-  LmMessageNode *x;
-  GSList *sl_bookmarks = NULL;
-
-  // If we have no bookmarks, probably the server doesn't support them.
-  if (!bookmarks)
-    return NULL;
-
-  // Walk through the storage bookmark tags
-  for (x = bookmarks->children ; x; x = x->next) {
-    // If the node is a conference item, let's add the note to our list.
-    if (x->name && !strcmp(x->name, "conference")) {
-      struct bookmark *bm_elt;
-      const char *autojoin, *name, *nick;
-      const char *fjid = lm_message_node_get_attribute(x, "jid");
-      if (!fjid)
-        continue;
-      bm_elt = g_new0(struct bookmark, 1);
-      bm_elt->roomjid = g_strdup(fjid);
-      autojoin = lm_message_node_get_attribute(x, "autojoin");
-      nick = lm_message_node_get_attribute(x, "nick");
-      name = lm_message_node_get_attribute(x, "name");
-      if (autojoin && !strcmp(autojoin, "1"))
-        bm_elt->autojoin = 1;
-      if (nick)
-        bm_elt->nick = g_strdup(nick);
-      if (name)
-        bm_elt->name = g_strdup(name);
-      sl_bookmarks = g_slist_append(sl_bookmarks, bm_elt);
-    }
-  }
-  return sl_bookmarks;
-}
-
-//  xmpp_set_storage_bookmark(roomid, name, nick, passwd, autojoin,
-//                          printstatus, autowhois)
-// Update the private storage bookmarks: add a conference room.
-// If name is nil, we remove the bookmark.
-void xmpp_set_storage_bookmark(const char *roomid, const char *name,
-                               const char *nick, const char *passwd,
-                               int autojoin, enum room_printstatus pstatus,
-                               enum room_autowhois awhois)
-{
-  LmMessageNode *x;
-  bool changed = FALSE;
-
-  if (!roomid)
-    return;
-
-  // If we have no bookmarks, probably the server doesn't support them.
-  if (!bookmarks) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Sorry, your server doesn't seem to support private storage.");
-    return;
-  }
-
-  // Walk through the storage tags
-  for (x = bookmarks->children ; x; x = x->next) {
-    // If the current node is a conference item, see if we have to replace it.
-    if (x->name && !strcmp(x->name, "conference")) {
-      const char *fjid = lm_message_node_get_attribute(x, "jid");
-      if (!fjid)
-        continue;
-      if (!strcmp(fjid, roomid)) {
-        // We've found a bookmark for this room.  Let's hide it and we'll
-        // create a new one.
-        lm_message_node_hide(x);
-        changed = TRUE;
-        if (!name)
-          scr_LogPrint(LPRINT_LOGNORM, "Deleting bookmark...");
-      }
-    }
-  }
-
-  // Let's create a node/bookmark for this roomid, if the name is not NULL.
-  if (name) {
-    x = lm_message_node_add_child(bookmarks, "conference", NULL);
-    lm_message_node_set_attributes(x,
-                                   "jid", roomid,
-                                   "name", name,
-                                   "autojoin", autojoin ? "1" : "0",
-                                   NULL);
-    if (nick)
-      lm_message_node_add_child(x, "nick", nick);
-    if (passwd)
-      lm_message_node_add_child(x, "password", passwd);
-    if (pstatus)
-      lm_message_node_add_child(x, "print_status", strprintstatus[pstatus]);
-    if (awhois)
-      lm_message_node_set_attributes(x, "autowhois",
-                                     (awhois == autowhois_on) ? "1" : "0",
-                                     NULL);
-    changed = TRUE;
-    scr_LogPrint(LPRINT_LOGNORM, "Updating bookmarks...");
-  }
-
-  if (!changed)
-    return;
-
-  if (lm_connection_is_authenticated(lconnection))
-    send_storage(bookmarks);
-  else
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Warning: you're not connected to the server.");
-}
-
-static struct annotation *parse_storage_rosternote(LmMessageNode *notenode)
-{
-  const char *p;
-  struct annotation *note = g_new0(struct annotation, 1);
-  p = lm_message_node_get_attribute(notenode, "cdate");
-  if (p)
-    note->cdate = from_iso8601(p, 1);
-  p = lm_message_node_get_attribute(notenode, "mdate");
-  if (p)
-    note->mdate = from_iso8601(p, 1);
-  note->text = g_strdup(lm_message_node_get_value(notenode));
-  note->jid = g_strdup(lm_message_node_get_attribute(notenode, "jid"));
-  return note;
-}
-
-//  xmpp_get_all_storage_rosternotes()
-// Return a GSList with all storage annotations.
-// The caller should g_free the list and its contents.
-GSList *xmpp_get_all_storage_rosternotes(void)
-{
-  LmMessageNode *x;
-  GSList *sl_notes = NULL;
-
-  // If we have no rosternotes, probably the server doesn't support them.
-  if (!rosternotes)
-    return NULL;
-
-  // Walk through the storage rosternotes tags
-  for (x = rosternotes->children ; x; x = x->next) {
-    struct annotation *note;
-
-    // We want a note item
-    if (!x->name || strcmp(x->name, "note"))
-      continue;
-    // Just in case, check the jid...
-    if (!lm_message_node_get_attribute(x, "jid"))
-      continue;
-    // Ok, let's add the note to our list
-    note = parse_storage_rosternote(x);
-    sl_notes = g_slist_append(sl_notes, note);
-  }
-  return sl_notes;
-}
-
-//  xmpp_get_storage_rosternotes(barejid, silent)
-// Return the annotation associated with this jid.
-// If silent is TRUE, no warning is displayed when rosternotes is disabled
-// The caller should g_free the string and structure after use.
-struct annotation *xmpp_get_storage_rosternotes(const char *barejid, int silent)
-{
-  LmMessageNode *x;
-
-  if (!barejid)
-    return NULL;
-
-  // If we have no rosternotes, probably the server doesn't support them.
-  if (!rosternotes) {
-    if (!silent)
-      scr_LogPrint(LPRINT_NORMAL, "Sorry, "
-                   "your server doesn't seem to support private storage.");
-    return NULL;
-  }
-
-  // Walk through the storage rosternotes tags
-  for (x = rosternotes->children ; x; x = x->next) {
-    const char *fjid;
-    // We want a note item
-    if (!x->name || strcmp(x->name, "note"))
-      continue;
-    // Just in case, check the jid...
-    fjid = lm_message_node_get_attribute(x, "jid");
-    if (fjid && !strcmp(fjid, barejid)) // We've found a note for this contact.
-      return parse_storage_rosternote(x);
-  }
-  return NULL;  // No note found
-}
-
-//  xmpp_set_storage_rosternotes(barejid, note)
-// Update the private storage rosternotes: add/delete a note.
-// If note is nil, we remove the existing note.
-void xmpp_set_storage_rosternotes(const char *barejid, const char *note)
-{
-  LmMessageNode *x;
-  bool changed = FALSE;
-  const char *cdate = NULL;
-
-  if (!barejid)
-    return;
-
-  // If we have no rosternotes, probably the server doesn't support them.
-  if (!rosternotes) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Sorry, your server doesn't seem to support private storage.");
-    return;
-  }
-
-  // Walk through the storage tags
-  for (x = rosternotes->children ; x; x = x->next) {
-    // If the current node is a conference item, see if we have to replace it.
-    if (x->name && !strcmp(x->name, "note")) {
-      const char *fjid = lm_message_node_get_attribute(x, "jid");
-      if (!fjid)
-        continue;
-      if (!strcmp(fjid, barejid)) {
-        // We've found a note for this jid.  Let's hide it and we'll
-        // create a new one.
-        cdate = lm_message_node_get_attribute(x, "cdate");
-        lm_message_node_hide(x);
-        changed = TRUE;
-        break;
-      }
-    }
-  }
-
-  // Let's create a node for this jid, if the note is not NULL.
-  if (note) {
-    char mdate[20];
-    time_t now;
-    time(&now);
-    to_iso8601(mdate, now);
-    if (!cdate)
-      cdate = mdate;
-    x = lm_message_node_add_child(rosternotes, "note", note);
-    lm_message_node_set_attributes(x,
-                                   "jid", barejid,
-                                   "cdate", cdate,
-                                   "mdate", mdate,
-                                   NULL);
-    changed = TRUE;
-  }
-
-  if (!changed)
-    return;
-
-  if (lm_connection_is_authenticated(lconnection))
-    send_storage(rosternotes);
-  else
-    scr_LogPrint(LPRINT_LOGNORM,
-                 "Warning: you're not connected to the server.");
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-#ifndef __XMPP_H__
-#define __XMPP_H__ 1
-
-#include <loudmouth/loudmouth.h>
-#include "roster.h"
-
-enum iqreq_type {
-  iqreq_none,
-  iqreq_version,
-  iqreq_time,
-  iqreq_last,
-  iqreq_vcard
-};
-
-struct annotation {
-  time_t cdate;
-  time_t mdate;
-  gchar *jid;
-  gchar *text;
-};
-
-struct bookmark {
-  gchar *roomjid;
-  gchar *name;
-  gchar *nick;
-  guint autojoin;
-  /* enum room_printstatus pstatus; */
-  /* enum room_autowhois awhois; */
-};
-
-extern LmConnection* lconnection;
-extern LmSSL* lssl;
-
-void xmpp_connect(void);
-void xmpp_disconnect(void);
-
-void xmpp_room_join(const char *room, const char *nickname, const char *passwd);
-int xmpp_room_setattrib(const char *roomid, const char *fjid,
-                        const char *nick, struct role_affil ra,
-                        const char *reason);
-void xmpp_room_invite(const char *room, const char *fjid, const char *reason);
-void xmpp_room_unlock(const char *room);
-void xmpp_room_destroy(const char *room, const char *venue, const char *reason);
-
-void xmpp_addbuddy(const char *bjid, const char *name, const char *group);
-void xmpp_updatebuddy(const char *bjid, const char *name, const char *group);
-void xmpp_delbuddy(const char *bjid);
-
-void xmpp_send_msg(const char *fjid, const char *text, int type,
-                   const char *subject, gboolean otrinject, gint *encrypted,
-                   LmMessageSubType type_overwrite, gpointer *xep184);
-
-void xmpp_send_s10n(const char *bjid, LmMessageSubType type);
-
-enum imstatus xmpp_getstatus(void);
-const char *xmpp_getstatusmsg(void);
-void xmpp_setprevstatus(void);
-
-void xmpp_setstatus(enum imstatus st, const char *recipient,
-                    const char *msg, int do_not_sign);
-
-void xmpp_send_chatstate(gpointer buddy, guint chatstate);
-
-GSList *xmpp_get_all_storage_bookmarks(void);
-GSList *xmpp_get_all_storage_rosternotes(void);
-void xmpp_set_storage_bookmark(const char *roomid, const char *name,
-                               const char *nick, const char *passwd,
-                               int autojoin, enum room_printstatus pstatus,
-                               enum room_autowhois awhois);
-struct annotation *xmpp_get_storage_rosternotes(const char *barejid,
-                                                int silent);
-void xmpp_set_storage_rosternotes(const char *barejid, const char *note);
-guint xmpp_is_bookmarked(const char *bjid);
-const char *xmpp_get_bookmark_nick(const char *bjid);
-
-void xmpp_request(const char *fjid, enum iqreq_type reqtype);
-void request_vcard(const char *bjid);
-void xmpp_request_storage(const gchar *storage);
-
-#endif /* __XMPP_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_defines.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-#ifndef __XMPP_DEFINES_H__
-#define __XMPP_DEFINES_H__ 1
-
-#define MCABBER_CAPS_NODE "http://mcabber.com/caps"
-
-#define NS_CLIENT    "jabber:client"
-#define NS_SERVER    "jabber:server"
-#define NS_DIALBACK  "jabber:server:dialback"
-#define NS_AUTH      "jabber:iq:auth"
-#define NS_AUTH_CRYPT "jabber:iq:auth:crypt"
-#define NS_REGISTER  "jabber:iq:register"
-#define NS_ROSTER    "jabber:iq:roster"
-#define NS_OFFLINE   "jabber:x:offline"
-#define NS_AGENT     "jabber:iq:agent"
-#define NS_AGENTS    "jabber:iq:agents"
-#define NS_DELAY     "jabber:x:delay"
-#define NS_VERSION   "jabber:iq:version"
-#define NS_TIME      "jabber:iq:time"
-#define NS_VCARD     "vcard-temp"
-#define NS_PRIVATE   "jabber:iq:private"
-#define NS_SEARCH    "jabber:iq:search"
-#define NS_OOB       "jabber:iq:oob"
-#define NS_XOOB      "jabber:x:oob"
-#define NS_ADMIN     "jabber:iq:admin"
-#define NS_FILTER    "jabber:iq:filter"
-#define NS_AUTH_0K   "jabber:iq:auth:0k"
-#define NS_BROWSE    "jabber:iq:browse"
-#define NS_EVENT     "jabber:x:event"
-#define NS_CONFERENCE "jabber:iq:conference"
-#define NS_SIGNED    "jabber:x:signed"
-#define NS_ENCRYPTED "jabber:x:encrypted"
-#define NS_GATEWAY   "jabber:iq:gateway"
-#define NS_LAST      "jabber:iq:last"
-#define NS_ENVELOPE  "jabber:x:envelope"
-#define NS_EXPIRE    "jabber:x:expire"
-#define NS_XHTML     "http://www.w3.org/1999/xhtml"
-#define NS_DISCO_INFO "http://jabber.org/protocol/disco#info"
-#define NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items"
-#define NS_IQ_AUTH    "http://jabber.org/features/iq-auth"
-#define NS_REGISTER_FEATURE "http://jabber.org/features/iq-register"
-
-#define NS_CAPS       "http://jabber.org/protocol/caps"
-#define NS_CHATSTATES "http://jabber.org/protocol/chatstates"
-#define NS_COMMANDS   "http://jabber.org/protocol/commands"
-#define NS_MUC        "http://jabber.org/protocol/muc"
-
-#define NS_XDBGINSERT "jabber:xdb:ginsert"
-#define NS_XDBNSLIST  "jabber:xdb:nslist"
-
-#define NS_XMPP_STANZAS "urn:ietf:params:xml:ns:xmpp-stanzas"
-#define NS_XMPP_TLS  "urn:ietf:params:xml:ns:xmpp-tls"
-#define NS_XMPP_STREAMS "urn:ietf:params:xml:ns:xmpp-streams"
-
-#define NS_XMPP_DELAY "urn:xmpp:delay"
-#define NS_XMPP_TIME  "urn:xmpp:time"
-#define NS_PING       "urn:xmpp:ping"
-#define NS_RECEIPTS   "urn:xmpp:receipts"
-
-#define NS_JABBERD_STOREDPRESENCE "http://jabberd.org/ns/storedpresence"
-#define NS_JABBERD_HISTORY "http://jabberd.org/ns/history"
-
-#define XMPP_ERROR_REDIRECT              302
-#define XMPP_ERROR_BAD_REQUEST           400
-#define XMPP_ERROR_NOT_AUTHORIZED        401
-#define XMPP_ERROR_PAYMENT_REQUIRED      402
-#define XMPP_ERROR_FORBIDDEN             403
-#define XMPP_ERROR_NOT_FOUND             404
-#define XMPP_ERROR_NOT_ALLOWED           405
-#define XMPP_ERROR_NOT_ACCEPTABLE        406
-#define XMPP_ERROR_REGISTRATION_REQUIRED 407
-#define XMPP_ERROR_REQUEST_TIMEOUT       408
-#define XMPP_ERROR_CONFLICT              409
-#define XMPP_ERROR_INTERNAL_SERVER_ERROR 500
-#define XMPP_ERROR_NOT_IMPLEMENTED       501
-#define XMPP_ERROR_REMOTE_SERVER_ERROR   502
-#define XMPP_ERROR_SERVICE_UNAVAILABLE   503
-#define XMPP_ERROR_REMOTE_SERVER_TIMEOUT 504
-#define XMPP_ERROR_DISCONNECTED          510
-
-#endif
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_helper.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,400 +0,0 @@
-/*
- * xmpp_helper.c    -- Jabber protocol helper functions
- *
- * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- * Some parts initially came from the centericq project:
- * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <string.h>
-#include <stdlib.h>
-
-#include "xmpp_helper.h"
-#include "settings.h"
-#include "utils.h"
-#include "caps.h"
-#include "logprint.h"
-#include "config.h"
-
-time_t iqlast; // last message/status change time
-
-extern char *imstatus_showmap[];
-
-struct xmpp_error xmpp_errors[] = {
-  {XMPP_ERROR_REDIRECT,              "302",
-    "Redirect",              "redirect",                "modify"},
-  {XMPP_ERROR_BAD_REQUEST,           "400",
-    "Bad Request",           "bad-request",             "modify"},
-  {XMPP_ERROR_NOT_AUTHORIZED,        "401",
-    "Not Authorized",        "not-authorized",          "auth"},
-  {XMPP_ERROR_PAYMENT_REQUIRED,      "402",
-    "Payment Required",      "payment-required",        "auth"},
-  {XMPP_ERROR_FORBIDDEN,             "403",
-    "Forbidden",             "forbidden",               "auth"},
-  {XMPP_ERROR_NOT_FOUND,             "404",
-    "Not Found",             "item-not-found",          "cancel"},
-  {XMPP_ERROR_NOT_ALLOWED,           "405",
-    "Not Allowed",           "not-allowed",             "cancel"},
-  {XMPP_ERROR_NOT_ACCEPTABLE,        "406",
-    "Not Acceptable",        "not-acceptable",          "modify"},
-  {XMPP_ERROR_REGISTRATION_REQUIRED, "407",
-    "Registration required", "registration-required",   "auth"},
-  {XMPP_ERROR_REQUEST_TIMEOUT,       "408",
-    "Request Timeout",       "remote-server-timeout",   "wait"},
-  {XMPP_ERROR_CONFLICT,              "409",
-    "Conflict",               "conflict",               "cancel"},
-  {XMPP_ERROR_INTERNAL_SERVER_ERROR, "500",
-    "Internal Server Error", "internal-server-error",   "wait"},
-  {XMPP_ERROR_NOT_IMPLEMENTED,       "501",
-    "Not Implemented",       "feature-not-implemented", "cancel"},
-  {XMPP_ERROR_REMOTE_SERVER_ERROR,   "502",
-    "Remote Server Error",   "service-unavailable",     "wait"},
-  {XMPP_ERROR_SERVICE_UNAVAILABLE,   "503",
-    "Service Unavailable",   "service-unavailable",     "cancel"},
-  {XMPP_ERROR_REMOTE_SERVER_TIMEOUT, "504",
-    "Remote Server Timeout", "remote-server-timeout",   "wait"},
-  {XMPP_ERROR_DISCONNECTED,          "510",
-    "Disconnected",          "service-unavailable",     "cancel"},
-  {0, NULL, NULL, NULL, NULL}
-};
-
-
-#ifdef MODULES_ENABLE
-static GSList *xmpp_additional_features = NULL;
-static char *ver, *ver_notavail;
-
-void xmpp_add_feature (const char *xmlns)
-{
-  if (xmlns) {
-    ver = NULL;
-    ver_notavail = NULL;
-    xmpp_additional_features = g_slist_append(xmpp_additional_features,
-                                              g_strdup (xmlns));
-  }
-}
-
-void xmpp_del_feature (const char *xmlns)
-{
-  GSList *feature = xmpp_additional_features;
-  while (feature) {
-    if (!strcmp(feature->data, xmlns)) {
-      ver = NULL;
-      ver_notavail = NULL;
-      g_free (feature->data);
-      xmpp_additional_features = g_slist_delete_link(xmpp_additional_features,
-                                                     feature);
-      return;
-    }
-    feature = g_slist_next (feature);
-  }
-}
-#endif
-
-const gchar* lm_message_node_get_child_value(LmMessageNode *node,
-                                             const gchar *child)
-{
-  LmMessageNode *tmp;
-  tmp = lm_message_node_find_child(node, child);
-  if (tmp)
-    return lm_message_node_get_value(tmp);
-  else return NULL;
-}
-
-static LmMessageNode *hidden = NULL;
-
-void lm_message_node_hide(LmMessageNode *node)
-{
-  LmMessageNode *parent = node->parent, *prev_sibling = node->prev;
-
-  if (hidden) {
-    hidden->children = hidden->next = hidden->prev = hidden->parent = NULL;
-    lm_message_node_unref(hidden);
-  }
-
-  if (parent->children == node)
-    parent->children = node->next;
-  if (prev_sibling)
-    prev_sibling->next = node->next;
-  if (node->next)
-    node->next->prev = prev_sibling;
-}
-
-//maybe not a good idea, because it uses internals of loudmouth...
-//it's used for rosternotes/bookmarks
-LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns)
-{
-  LmMessageNode *node;
-
-  node = g_new0 (LmMessageNode, 1);
-  node->name       = g_strdup (name);
-  node->value      = NULL;
-  node->raw_mode   = FALSE;
-  node->attributes = NULL;
-  node->next       = NULL;
-  node->prev       = NULL;
-  node->parent     = NULL;
-  node->children   = NULL;
-
-  node->ref_count  = 1;
-  lm_message_node_set_attribute(node, "xmlns", xmlns);
-  return node;
-}
-
-void lm_message_node_insert_childnode(LmMessageNode *node,
-                                      LmMessageNode *child)
-{
-  LmMessageNode *x;
-  lm_message_node_deep_ref(child);
-
-  if (node->children == NULL)
-    node->children = child;
-  else {
-    for (x = node->children; x->next; x = x->next)
-      ;
-    x->next = child;
-  }
-}
-
-void lm_message_node_deep_ref(LmMessageNode *node)
-{
-  if (node == NULL)
-    return;
-  lm_message_node_ref(node);
-  lm_message_node_deep_ref(node->next);
-  lm_message_node_deep_ref(node->children);
-}
-
-const gchar* lm_message_get_from(LmMessage *m)
-{
-  return lm_message_node_get_attribute(m->node, "from");
-}
-
-const gchar* lm_message_get_id(LmMessage *m)
-{
-  return lm_message_node_get_attribute(m->node, "id");
-}
-
-LmMessage *lm_message_new_iq_from_query(LmMessage *m,
-                                        LmMessageSubType type)
-{
-  LmMessage *new;
-  const char *from = lm_message_node_get_attribute(m->node, "from");
-  const char *id = lm_message_node_get_attribute(m->node, "id");
-
-  new = lm_message_new_with_sub_type(from, LM_MESSAGE_TYPE_IQ,
-                                     type);
-  if (id)
-    lm_message_node_set_attribute(new->node, "id", id);
-
-  return new;
-}
-
-//  entity_version(enum imstatus status)
-// Return a static version string for Entity Capabilities.
-// It should be specific to the client version, please change the id
-// if you alter mcabber's disco support (or add something to the version
-// number) so that it doesn't conflict with the official client.
-const char *entity_version(enum imstatus status)
-{
-#ifndef MODULES_ENABLE
-  static char *ver, *ver_notavail;
-#endif
-
-  if (ver && (status != notavail))
-    return ver;
-  if (ver_notavail)
-    return ver_notavail;
-
-  caps_add("");
-  caps_set_identity("", "client", PACKAGE_STRING, "pc");
-  caps_add_feature("", NS_DISCO_INFO);
-  caps_add_feature("", NS_MUC);
-  // advertise ChatStates only if they aren't disabled
-  if (!settings_opt_get_int("disable_chatstates"))
-   caps_add_feature("", NS_CHATSTATES);
-  caps_add_feature("", NS_TIME);
-  caps_add_feature("", NS_XMPP_TIME);
-  caps_add_feature("", NS_VERSION);
-  caps_add_feature("", NS_PING);
-  caps_add_feature("", NS_COMMANDS);
-  caps_add_feature("", NS_RECEIPTS);
-  if (!settings_opt_get_int("iq_last_disable") &&
-      (!settings_opt_get_int("iq_last_disable_when_notavail") ||
-       status != notavail))
-   caps_add_feature("", NS_LAST);
-#ifdef MODULES_ENABLE
-  {
-    GSList *el = xmpp_additional_features;
-    while (el) {
-      caps_add_feature("", el->data);
-      el = g_slist_next (el);
-    }
-  }
-#endif
-
-  if (status == notavail) {
-    ver_notavail = caps_generate();
-    return ver_notavail;
-  }
-
-  ver = caps_generate();
-  return ver;
-}
-
-LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
-                                          const char *xmlns)
-{
-  LmMessageNode *x;
-  const char *p;
-
-  for (x = node->children ; x; x = x->next) {
-    if ((p = lm_message_node_get_attribute(x, "xmlns")) && !strcmp(p, xmlns))
-      break;
-  }
-  return x;
-}
-
-time_t lm_message_node_get_timestamp(LmMessageNode *node)
-{
-  LmMessageNode *x;
-  const char *p;
-
-  x = lm_message_node_find_xmlns(node, NS_XMPP_DELAY);
-  if (x && (!strcmp(x->name, "delay")) &&
-      (p = lm_message_node_get_attribute(x, "stamp")) != NULL)
-    return from_iso8601(p, 1);
-  x = lm_message_node_find_xmlns(node, NS_DELAY);
-  if (x && (p = lm_message_node_get_attribute(x, "stamp")) != NULL)
-    return from_iso8601(p, 1);
-  return 0;
-}
-
-//  lm_message_new_presence(status, recipient, message)
-// Create an xmlnode with default presence attributes
-// Note: the caller must free the node after use
-LmMessage *lm_message_new_presence(enum imstatus st,
-                                   const char *recipient,
-                                   const char *msg)
-{
-  unsigned int prio;
-  LmMessage *x = lm_message_new(recipient, LM_MESSAGE_TYPE_PRESENCE);
-
-  switch(st) {
-    case away:
-    case notavail:
-    case dontdisturb:
-    case freeforchat:
-        lm_message_node_add_child(x->node, "show", imstatus_showmap[st]);
-        break;
-
-    case invisible:
-        lm_message_node_set_attribute(x->node, "type", "invisible");
-        break;
-
-    case offline:
-        lm_message_node_set_attribute(x->node, "type", "unavailable");
-        break;
-
-    default:
-        break;
-  }
-
-  if (st == away || st == notavail)
-    prio = settings_opt_get_int("priority_away");
-  else
-    prio = settings_opt_get_int("priority");
-
-  if (prio) {
-    char strprio[8];
-    snprintf(strprio, 8, "%d", (int)prio);
-    lm_message_node_add_child(x->node, "priority", strprio);
-  }
-
-  if (msg)
-    lm_message_node_add_child(x->node, "status", msg);
-
-  return x;
-}
-
-static const char *defaulterrormsg(guint code)
-{
-  int i;
-
-  for (i = 0; xmpp_errors[i].code; ++i) {
-    if (xmpp_errors[i].code == code)
-      return xmpp_errors[i].meaning;
-  }
-  return NULL;
-}
-
-//  display_server_error(x)
-// Display the error to the user
-// x: error tag xmlnode pointer
-void display_server_error(LmMessageNode *x)
-{
-  const char *desc = NULL, *p=NULL, *s;
-  char *sdesc, *tmp;
-  int code = 0;
-
-  if (!x) return;
-
-  /* RFC3920:
-   *    The <error/> element:
-   *       o  MUST contain a child element corresponding to one of the defined
-   *          stanza error conditions specified below; this element MUST be
-   *          qualified by the 'urn:ietf:params:xml:ns:xmpp-stanzas' namespace.
-   */
-  if (x->children)
-    p = x->children->name;
-  if (p)
-    scr_LogPrint(LPRINT_LOGNORM, "Received error packet [%s]", p);
-
-  // For backward compatibility
-  if ((s = lm_message_node_get_attribute(x, "code")) != NULL) {
-    code = atoi(s);
-    // Default message
-    desc = defaulterrormsg(code);
-  }
-
-  // Error tag data is better, if available
-  s = lm_message_node_get_value(x);
-  if (s && *s) desc = s;
-
-  // And sometimes there is a text message
-  s = lm_message_node_get_child_value(x, "text");
-
-  if (s && *s) desc = s;
-
-  // If we still have no description, let's give up
-  if (!desc)
-    return;
-
-  // Strip trailing newlines
-  sdesc = g_strdup(desc);
-  for (tmp = sdesc; *tmp; tmp++) ;
-  if (tmp > sdesc)
-    tmp--;
-  while (tmp >= sdesc && (*tmp == '\n' || *tmp == '\r'))
-    *tmp-- = '\0';
-
-  scr_LogPrint(LPRINT_LOGNORM, "Error code from server: %d %s", code, sdesc);
-  g_free(sdesc);
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_helper.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,58 +0,0 @@
-#ifndef __XMPPHELPER_H__
-#define __XMPPHELPER_H__ 1
-
-#include <time.h>
-#include <loudmouth/loudmouth.h>
-
-#include "xmpp.h"
-#include "xmpp_defines.h"
-#include "config.h"
-
-extern time_t iqlast;           /* last message/status change time */
-
-struct T_presence {
-  enum imstatus st;
-  const char *msg;
-};
-
-struct xmpp_error {
-  guint code;
-  const char *code_str;
-  const char *meaning;
-  const char *condition;
-  const char *type;
-};
-
-
-#ifdef MODULES_ENABLE
-void xmpp_add_feature (const char *xmlns);
-void xmpp_del_feature (const char *xmlns);
-#endif
-
-LmMessageNode *lm_message_node_new(const gchar *name, const gchar *xmlns);
-LmMessageNode *lm_message_node_find_xmlns(LmMessageNode *node,
-                                          const char *xmlns);
-const gchar* lm_message_node_get_child_value(LmMessageNode *node,
-                                             const gchar *child);
-void lm_message_node_hide(LmMessageNode *node);
-void lm_message_node_insert_childnode(LmMessageNode *node,
-                                      LmMessageNode *child);
-void lm_message_node_deep_ref(LmMessageNode *node);
-time_t lm_message_node_get_timestamp(LmMessageNode *node);
-
-LmMessage *lm_message_new_iq_from_query(LmMessage *m, LmMessageSubType type);
-
-LmMessage *lm_message_new_presence(enum imstatus st,
-                                   const char *recipient, const char *msg);
-
-const gchar* lm_message_get_from(LmMessage *m);
-const gchar* lm_message_get_id(LmMessage *m);
-
-void display_server_error(LmMessageNode *x);
-
-/* XEP-0115 (Entity Capabilities) node */
-const char *entity_version(enum imstatus status);
-
-#endif
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_iq.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,836 +0,0 @@
-/*
- * xmpp_iq.c    -- Jabber protocol IQ-related stuff
- *
- * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- * Parts come from the centericq project:
- * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua>
- * Some small parts come from the Pidgin project <http://pidgin.im/>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <string.h>
-#include <sys/utsname.h>
-
-#include "xmpp_helper.h"
-#include "commands.h"
-#include "screen.h"
-#include "utils.h"
-#include "logprint.h"
-#include "settings.h"
-#include "caps.h"
-#include "main.h"
-
-extern struct xmpp_error xmpp_errors[];
-
-static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h,
-                                                    LmConnection *c,
-                                                    LmMessage *m,
-                                                    gpointer ud);
-
-static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h,
-                                                          LmConnection *c,
-                                                          LmMessage *m,
-                                                          gpointer ud);
-
-inline double seconds_since_last_use(void);
-
-struct adhoc_command {
-  char *name;
-  char *description;
-  bool only_for_self;
-  LmHandleMessageFunction callback;
-};
-
-const struct adhoc_command adhoc_command_list[] = {
-  { "http://jabber.org/protocol/rc#set-status",
-    "Change client status",
-    1,
-    &handle_iq_command_set_status },
-  { "http://jabber.org/protocol/rc#leave-groupchats",
-    "Leave groupchat(s)",
-    1,
-    &handle_iq_command_leave_groupchats },
-  { NULL, NULL, 0, NULL },
-};
-
-struct adhoc_status {
-  char *name;   // the name used by adhoc
-  char *description;
-  char *status; // the string, used by setstus
-};
-// It has to match imstatus of roster.h!
-const struct adhoc_status adhoc_status_list[] = {
-  {"offline", "Offline", "offline"},
-  {"online", "Online", "avail"},
-  {"chat", "Chat", "free"},
-  {"dnd", "Do not disturb", "dnd"},
-  {"xd", "Extended away", "notavail"},
-  {"away", "Away", "away"},
-  {"invisible", "Invisible", "invisible"},
-  {NULL, NULL, NULL},
-};
-
-static char *generate_session_id(char *prefix)
-{
-  char *result;
-  static int counter = 0;
-  counter++;
-  // TODO better use timestamp?
-  result = g_strdup_printf("%s-%i", prefix, counter);
-  return result;
-}
-
-static LmMessage *lm_message_new_iq_error(LmMessage *m, guint error)
-{
-  LmMessage *r;
-  LmMessageNode *err;
-  int i;
-
-  for (i = 0; xmpp_errors[i].code; ++i)
-    if (xmpp_errors[i].code == error)
-      break;
-  g_return_val_if_fail(xmpp_errors[i].code > 0, NULL);
-
-  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_ERROR);
-  err = lm_message_node_add_child(r->node, "error", NULL);
-  lm_message_node_set_attribute(err, "code", xmpp_errors[i].code_str);
-  lm_message_node_set_attribute(err, "type", xmpp_errors[i].type);
-  lm_message_node_set_attribute
-          (lm_message_node_add_child(err,
-                                     xmpp_errors[i].condition, NULL),
-           "xmlns", NS_XMPP_STANZAS);
-
-  return r;
-}
-
-void send_iq_error(LmConnection *c, LmMessage *m, guint error)
-{
-  LmMessage *r;
-  r = lm_message_new_iq_error(m, error);
-  lm_connection_send(c, r, NULL);
-  lm_message_unref(r);
-}
-
-static void lm_message_node_add_dataform_result(LmMessageNode *node,
-                                                const char *message)
-{
-  LmMessageNode *x, *field;
-
-  x = lm_message_node_add_child(node, "x", NULL);
-  lm_message_node_set_attributes(x,
-                                 "type", "result",
-                                 "xmlns", "jabber:x:data",
-                                 NULL);
-  field = lm_message_node_add_child(x, "field", NULL);
-  lm_message_node_set_attributes(field,
-                                 "type", "text-single",
-                                 "var", "message",
-                                 NULL);
-  lm_message_node_add_child(field, "value", message);
-}
-
-static LmHandlerResult handle_iq_commands_list(LmMessageHandler *h,
-                                               LmConnection *c,
-                                               LmMessage *m, gpointer ud)
-{
-  LmMessage *iq;
-  LmMessageNode *query;
-  const char *requester_jid;
-  const struct adhoc_command *command;
-  const char *node;
-  gboolean from_self;
-
-  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  query = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_COMMANDS);
-  node = lm_message_node_get_attribute
-          (lm_message_node_get_child(m->node, "query"),
-           "node");
-  if (node)
-    lm_message_node_set_attribute(query, "node", node);
-
-  requester_jid = lm_message_get_from(m);
-  from_self = jid_equal(lm_connection_get_jid(c), requester_jid);
-
-  for (command = adhoc_command_list ; command->name ; command++) {
-    if (!command->only_for_self || from_self) {
-      lm_message_node_set_attributes
-              (lm_message_node_add_child(query, "item", NULL),
-               "node", command->name,
-               "name", command->description,
-               "jid", lm_connection_get_jid(c),
-               NULL);
-    }
-  }
-
-  lm_connection_send(c, iq, NULL);
-  lm_message_unref(iq);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h,
-                                                    LmConnection *c,
-                                                    LmMessage *m, gpointer ud)
-{
-  const char *action, *node;
-  char *sessionid;
-  LmMessage *iq;
-  LmMessageNode *command, *x, *y;
-  const struct adhoc_status *s;
-
-  x = lm_message_node_get_child(m->node, "command");
-  action = lm_message_node_get_attribute(x, "action");
-  node = lm_message_node_get_attribute(x, "node");
-  sessionid = (char *)lm_message_node_get_attribute(x, "sessionid");
-
-  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  command = lm_message_node_add_child(iq->node, "command", NULL);
-  lm_message_node_set_attribute(command, "node", node);
-  lm_message_node_set_attribute(command, "xmlns", NS_COMMANDS);
-
-  if (!sessionid) {
-    sessionid = generate_session_id("set-status");
-    lm_message_node_set_attribute(command, "sessionid", sessionid);
-    g_free(sessionid);
-    sessionid = NULL;
-    lm_message_node_set_attribute(command, "status", "executing");
-
-    x = lm_message_node_add_child(command, "x", NULL);
-    lm_message_node_set_attribute(x, "type", "form");
-    lm_message_node_set_attribute(x, "xmlns", "jabber:x:data");
-
-    lm_message_node_add_child(x, "title", "Change Status");
-
-    lm_message_node_add_child(x, "instructions",
-                              "Choose the status and status message");
-
-    // TODO see if factorisation is possible
-    y = lm_message_node_add_child(x, "field", NULL);
-    lm_message_node_set_attribute(y, "type", "hidden");
-    lm_message_node_set_attribute(y, "var", "FORM_TYPE");
-
-    lm_message_node_add_child(y, "value", "http://jabber.org/protocol/rc");
-
-    y = lm_message_node_add_child(x, "field", NULL);
-    lm_message_node_set_attributes(y,
-                                   "type", "list-single",
-                                   "var", "status",
-                                   "label", "Status",
-                                   NULL);
-    lm_message_node_add_child(y, "required", NULL);
-
-    // XXX: ugly
-    lm_message_node_add_child(y, "value",
-                              adhoc_status_list[xmpp_getstatus()].name);
-    for (s = adhoc_status_list; s->name; s++) {
-        LmMessageNode *option = lm_message_node_add_child(y, "option", NULL);
-        lm_message_node_add_child(option, "value", s->name);
-        lm_message_node_set_attribute(option, "label", s->description);
-    }
-    // TODO add priority ?
-    // I do not think this is useful, user should not have to care of the
-    // priority like gossip and gajim do (misc)
-    lm_message_node_set_attributes
-            (lm_message_node_add_child(x, "field", NULL),
-             "type", "text-multi",
-             "var", "status-message",
-             "label", "Message",
-             NULL);
-  } else if (action && !strcmp(action, "cancel")) {
-    lm_message_node_set_attribute(command, "status", "canceled");
-  } else  { // (if sessionid and not canceled)
-    y = lm_message_node_find_xmlns(x, "jabber:x:data"); //x?xmlns=jabber:x:data
-    if (y) {
-      const char *value=NULL, *message=NULL;
-      LmMessageNode *fields, *field;
-      field = fields = lm_message_node_get_child(y, "field"); //field?var=status
-      while (field && strcmp("status",
-                             lm_message_node_get_attribute(field, "var")))
-        field = field->next;
-      field = lm_message_node_get_child(field, "value");
-      if (field)
-        value = lm_message_node_get_value(field);
-      field = fields; //field?var=status-message
-      while (field && strcmp("status-message",
-                             lm_message_node_get_attribute(field, "var")))
-        field = field->next;
-      field = lm_message_node_get_child(field, "value");
-      if (field)
-        message = lm_message_node_get_value(field);
-      if (value) {
-        for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++);
-        if (s->name) {
-          char *status = g_strdup_printf("%s %s", s->status,
-                                         message ? message : "");
-          cmd_setstatus(NULL, status);
-          g_free(status);
-          lm_message_node_set_attribute(command, "status", "completed");
-          lm_message_node_add_dataform_result(command,
-                                              "Status has been changed");
-        }
-      }
-    }
-  }
-  if (sessionid)
-    lm_message_node_set_attribute(command, "sessionid", sessionid);
-  lm_connection_send(c, iq, NULL);
-  lm_message_unref(iq);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void _callback_foreach_buddy_groupchat(gpointer rosterdata, void *param)
-{
-  LmMessageNode *field, *option;
-  const char *room_jid, *nickname;
-  char *desc;
-
-  room_jid = buddy_getjid(rosterdata);
-  if (!room_jid) return;
-  nickname = buddy_getnickname(rosterdata);
-  if (!nickname) return;
-  field = param;
-
-  option = lm_message_node_add_child(field, "option", NULL);
-  lm_message_node_add_child(option, "value", room_jid);
-  desc = g_strdup_printf("%s on %s", nickname, room_jid);
-  lm_message_node_set_attribute(option, "label", desc);
-  g_free(desc);
-}
-
-static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h,
-                                                          LmConnection *c,
-                                                          LmMessage *m,
-                                                          gpointer ud)
-{
-  const char *action, *node;
-  char *sessionid;
-  LmMessage *iq;
-  LmMessageNode *command, *x;
-
-  x = lm_message_node_get_child(m->node, "command");
-  action = lm_message_node_get_attribute(x, "action");
-  node = lm_message_node_get_attribute(x, "node");
-  sessionid = (char*)lm_message_node_get_attribute(x, "sessionid");
-
-  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  command = lm_message_node_add_child(iq->node, "command", NULL);
-  lm_message_node_set_attributes(command,
-                                 "node", node,
-                                 "xmlns", NS_COMMANDS,
-                                 NULL);
-
-  if (!sessionid) {
-    LmMessageNode *field;
-
-    sessionid = generate_session_id("leave-groupchats");
-    lm_message_node_set_attribute(command, "sessionid", sessionid);
-    g_free(sessionid);
-    sessionid = NULL;
-    lm_message_node_set_attribute(command, "status", "executing");
-
-    x = lm_message_node_add_child(command, "x", NULL);
-    lm_message_node_set_attributes(x,
-                                   "type", "form",
-                                   "xmlns", "jabber:x:data",
-                                   NULL);
-
-    lm_message_node_add_child(x, "title", "Leave groupchat(s)");
-
-    lm_message_node_add_child(x, "instructions",
-                              "What groupchats do you want to leave?");
-
-    field = lm_message_node_add_child(x, "field", NULL);
-    lm_message_node_set_attributes(field,
-                                   "type", "hidden",
-                                   "var", "FORM_TYPE",
-                                   NULL);
-
-    lm_message_node_add_child(field, "value",
-                              "http://jabber.org/protocol/rc");
-
-    field = lm_message_node_add_child(x, "field", NULL);
-    lm_message_node_set_attributes(field,
-                                   "type", "list-multi",
-                                   "var", "groupchats",
-                                   "label", "Groupchats: ",
-                                   NULL);
-    lm_message_node_add_child(field, "required", NULL);
-
-    foreach_buddy(ROSTER_TYPE_ROOM, &_callback_foreach_buddy_groupchat, field);
-    //TODO: return an error if we are not connected to groupchats
-  } else if (action && !strcmp(action, "cancel")) {
-    lm_message_node_set_attribute(command, "status", "canceled");
-  } else  { // (if sessionid and not canceled)
-    LmMessageNode *form = lm_message_node_find_xmlns(x, "jabber:x:data");//TODO
-    if (form) {
-      LmMessageNode *field;
-
-      lm_message_node_set_attribute(command, "status", "completed");
-      //TODO: implement sth. like "field?var=groupchats" in xmlnode...
-      field  = lm_message_node_get_child(form, "field");
-      while (field && strcmp("groupchats",
-                             lm_message_node_get_attribute(field, "var")))
-        field = field->next;
-
-      if (field)
-        for (x = field->children ; x ; x = x->next)
-        {
-          if (!strcmp (x->name, "value")) {
-            GList* b = buddy_search_jid(lm_message_node_get_value(x));
-            if (b)
-              cmd_room_leave(b->data, "Requested by remote command");
-          }
-        }
-      lm_message_node_add_dataform_result(command,
-                                          "Groupchats have been left");
-    }
-  }
-  if (sessionid)
-    lm_message_node_set_attribute(command, "sessionid", sessionid);
-  lm_connection_send(c, iq, NULL);
-  lm_message_unref(iq);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-LmHandlerResult handle_iq_commands(LmMessageHandler *h,
-                                   LmConnection *c,
-                                   LmMessage *m, gpointer ud)
-{
-  const char *requester_jid = NULL;
-  LmMessageNode *cmd;
-  const struct adhoc_command *command;
-
-  // mcabber has only partial XEP-0146 support...
-  if (LM_MESSAGE_SUB_TYPE_SET != lm_message_get_sub_type(m))
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
-  requester_jid = lm_message_get_from(m);
-
-  cmd = lm_message_node_get_child(m->node, "command");
-  if (jid_equal(lm_connection_get_jid(c), requester_jid)) {
-    const char *action, *node;
-    action = lm_message_node_get_attribute(cmd, "action");
-    node = lm_message_node_get_attribute(cmd, "node");
-    // action can be NULL, in which case it seems to take the default,
-    // ie execute
-    if (!action || !strcmp(action, "execute") || !strcmp(action, "cancel")
-        || !strcmp(action, "next") || !strcmp(action, "complete")) {
-      for (command = adhoc_command_list; command->name; command++) {
-        if (!strcmp(node, command->name))
-          command->callback(h, c, m, ud);
-      }
-      // "prev" action will get there, as we do not implement it,
-      // and do not authorize it
-    } else {
-      LmMessage *r;
-      LmMessageNode *err;
-      r = lm_message_new_iq_error(m, XMPP_ERROR_BAD_REQUEST);
-      err = lm_message_node_get_child(r->node, "error");
-      lm_message_node_set_attribute
-              (lm_message_node_add_child(err, "malformed-action", NULL),
-               "xmlns", NS_COMMANDS);
-      lm_connection_send(c, r, NULL);
-      lm_message_unref(r);
-    }
-  } else {
-    send_iq_error(c, m, XMPP_ERROR_FORBIDDEN);
-  }
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-
-LmHandlerResult handle_iq_disco_items(LmMessageHandler *h,
-                                      LmConnection *c,
-                                      LmMessage *m, gpointer ud)
-{
-  LmMessageNode *query;
-  const char *node;
-  query = lm_message_node_get_child(m->node, "query");
-  node = lm_message_node_get_attribute(query, "node");
-  if (node) {
-    if (!strcmp(node, NS_COMMANDS)) {
-      return handle_iq_commands_list(NULL, c, m, ud);
-    } else {
-      send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED);
-    }
-  } else {
-    // not sure about this one
-    send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED);
-  }
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-
-void _disco_add_feature_helper(gpointer data, gpointer user_data)
-{
-  LmMessageNode *node = user_data;
-  lm_message_node_set_attribute
-          (lm_message_node_add_child(node, "feature", NULL), "var", data);
-}
-
-//  disco_info_set_caps(ansquery, entitycaps)
-// Add features attributes to ansquery.  entitycaps should either be a
-// valid capabilities hash or NULL. If it is NULL, the node attribute won't
-// be added to the query child and Entity Capabilities will be announced
-// as a feature.
-// Please change the entity version string if you modify mcabber disco
-// source code, so that it doesn't conflict with the upstream client.
-static void disco_info_set_caps(LmMessageNode *ansquery,
-                                const char *entitycaps)
-{
-  if (entitycaps) {
-    char *eversion;
-    eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entitycaps);
-    lm_message_node_set_attribute(ansquery, "node", eversion);
-    g_free(eversion);
-  }
-
-  lm_message_node_set_attributes
-          (lm_message_node_add_child(ansquery, "identity", NULL),
-           "category", "client",
-           "name", PACKAGE_STRING,
-           "type", "pc",
-           NULL);
-
-  if (entitycaps)
-    caps_foreach_feature(entitycaps, _disco_add_feature_helper, ansquery);
-  else {
-    caps_foreach_feature(entity_version(xmpp_getstatus()),
-                         _disco_add_feature_helper,
-                         ansquery);
-    lm_message_node_set_attribute
-            (lm_message_node_add_child(ansquery, "feature", NULL),
-             "var", NS_CAPS);
-  }
-}
-
-LmHandlerResult handle_iq_disco_info(LmMessageHandler *h,
-                                     LmConnection *c,
-                                     LmMessage *m, gpointer ud)
-{
-  LmMessage *r;
-  LmMessageNode *query, *tmp;
-  const char *node = NULL;
-  const char *param = NULL;
-
-  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_RESULT)
-    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-
-  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  query = lm_message_node_add_child(r->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_DISCO_INFO);
-  tmp = lm_message_node_find_child(m->node, "query");
-  if (tmp) {
-    node = lm_message_node_get_attribute(tmp, "node");
-    param = node+strlen(MCABBER_CAPS_NODE)+1;
-  }
-  if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE))
-    disco_info_set_caps(query, param);  // client#version
-  else
-    // Basic discovery request
-    disco_info_set_caps(query, NULL);
-
-  lm_connection_send(c, r, NULL);
-  lm_message_unref(r);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c,
-                                 LmMessage *m, gpointer ud)
-{
-  LmMessageNode *y;
-  const char *fjid, *name, *group, *sub, *ask;
-  char *cleanalias;
-  enum subscr esub;
-  int need_refresh = FALSE;
-  guint roster_type;
-
-  for (y = lm_message_node_find_child(lm_message_node_find_xmlns
-                                      (m->node, NS_ROSTER),
-                                      "item");
-       y;
-       y = y->next) {
-    char *name_tmp = NULL;
-
-    fjid = lm_message_node_get_attribute(y, "jid");
-    name = lm_message_node_get_attribute(y, "name");
-    sub = lm_message_node_get_attribute(y, "subscription");
-    ask = lm_message_node_get_attribute(y, "ask");
-
-    if (lm_message_node_find_child(y, "group"))
-      group = lm_message_node_get_value(lm_message_node_find_child(y, "group"));
-    else
-      group = NULL;
-
-    if (!fjid)
-      continue;
-
-    cleanalias = jidtodisp(fjid);
-
-    esub = sub_none;
-    if (sub) {
-      if (!strcmp(sub, "to"))          esub = sub_to;
-      else if (!strcmp(sub, "from"))   esub = sub_from;
-      else if (!strcmp(sub, "both"))   esub = sub_both;
-      else if (!strcmp(sub, "remove")) esub = sub_remove;
-    }
-
-    if (esub == sub_remove) {
-      roster_del_user(cleanalias);
-      scr_LogPrint(LPRINT_LOGNORM, "Buddy <%s> has been removed "
-                   "from the roster", cleanalias);
-      g_free(cleanalias);
-      need_refresh = TRUE;
-      continue;
-    }
-
-    if (ask && !strcmp(ask, "subscribe"))
-      esub |= sub_pending;
-
-    if (!name) {
-      if (!settings_opt_get_int("roster_hide_domain")) {
-        name = cleanalias;
-      } else {
-        char *p;
-        name = name_tmp = g_strdup(cleanalias);
-        p = strchr(name_tmp, JID_DOMAIN_SEPARATOR);
-        if (p)  *p = '\0';
-      }
-    }
-
-    // Tricky... :-\  My guess is that if there is no JID_DOMAIN_SEPARATOR,
-    // this is an agent.
-    if (strchr(cleanalias, JID_DOMAIN_SEPARATOR))
-      roster_type = ROSTER_TYPE_USER;
-    else
-      roster_type = ROSTER_TYPE_AGENT;
-
-    roster_add_user(cleanalias, name, group, roster_type, esub, 1);
-
-    g_free(name_tmp);
-    g_free(cleanalias);
-  }
-
-  buddylist_build();
-  update_roster = TRUE;
-  if (need_refresh)
-    scr_UpdateBuddyWindow();
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer ud)
-{
-  LmMessage *r;
-
-  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  lm_connection_send(c, r, NULL);
-  lm_message_unref(r);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-double seconds_since_last_use(void)
-{
-  return difftime(time(NULL), iqlast);
-}
-
-LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer ud)
-{
-  LmMessage *r;
-  LmMessageNode *query;
-  char *seconds;
-
-  if (!settings_opt_get_int("iq_hide_requests")) {
-    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ last time request from <%s>",
-                 lm_message_get_from(m));
-  }
-
-  if (settings_opt_get_int("iq_last_disable") ||
-      (settings_opt_get_int("iq_last_disable_when_notavail") &&
-       xmpp_getstatus() == notavail))
-  {
-    send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
-    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-  }
-
-  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  query = lm_message_node_add_child(r->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_LAST);
-  seconds = g_strdup_printf("%.0f", seconds_since_last_use());
-  lm_message_node_set_attribute(query, "seconds", seconds);
-  g_free(seconds);
-
-  lm_connection_send(c, r, NULL);
-  lm_message_unref(r);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c,
-                                  LmMessage *m, gpointer ud)
-{
-  LmMessage *r;
-  LmMessageNode *query;
-  char *os = NULL;
-  char *ver = mcabber_version();
-
-  if (!settings_opt_get_int("iq_hide_requests")) {
-    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ version request from <%s>",
-                 lm_message_get_from(m));
-  }
-  if (!settings_opt_get_int("iq_version_hide_os")) {
-    struct utsname osinfo;
-    uname(&osinfo);
-    os = g_strdup_printf("%s %s %s", osinfo.sysname, osinfo.release,
-                         osinfo.machine);
-  }
-
-  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-
-  query = lm_message_node_add_child(r->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_VERSION);
-
-  lm_message_node_add_child(query, "name", PACKAGE_NAME);
-  lm_message_node_add_child(query, "version", ver);
-  if (os) {
-    lm_message_node_add_child(query, "os", os);
-    g_free(os);
-  }
-
-  g_free(ver);
-  lm_connection_send(c, r, NULL);
-  lm_message_unref(r);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-// This function borrows some code from the Pidgin project
-LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer ud)
-{
-  LmMessage *r;
-  LmMessageNode *query;
-  char *buf, *utf8_buf;
-  time_t now_t;
-  struct tm *now;
-
-  time(&now_t);
-
-  if (!settings_opt_get_int("iq_hide_requests")) {
-    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>",
-                 lm_message_get_from(m));
-  }
-
-  buf = g_new0(char, 512);
-
-  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  query = lm_message_node_add_child(r->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_TIME);
-
-  now = gmtime(&now_t);
-
-  strftime(buf, 512, "%Y%m%dT%T", now);
-  lm_message_node_add_child(query, "utc", buf);
-
-  now = localtime(&now_t);
-
-  strftime(buf, 512, "%Z", now);
-  if ((utf8_buf = to_utf8(buf))) {
-    lm_message_node_add_child(query, "tz", utf8_buf);
-    g_free(utf8_buf);
-  }
-
-  strftime(buf, 512, "%d %b %Y %T", now);
-  if ((utf8_buf = to_utf8(buf))) {
-    lm_message_node_add_child(query, "display", utf8_buf);
-    g_free(utf8_buf);
-  }
-
-  lm_connection_send(c, r, NULL);
-  lm_message_unref(r);
-  g_free(buf);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-// This function borrows some code from the Pidgin project
-LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c,
-                                  LmMessage *m, gpointer ud)
-{
-  LmMessage *r;
-  LmMessageNode *query;
-  char *buf, *utf8_buf;
-  time_t now_t;
-  struct tm *now;
-  char const *sign;
-  int diff = 0;
-
-  time(&now_t);
-
-  if (!settings_opt_get_int("iq_hide_requests")) {
-    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>",
-                 lm_message_get_from(m));
-  }
-
-  buf = g_new0(char, 512);
-
-  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
-  query = lm_message_node_add_child(r->node, "time", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_XMPP_TIME);
-
-  now = localtime(&now_t);
-
-  if (now->tm_isdst >= 0) {
-#if defined HAVE_TM_GMTOFF
-    diff = now->tm_gmtoff;
-#elif defined HAVE_TIMEZONE
-    tzset();
-    diff = -timezone;
-#endif
-  }
-
-  if (diff < 0) {
-    sign = "-";
-    diff = -diff;
-  } else {
-    sign = "+";
-  }
-  diff /= 60;
-  snprintf(buf, 512, "%c%02d:%02d", *sign, diff / 60, diff % 60);
-  if ((utf8_buf = to_utf8(buf))) {
-    lm_message_node_add_child(query, "tzo", utf8_buf);
-    g_free(utf8_buf);
-  }
-
-  now = gmtime(&now_t);
-
-  strftime(buf, 512, "%Y-%m-%dT%TZ", now);
-  lm_message_node_add_child(query, "utc", buf);
-
-  lm_connection_send(c, r, NULL);
-  lm_message_unref(r);
-  g_free(buf);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c,
-                                LmMessage *m, gpointer ud)
-{
-  send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_iq.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-#ifndef __XMPP_IQ_H__
-#define __XMPP_IQ_H__ 1
-
-LmHandlerResult handle_iq_commands(LmMessageHandler *h,
-                                   LmConnection *c,
-                                   LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_disco_items(LmMessageHandler *h,
-                                      LmConnection *c,
-                                      LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_disco_info(LmMessageHandler *h,
-                                     LmConnection *c,
-                                     LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c,
-                                 LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c,
-                                  LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c,
-                                  LmMessage *m, gpointer ud);
-LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c,
-                                LmMessage *m, gpointer ud);
-
-void send_iq_error(LmConnection *c, LmMessage *m, guint error);
-
-#endif /* __XMPP_IQ_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_iqrequest.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,636 +0,0 @@
-/*
- * xmpp_iqrequest.c -- Jabber IQ request handling
- *
- * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <string.h>
-#include <stdlib.h>
-
-#include "xmpp_helper.h"
-#include "xmpp_iq.h"
-#include "screen.h"
-#include "utils.h"
-#include "settings.h"
-#include "hooks.h"
-#include "hbuf.h"
-
-extern LmMessageNode *bookmarks;
-extern LmMessageNode *rosternotes;
-
-static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c,
-                                 LmMessage *m, gpointer user_data);
-static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c,
-                                  LmMessage *m, gpointer user_data);
-static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer user_data);
-static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer user_data);
-static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer user_data);
-
-static struct IqRequestHandlers
-{
-  const gchar *xmlns;
-  const gchar *querytag;
-  LmHandleMessageFunction handler;
-} iq_request_handlers[] = {
-  {NS_ROSTER, "query", &cb_roster},
-  {NS_VERSION,"query", &cb_version},
-  {NS_TIME,   "query", &cb_time},
-  {NS_LAST,   "query", &cb_last},
-  {NS_VCARD,  "vCard", &cb_vcard},
-  {NULL, NULL, NULL}
-};
-
-// Enum for vCard attributes
-enum vcard_attr {
-  vcard_home    = 1<<0,
-  vcard_work    = 1<<1,
-  vcard_postal  = 1<<2,
-  vcard_voice   = 1<<3,
-  vcard_fax     = 1<<4,
-  vcard_cell    = 1<<5,
-  vcard_inet    = 1<<6,
-  vcard_pref    = 1<<7,
-};
-
-// xmlns has to be a namespace from iq_request_handlers[].xmlns
-void xmpp_iq_request(const char *fulljid, const char *xmlns)
-{
-  LmMessage *iq;
-  LmMessageNode *query;
-  LmMessageHandler *handler;
-  int i;
-
-  iq = lm_message_new_with_sub_type(fulljid, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_GET);
-  for (i = 0; strcmp(iq_request_handlers[i].xmlns, xmlns) != 0 ; ++i)
-       ;
-  query = lm_message_node_add_child(iq->node,
-                                    iq_request_handlers[i].querytag,
-                                    NULL);
-  lm_message_node_set_attribute(query, "xmlns", xmlns);
-  handler = lm_message_handler_new(iq_request_handlers[i].handler,
-                                   NULL, FALSE);
-  lm_connection_send_with_reply(lconnection, iq, handler, NULL);
-  lm_message_handler_unref(handler);
-  lm_message_unref(iq);
-}
-
-//  This callback is reached when mcabber receives the first roster update
-// after the connection.
-static LmHandlerResult cb_roster(LmMessageHandler *h, LmConnection *c,
-                                 LmMessage *m, gpointer user_data)
-{
-  LmMessageNode *x;
-  const char *ns;
-
-  // Only execute the hook if the roster has been successfully retrieved
-  if (lm_message_get_sub_type(m) != LM_MESSAGE_SUB_TYPE_RESULT)
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
-  x = lm_message_node_find_child(m->node, "query");
-  if (!x)
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-
-  ns = lm_message_node_get_attribute(x, "xmlns");
-  if (ns && !strcmp(ns, NS_ROSTER))
-    handle_iq_roster(NULL, c, m, user_data);
-
-  // Post-login stuff
-  hook_execute_internal("hook-post-connect");
-
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static LmHandlerResult cb_version(LmMessageHandler *h, LmConnection *c,
-                                  LmMessage *m, gpointer user_data)
-{
-  LmMessageNode *ansqry;
-  const char *p, *bjid;
-  char *tmp;
-  char *buf;
-
-  ansqry = lm_message_node_get_child(m->node, "query");
-  if (!ansqry) {
-    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result!");
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-
-  // Display IQ result sender...
-  p = lm_message_get_from(m);
-  if (!p) {
-    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:version result (no sender name).");
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-  bjid = p;
-
-  buf = g_strdup_printf("Received IQ:version result from <%s>", bjid);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-
-  // bjid should now really be the "bare JID", let's strip the resource
-  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
-  if (tmp) *tmp = '\0';
-
-  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
-  g_free(buf);
-
-  // Get result data...
-  p = lm_message_node_get_child_value(ansqry, "name");
-  if (p) {
-    buf = g_strdup_printf("Name:    %s", p);
-    scr_WriteIncomingMessage(bjid, buf,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-    g_free(buf);
-  }
-  p = lm_message_node_get_child_value(ansqry, "version");
-  if (p) {
-    buf = g_strdup_printf("Version: %s", p);
-    scr_WriteIncomingMessage(bjid, buf,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-    g_free(buf);
-  }
-  p = lm_message_node_get_child_value(ansqry, "os");
-  if (p) {
-    buf = g_strdup_printf("OS:      %s", p);
-    scr_WriteIncomingMessage(bjid, buf,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-    g_free(buf);
-  }
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static LmHandlerResult cb_time(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer user_data)
-{
-  LmMessageNode *ansqry;
-  const char *p, *bjid;
-  char *tmp;
-  char *buf;
-
-  ansqry = lm_message_node_get_child(m->node, "query");
-  if (!ansqry) {
-    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result!");
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-  // Display IQ result sender...
-  p = lm_message_get_from(m);
-  if (!p) {
-    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:time result (no sender name).");
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-  bjid = p;
-
-  buf = g_strdup_printf("Received IQ:time result from <%s>", bjid);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-
-  // bjid should now really be the "bare JID", let's strip the resource
-  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
-  if (tmp) *tmp = '\0';
-
-  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
-  g_free(buf);
-
-  // Get result data...
-  p = lm_message_node_get_child_value(ansqry, "utc");
-  if (p) {
-    buf = g_strdup_printf("UTC:  %s", p);
-    scr_WriteIncomingMessage(bjid, buf,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-    g_free(buf);
-  }
-  p = lm_message_node_get_child_value(ansqry, "tz");
-  if (p) {
-    buf = g_strdup_printf("TZ:   %s", p);
-    scr_WriteIncomingMessage(bjid, buf,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-    g_free(buf);
-  }
-  p = lm_message_node_get_child_value(ansqry, "display");
-  if (p) {
-    buf = g_strdup_printf("Time: %s", p);
-    scr_WriteIncomingMessage(bjid, buf,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-    g_free(buf);
-  }
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static LmHandlerResult cb_last(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer user_data)
-{
-  LmMessageNode *ansqry;
-  const char *p, *bjid;
-  char *buf, *tmp;
-
-  ansqry = lm_message_node_get_child(m->node, "query");
-  if (!ansqry) {
-    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result!");
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-  // Display IQ result sender...
-  p = lm_message_get_from(m);
-  if (!p) {
-    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:last result (no sender name).");
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-  bjid = p;
-
-  buf = g_strdup_printf("Received IQ:last result from <%s>", bjid);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-
-  // bjid should now really be the "bare JID", let's strip the resource
-  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
-  if (tmp) *tmp = '\0';
-
-  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
-  g_free(buf);
-
-  // Get result data...
-  p = lm_message_node_get_attribute(ansqry, "seconds");
-  if (p) {
-    long int s;
-    GString *sbuf;
-    sbuf = g_string_new("Idle time: ");
-    s = atol(p);
-    // Days
-    if (s > 86400L) {
-      g_string_append_printf(sbuf, "%ldd ", s/86400L);
-      s %= 86400L;
-    }
-    // hh:mm:ss
-    g_string_append_printf(sbuf, "%02ld:", s/3600L);
-    s %= 3600L;
-    g_string_append_printf(sbuf, "%02ld:%02ld", s/60L, s%60L);
-    scr_WriteIncomingMessage(bjid, sbuf->str,
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-    g_string_free(sbuf, TRUE);
-  } else {
-    scr_WriteIncomingMessage(bjid, "No idle time reported.",
-                             0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-  }
-  p = lm_message_node_get_value(ansqry);
-  if (p) {
-    buf = g_strdup_printf("Status message: %s", p);
-    scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
-    g_free(buf);
-  }
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void display_vcard_item(const char *bjid, const char *label,
-                               enum vcard_attr vcard_attrib, const char *text)
-{
-  char *buf;
-
-  if (!text || !bjid || !label)
-    return;
-
-  buf = g_strdup_printf("%s: %s%s%s%s%s%s%s%s%s%s", label,
-                        (vcard_attrib & vcard_home ? "[home]" : ""),
-                        (vcard_attrib & vcard_work ? "[work]" : ""),
-                        (vcard_attrib & vcard_postal ? "[postal]" : ""),
-                        (vcard_attrib & vcard_voice ? "[voice]" : ""),
-                        (vcard_attrib & vcard_fax  ? "[fax]"  : ""),
-                        (vcard_attrib & vcard_cell ? "[cell]" : ""),
-                        (vcard_attrib & vcard_inet ? "[inet]" : ""),
-                        (vcard_attrib & vcard_pref ? "[pref]" : ""),
-                        (vcard_attrib ? " " : ""),
-                        text);
-  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
-  g_free(buf);
-}
-
-static void handle_vcard_node(const char *barejid, LmMessageNode *vcardnode)
-{
-  LmMessageNode *x;
-  const char *p;
-
-  for (x = vcardnode->children ; x; x = x->next) {
-    const char *data;
-    enum vcard_attr vcard_attrib = 0;
-
-    p = x->name;
-    data = lm_message_node_get_value(x);
-    if (!p || !data)
-      continue;
-
-    if (!strcmp(p, "FN"))
-      display_vcard_item(barejid, "Name", vcard_attrib, data);
-    else if (!strcmp(p, "NICKNAME"))
-      display_vcard_item(barejid, "Nickname", vcard_attrib, data);
-    else if (!strcmp(p, "URL"))
-      display_vcard_item(barejid, "URL", vcard_attrib, data);
-    else if (!strcmp(p, "BDAY"))
-      display_vcard_item(barejid, "Birthday", vcard_attrib, data);
-    else if (!strcmp(p, "TZ"))
-      display_vcard_item(barejid, "Timezone", vcard_attrib, data);
-    else if (!strcmp(p, "TITLE"))
-      display_vcard_item(barejid, "Title", vcard_attrib, data);
-    else if (!strcmp(p, "ROLE"))
-      display_vcard_item(barejid, "Role", vcard_attrib, data);
-    else if (!strcmp(p, "DESC"))
-      display_vcard_item(barejid, "Comment", vcard_attrib, data);
-    else if (!strcmp(p, "N")) {
-      data = lm_message_node_get_child_value(x, "FAMILY");
-      display_vcard_item(barejid, "Family Name", vcard_attrib, data);
-      data = lm_message_node_get_child_value(x, "GIVEN");
-      display_vcard_item(barejid, "Given Name", vcard_attrib, data);
-      data = lm_message_node_get_child_value(x, "MIDDLE");
-      display_vcard_item(barejid, "Middle Name", vcard_attrib, data);
-    } else if (!strcmp(p, "ORG")) {
-      data = lm_message_node_get_child_value(x, "ORGNAME");
-      display_vcard_item(barejid, "Organisation name", vcard_attrib, data);
-      data = lm_message_node_get_child_value(x, "ORGUNIT");
-      display_vcard_item(barejid, "Organisation unit", vcard_attrib, data);
-    } else {
-      // The HOME, WORK and PREF attributes are common to the remaining fields
-      // (ADR, TEL & EMAIL)
-      if (lm_message_node_get_child(x, "HOME"))
-        vcard_attrib |= vcard_home;
-      if (lm_message_node_get_child(x, "WORK"))
-        vcard_attrib |= vcard_work;
-      if (lm_message_node_get_child(x, "PREF"))
-        vcard_attrib |= vcard_pref;
-      if (!strcmp(p, "ADR")) {          // Address
-        if (lm_message_node_get_child(x, "POSTAL"))
-          vcard_attrib |= vcard_postal;
-        data = lm_message_node_get_child_value(x, "EXTADD");
-        display_vcard_item(barejid, "Addr (ext)", vcard_attrib, data);
-        data = lm_message_node_get_child_value(x, "STREET");
-        display_vcard_item(barejid, "Street", vcard_attrib, data);
-        data = lm_message_node_get_child_value(x, "LOCALITY");
-        display_vcard_item(barejid, "Locality", vcard_attrib, data);
-        data = lm_message_node_get_child_value(x, "REGION");
-        display_vcard_item(barejid, "Region", vcard_attrib, data);
-        data = lm_message_node_get_child_value(x, "PCODE");
-        display_vcard_item(barejid, "Postal code", vcard_attrib, data);
-        data = lm_message_node_get_child_value(x, "CTRY");
-        display_vcard_item(barejid, "Country", vcard_attrib, data);
-      } else if (!strcmp(p, "TEL")) {   // Telephone
-        data = lm_message_node_get_child_value(x, "NUMBER");
-        if (data) {
-          if (lm_message_node_get_child(x, "VOICE"))
-            vcard_attrib |= vcard_voice;
-          if (lm_message_node_get_child(x, "FAX"))
-            vcard_attrib |= vcard_fax;
-          if (lm_message_node_get_child(x, "CELL"))
-            vcard_attrib |= vcard_cell;
-          display_vcard_item(barejid, "Phone", vcard_attrib, data);
-        }
-      } else if (!strcmp(p, "EMAIL")) { // Email
-        if (lm_message_node_get_child(x, "INTERNET"))
-          vcard_attrib |= vcard_inet;
-        data = lm_message_node_get_child_value(x, "USERID");
-        display_vcard_item(barejid, "Email", vcard_attrib, data);
-      }
-    }
-  }
-}
-
-static LmHandlerResult cb_vcard(LmMessageHandler *h, LmConnection *c,
-                               LmMessage *m, gpointer user_data)
-{
-  LmMessageNode *ansqry;
-  const char *p, *bjid;
-  char *buf, *tmp;
-
-  // Display IQ result sender...
-  p = lm_message_get_from(m);
-  if (!p) {
-    scr_LogPrint(LPRINT_LOGNORM, "Invalid IQ:vCard result (no sender name).");
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
-  }
-  bjid = p;
-
-  buf = g_strdup_printf("Received IQ:vCard result from <%s>", bjid);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-
-  // Get the vCard node
-  ansqry = lm_message_node_get_child(m->node, "vCard");
-  if (!ansqry) {
-    scr_LogPrint(LPRINT_LOGNORM, "Empty IQ:vCard result!");
-    g_free(buf);
-    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-  }
-
-  // bjid should really be the "bare JID", let's strip the resource
-  tmp = strchr(bjid, JID_RESOURCE_SEPARATOR);
-  if (tmp) *tmp = '\0';
-
-  scr_WriteIncomingMessage(bjid, buf, 0, HBB_PREFIX_INFO, 0);
-  g_free(buf);
-
-  // Get result data...
-  handle_vcard_node(bjid, ansqry);
-  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-}
-
-static void storage_bookmarks_parse_conference(LmMessageNode *node)
-{
-  const char *fjid, *name, *autojoin;
-  const char *pstatus, *awhois;
-  char *bjid;
-  GSList *room_elt;
-
-  fjid = lm_message_node_get_attribute(node, "jid");
-  if (!fjid)
-    return;
-  name = lm_message_node_get_attribute(node, "name");
-  autojoin = lm_message_node_get_attribute(node, "autojoin");
-  awhois = lm_message_node_get_attribute(node, "autowhois");
-  pstatus = lm_message_node_get_child_value(node, "print_status");
-
-  bjid = jidtodisp(fjid); // Bare jid
-
-  // Make sure this is a room (it can be a conversion user->room)
-  room_elt = roster_find(bjid, jidsearch, 0);
-  if (!room_elt) {
-    room_elt = roster_add_user(bjid, name, NULL, ROSTER_TYPE_ROOM,
-                               sub_none, -1);
-  } else {
-    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
-    /*
-    // If the name is available, should we use it?
-    // I don't think so, it would be confusing because this item is already
-    // in the roster.
-    if (name)
-      buddy_setname(room_elt->data, name);
-    */
-  }
-
-  // Set the print_status and auto_whois values
-  if (pstatus) {
-    enum room_printstatus i;
-    for (i = status_none; i <= status_all; i++)
-      if (!strcasecmp(pstatus, strprintstatus[i]))
-        break;
-    if (i <= status_all)
-      buddy_setprintstatus(room_elt->data, i);
-  }
-  if (awhois) {
-    enum room_autowhois i = autowhois_default;
-    if (!strcmp(awhois, "1"))
-      i = autowhois_on;
-    else if (!strcmp(awhois, "0"))
-      i = autowhois_off;
-    if (i != autowhois_default)
-      buddy_setautowhois(room_elt->data, i);
-  }
-
-  // Is autojoin set?
-  // If it is, we'll look up for more information (nick? password?) and
-  // try to join the room.
-  if (autojoin && !strcmp(autojoin, "1")) {
-    const char *nick, *passwd;
-    char *tmpnick = NULL;
-    nick = lm_message_node_get_child_value(node, "nick");
-    passwd = lm_message_node_get_child_value(node, "password");
-    if (!nick || !*nick)
-      nick = tmpnick = default_muc_nickname(NULL);
-    // Let's join now
-    scr_LogPrint(LPRINT_LOGNORM, "Auto-join bookmark <%s>", bjid);
-    xmpp_room_join(bjid, nick, passwd);
-    g_free(tmpnick);
-  }
-  g_free(bjid);
-}
-
-static LmHandlerResult cb_storage_bookmarks(LmMessageHandler *h,
-                                            LmConnection *c,
-                                            LmMessage *m, gpointer user_data)
-{
-  LmMessageNode *x, *ansqry;
-  char *p;
-
-  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) {
-    // No server support, or no bookmarks?
-    p = m->node->children->name;
-    if (p && !strcmp(p, "item-not-found")) {
-      // item-no-found means the server has Private Storage, but it's
-      // currently empty.
-      if (bookmarks)
-        lm_message_node_unref(bookmarks);
-      bookmarks = lm_message_node_new("storage", "storage:bookmarks");
-      // We return 0 so that the IQ error message be
-      // not displayed, as it isn't a real error.
-      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-    }
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error
-  }
-
-  ansqry = lm_message_node_get_child(m->node, "query");
-  ansqry = lm_message_node_get_child(ansqry, "storage");
-  if (!ansqry) {
-    scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! (storage:bookmarks)");
-    return 0;
-  }
-
-  // Walk through the storage tags
-  for (x = ansqry->children ; x; x = x->next) {
-    // If the current node is a conference item, parse it and update the roster
-    if (x->name && !strcmp(x->name, "conference"))
-      storage_bookmarks_parse_conference(x);
-  }
-  // "Copy" the bookmarks node
-  if (bookmarks)
-    lm_message_node_unref(bookmarks);
-  lm_message_node_deep_ref(ansqry);
-  bookmarks = ansqry;
-  return 0;
-}
-
-
-static LmHandlerResult cb_storage_rosternotes(LmMessageHandler *h,
-                                              LmConnection *c,
-                                              LmMessage *m, gpointer user_data)
-{
-  LmMessageNode *ansqry;
-
-  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) {
-    const char *p;
-    // No server support, or no roster notes?
-    p = m->node->children->name;
-    if (p && !strcmp(p, "item-not-found")) {
-      // item-no-found means the server has Private Storage, but it's
-      // currently empty.
-      if (rosternotes)
-        lm_message_node_unref(rosternotes);
-      rosternotes = lm_message_node_new("storage", "storage:rosternotes");
-      // We return 0 so that the IQ error message be
-      // not displayed, as it isn't a real error.
-      return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-    }
-    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; // Unhandled error
-  }
-
-  ansqry = lm_message_node_get_child(m->node, "query");
-  ansqry = lm_message_node_get_child(ansqry, "storage");
-  if (!ansqry) {
-    scr_LogPrint(LPRINT_LOG, "Invalid IQ:private result! "
-                 "(storage:rosternotes)");
-    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
-  }
-  // Copy the rosternotes node
-  if (rosternotes)
-    lm_message_node_unref(rosternotes);
-  lm_message_node_deep_ref(ansqry);
-  rosternotes = ansqry;
-  return 0;
-}
-
-
-static struct IqRequestStorageHandlers
-{
-  const gchar *storagens;
-  LmHandleMessageFunction handler;
-} iq_request_storage_handlers[] = {
-  {"storage:rosternotes", &cb_storage_rosternotes},
-  {"storage:bookmarks", &cb_storage_bookmarks},
-  {NULL, NULL}
-};
-
-void xmpp_request_storage(const gchar *storage)
-{
-  LmMessage *iq;
-  LmMessageNode *query;
-  LmMessageHandler *handler;
-  int i;
-
-  iq = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_GET);
-  query = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns", NS_PRIVATE);
-  lm_message_node_set_attribute(lm_message_node_add_child
-                                (query, "storage", NULL),
-                                "xmlns", storage);
-
-  for (i = 0;
-       strcmp(iq_request_storage_handlers[i].storagens, storage) != 0;
-       ++i) ;
-
-  handler = lm_message_handler_new(iq_request_storage_handlers[i].handler,
-                                   NULL, FALSE);
-  lm_connection_send_with_reply(lconnection, iq, handler, NULL);
-  lm_message_handler_unref(handler);
-  lm_message_unref(iq);
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_iqrequest.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-#ifndef __XMPP_IQREQUEST_H__
-#define __XMPP_IQREQUEST_H__ 1
-
-void xmpp_iq_request(const char *fulljid, const char *xmlns);
-
-#endif /* __XMPP_IQREQUEST_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_muc.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,715 +0,0 @@
-/*
- * xmpp_muc.c   -- Jabber MUC protocol handling
- *
- * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include <string.h>
-#include <stdlib.h>
-
-#include "xmpp_helper.h"
-#include "events.h"
-#include "hooks.h"
-#include "screen.h"
-#include "hbuf.h"
-#include "roster.h"
-#include "commands.h"
-#include "settings.h"
-#include "utils.h"
-#include "histolog.h"
-
-extern enum imstatus mystatus;
-extern gchar *mystatusmsg;
-
-static void decline_invitation(event_muc_invitation *invitation, char *reason)
-{
-  // cut and paste from xmpp_room_invite
-  LmMessage *m;
-  LmMessageNode *x, *y;
-
-  if (!invitation) return;
-  if (!invitation->to || !invitation->from) return;
-
-  m = lm_message_new(invitation->to, LM_MESSAGE_TYPE_MESSAGE);
-
-  x = lm_message_node_add_child(m->node, "x", NULL);
-  lm_message_node_set_attribute(x, "xmlns",
-                                "http://jabber.org/protocol/muc#user");
-
-  y = lm_message_node_add_child(x, "decline", NULL);
-  lm_message_node_set_attribute(y, "to", invitation->from);
-
-  if (reason)
-    lm_message_node_add_child(y, "reason", reason);
-
-  lm_connection_send(lconnection, m, NULL);
-  lm_message_unref(m);
-}
-
-static int evscallback_invitation(eviqs *evp, guint evcontext)
-{
-  event_muc_invitation *invitation = evp->data;
-
-  // Sanity check
-  if (!invitation) {
-    // Shouldn't happen.
-    scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback.");
-    return 0;
-  }
-
-  if (evcontext == EVS_CONTEXT_TIMEOUT) {
-    scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.", evp->id);
-    goto evscallback_invitation_free;
-  }
-  if (evcontext == EVS_CONTEXT_CANCEL) {
-    scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id);
-    goto evscallback_invitation_free;
-  }
-  if (!(evcontext & EVS_CONTEXT_USER))
-    goto evscallback_invitation_free;
-  // Ok, let's work now.
-  // evcontext: 0, 1 == reject, accept
-
-  if (evcontext & ~EVS_CONTEXT_USER) {
-    char *nickname = default_muc_nickname(invitation->to);
-    xmpp_room_join(invitation->to, nickname, invitation->passwd);
-    g_free(nickname);
-  } else {
-    scr_LogPrint(LPRINT_LOGNORM, "Invitation to %s refused.", invitation->to);
-    decline_invitation(invitation, NULL);
-  }
-
-evscallback_invitation_free:
-  g_free(invitation->to);
-  g_free(invitation->from);
-  g_free(invitation->passwd);
-  g_free(invitation->reason);
-  g_free(invitation);
-  evp->data = NULL;
-  return 0;
-}
-
-// Join a MUC room
-void xmpp_room_join(const char *room, const char *nickname, const char *passwd)
-{
-  LmMessage *x;
-  LmMessageNode *y;
-  gchar *roomid;
-  GSList *room_elt;
-
-  if (!lm_connection_is_authenticated(lconnection) || !room) return;
-  if (!nickname)        return;
-
-  roomid = g_strdup_printf("%s/%s", room, nickname);
-  if (check_jid_syntax(roomid)) {
-    scr_LogPrint(LPRINT_NORMAL, "<%s/%s> is not a valid Jabber room", room,
-                 nickname);
-    g_free(roomid);
-    return;
-  }
-
-  room_elt = roster_find(room, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
-  // Add room if it doesn't already exist
-  if (!room_elt) {
-    room_elt = roster_add_user(room, NULL, NULL, ROSTER_TYPE_ROOM,
-                               sub_none, -1);
-  } else {
-    // Make sure this is a room (it can be a conversion user->room)
-    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
-  }
-  // If insideroom is TRUE, this is a nickname change and we don't care here
-  if (!buddy_getinsideroom(room_elt->data)) {
-    // We're trying to enter a room
-    buddy_setnickname(room_elt->data, nickname);
-  }
-
-  // Send the XML request
-  lm_message_new(roomid, LM_MESSAGE_TYPE_PRESENCE);
-
-  x = lm_message_new_presence(mystatus, roomid, mystatusmsg);
-  y = lm_message_node_add_child(x->node, "x", NULL);
-  lm_message_node_set_attribute(y, "xmlns", "http://jabber.org/protocol/muc");
-  if (passwd)
-    lm_message_node_add_child(y, "password", passwd);
-
-  lm_connection_send(lconnection, x, NULL);
-  lm_message_unref(x);
-  g_free(roomid);
-}
-
-// Invite a user to a MUC room
-// room syntax: "room@server"
-// reason can be null.
-void xmpp_room_invite(const char *room, const char *fjid, const char *reason)
-{
-  LmMessage *msg;
-  LmMessageNode *x, *y;
-
-  if (!lm_connection_is_authenticated(lconnection) || !room || !fjid) return;
-
-  msg = lm_message_new(room, LM_MESSAGE_TYPE_MESSAGE);
-
-  x = lm_message_node_add_child(msg->node, "x", NULL);
-  lm_message_node_set_attribute(x, "xmlns",
-                                "http://jabber.org/protocol/muc#user");
-
-  y = lm_message_node_add_child(x, "invite", NULL);
-  lm_message_node_set_attribute(y, "to", fjid);
-
-  if (reason)
-    lm_message_node_add_child(y, "reason", reason);
-
-  lm_connection_send(lconnection, msg, NULL);
-  lm_message_unref(msg);
-}
-
-int xmpp_room_setattrib(const char *roomid, const char *fjid,
-                        const char *nick, struct role_affil ra,
-                        const char *reason)
-{
-  LmMessage *iq;
-  LmMessageNode *query, *x;
-
-  if (!lm_connection_is_authenticated(lconnection) || !roomid) return 1;
-  if (!fjid && !nick) return 1;
-
-  if (check_jid_syntax((char*)roomid)) {
-    scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", roomid);
-    return 1;
-  }
-  if (fjid && check_jid_syntax((char*)fjid)) {
-    scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber id", fjid);
-    return 1;
-  }
-
-  if (ra.type == type_affil && ra.val.affil == affil_outcast && !fjid)
-    return 1; // Shouldn't happen (jid mandatory when banning)
-
-  iq = lm_message_new_with_sub_type(roomid, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_SET);
-  query = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns",
-                                "http://jabber.org/protocol/muc#admin");
-  x = lm_message_node_add_child(query, "item", NULL);
-
-  if (fjid) {
-    lm_message_node_set_attribute(x, "jid", fjid);
-  } else { // nickname
-    lm_message_node_set_attribute(x, "nick", nick);
-  }
-
-  if (ra.type == type_affil)
-    lm_message_node_set_attribute(x, "affiliation", straffil[ra.val.affil]);
-  else if (ra.type == type_role)
-    lm_message_node_set_attribute(x, "role", strrole[ra.val.role]);
-
-  if (reason)
-    lm_message_node_add_child(x, "reason", reason);
-
-  lm_connection_send(lconnection, iq, NULL);
-  lm_message_unref(iq);
-
-  return 0;
-}
-
-// Unlock a MUC room
-// room syntax: "room@server"
-void xmpp_room_unlock(const char *room)
-{
-  LmMessageNode *node;
-  LmMessage *iq;
-
-  if (!lm_connection_is_authenticated(lconnection) || !room) return;
-
-  iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_SET);
-
-  node = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(node, "xmlns",
-                                "http://jabber.org/protocol/muc#owner");
-  node = lm_message_node_add_child(node, "x", NULL);
-  lm_message_node_set_attributes(node, "xmlns", "jabber:x:data",
-                                 "type", "submit", NULL);
-
-  lm_connection_send(lconnection, iq, NULL);
-  lm_message_unref(iq);
-}
-
-// Destroy a MUC room
-// room syntax: "room@server"
-void xmpp_room_destroy(const char *room, const char *venue, const char *reason)
-{
-  LmMessage *iq;
-  LmMessageNode *query, *x;
-
-  if (!lm_connection_is_authenticated(lconnection) || !room) return;
-
-  iq = lm_message_new_with_sub_type(room, LM_MESSAGE_TYPE_IQ,
-                                    LM_MESSAGE_SUB_TYPE_SET);
-  query = lm_message_node_add_child(iq->node, "query", NULL);
-  lm_message_node_set_attribute(query, "xmlns",
-                                "http://jabber.org/protocol/muc#owner");
-  x = lm_message_node_add_child(query, "destroy", NULL);
-
-  if (venue && *venue)
-    lm_message_node_set_attribute(x, "jid", venue);
-
-  if (reason)
-    lm_message_node_add_child(x, "reason", reason);
-
-  lm_connection_send(lconnection, iq, NULL);
-  lm_message_unref(iq);
-}
-
-//  muc_get_item_info(...)
-// Get room member's information from xmlndata.
-// The variables must be initialized before calling this function,
-// because they are not touched if the relevant information is missing.
-static void muc_get_item_info(const char *from, LmMessageNode *xmldata,
-                              enum imrole *mbrole, enum imaffiliation *mbaffil,
-                              const char **mbjid, const char **mbnick,
-                              const char **actorjid, const char **reason)
-{
-  LmMessageNode *y, *z;
-  const char *p;
-
-  y = lm_message_node_find_child(xmldata, "item");
-  if (!y)
-    return;
-
-  p = lm_message_node_get_attribute(y, "affiliation");
-  if (p) {
-    if (!strcmp(p, "owner"))        *mbaffil = affil_owner;
-    else if (!strcmp(p, "admin"))   *mbaffil = affil_admin;
-    else if (!strcmp(p, "member"))  *mbaffil = affil_member;
-    else if (!strcmp(p, "outcast")) *mbaffil = affil_outcast;
-    else if (!strcmp(p, "none"))    *mbaffil = affil_none;
-    else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown affiliation \"%s\"",
-                      from, p);
-  }
-  p = lm_message_node_get_attribute(y, "role");
-  if (p) {
-    if (!strcmp(p, "moderator"))        *mbrole = role_moderator;
-    else if (!strcmp(p, "participant")) *mbrole = role_participant;
-    else if (!strcmp(p, "visitor"))     *mbrole = role_visitor;
-    else if (!strcmp(p, "none"))        *mbrole = role_none;
-    else scr_LogPrint(LPRINT_LOGNORM, "<%s>: Unknown role \"%s\"",
-                      from, p);
-  }
-  *mbjid = lm_message_node_get_attribute(y, "jid");
-  *mbnick = lm_message_node_get_attribute(y, "nick");
-  // For kick/ban, there can be actor and reason tags
-  *reason = lm_message_node_get_child_value(y, "reason");
-  z = lm_message_node_find_child(y, "actor");
-  if (z)
-    *actorjid = lm_message_node_get_attribute(z, "jid");
-}
-
-//  muc_handle_join(...)
-// Handle a join event in a MUC room.
-// This function will return the new_member value TRUE if somebody else joins
-// the room (and FALSE if _we_ are joining the room).
-static bool muc_handle_join(const GSList *room_elt, const char *rname,
-                            const char *roomjid, const char *ournick,
-                            enum room_printstatus printstatus,
-                            time_t usttime, int log_muc_conf)
-{
-  bool new_member = FALSE; // True if somebody else joins the room (not us)
-  gchar *mbuf;
-
-  if (!buddy_getinsideroom(room_elt->data)) {
-    // We weren't inside the room yet.  Now we are.
-    // However, this could be a presence packet from another room member
-
-    buddy_setinsideroom(room_elt->data, TRUE);
-    // Set the message flag unless we're already in the room buffer window
-    scr_setmsgflag_if_needed(roomjid, FALSE);
-    // Add a message to the tracelog file
-    mbuf = g_strdup_printf("You have joined %s as \"%s\"", roomjid, ournick);
-    scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
-    g_free(mbuf);
-    mbuf = g_strdup_printf("You have joined as \"%s\"", ournick);
-
-    // The 1st presence message could be for another room member
-    if (strcmp(ournick, rname)) {
-      // Display current mbuf and create a new message for the member
-      // Note: the usttime timestamp is related to the other member,
-      //       so we use 0 here.
-      scr_WriteIncomingMessage(roomjid, mbuf, 0,
-                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
-      if (log_muc_conf)
-        hlog_write_message(roomjid, 0, -1, mbuf);
-      g_free(mbuf);
-      if (printstatus != status_none)
-        mbuf = g_strdup_printf("%s has joined", rname);
-      else
-        mbuf = NULL;
-      new_member = TRUE;
-    }
-  } else {
-    mbuf = NULL;
-    if (strcmp(ournick, rname)) {
-      if (printstatus != status_none)
-        mbuf = g_strdup_printf("%s has joined", rname);
-      new_member = TRUE;
-    }
-  }
-
-  if (mbuf) {
-    guint msgflags = HBB_PREFIX_INFO;
-    if (!settings_opt_get_int("muc_flag_joins"))
-      msgflags |= HBB_PREFIX_NOFLAG;
-    scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0);
-    if (log_muc_conf)
-      hlog_write_message(roomjid, 0, -1, mbuf);
-    g_free(mbuf);
-  }
-
-  return new_member;
-}
-
-void handle_muc_presence(const char *from, LmMessageNode *xmldata,
-                         const char *roomjid, const char *rname,
-                         enum imstatus ust, const char *ustmsg,
-                         time_t usttime, char bpprio)
-{
-  LmMessageNode *y;
-  const char *p;
-  char *mbuf;
-  const char *ournick;
-  enum imrole mbrole = role_none;
-  enum imaffiliation mbaffil = affil_none;
-  enum room_printstatus printstatus;
-  enum room_autowhois autowhois;
-  const char *mbjid = NULL, *mbnick = NULL;
-  const char *actorjid = NULL, *reason = NULL;
-  bool new_member = FALSE; // True if somebody else joins the room (not us)
-  guint statuscode = 0;
-  guint nickchange = 0;
-  GSList *room_elt;
-  int log_muc_conf;
-  guint msgflags;
-
-  log_muc_conf = settings_opt_get_int("log_muc_conf");
-
-  room_elt = roster_find(roomjid, jidsearch, 0);
-  if (!room_elt) {
-    // Add room if it doesn't already exist
-    // It shouldn't happen, there is probably something wrong (server or
-    // network issue?)
-    room_elt = roster_add_user(roomjid, NULL, NULL, ROSTER_TYPE_ROOM,
-                               sub_none, -1);
-    scr_LogPrint(LPRINT_LOGNORM, "Strange MUC presence message");
-  } else {
-    // Make sure this is a room (it can be a conversion user->room)
-    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
-  }
-
-  // Get room member's information
-  muc_get_item_info(from, xmldata, &mbrole, &mbaffil, &mbjid, &mbnick,
-                    &actorjid, &reason);
-
-  // Get our room nickname
-  ournick = buddy_getnickname(room_elt->data);
-
-  if (!ournick) {
-    // It shouldn't happen, probably a server issue
-    mbuf = g_strdup_printf("Unexpected groupchat packet!");
-
-    scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
-    scr_WriteIncomingMessage(roomjid, mbuf, 0, HBB_PREFIX_INFO, 0);
-    g_free(mbuf);
-    // Send back an unavailable packet
-    xmpp_setstatus(offline, roomjid, "", TRUE);
-    scr_DrawRoster();
-    return;
-  }
-
-  // Get the status code
-  // 201: a room has been created
-  // 301: the user has been banned from the room
-  // 303: new room nickname
-  // 307: the user has been kicked from the room
-  // 321,322,332: the user has been removed from the room
-  y = lm_message_node_find_child(xmldata, "status");
-  if (y) {
-    p = lm_message_node_get_attribute(y, "code");
-    if (p)
-      statuscode = atoi(p);
-  }
-
-  // Get the room's "print_status" settings
-  printstatus = buddy_getprintstatus(room_elt->data);
-  if (printstatus == status_default) {
-    printstatus = (guint) settings_opt_get_int("muc_print_status");
-    if (printstatus > 3)
-      printstatus = status_default;
-  }
-
-  // A new room has been created; accept MUC default config
-  if (statuscode == 201)
-    xmpp_room_unlock(roomjid);
-
-  // Check for nickname change
-  if (statuscode == 303 && mbnick) {
-    mbuf = g_strdup_printf("%s is now known as %s", rname, mbnick);
-    scr_WriteIncomingMessage(roomjid, mbuf, usttime,
-                             HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
-    if (log_muc_conf)
-      hlog_write_message(roomjid, 0, -1, mbuf);
-    g_free(mbuf);
-    buddy_resource_setname(room_elt->data, rname, mbnick);
-    // Maybe it's _our_ nickname...
-    if (ournick && !strcmp(rname, ournick))
-      buddy_setnickname(room_elt->data, mbnick);
-    nickchange = TRUE;
-  }
-
-  // Check for departure/arrival
-  if (!mbnick && ust == offline) {
-    // Somebody is leaving
-    enum { leave=0, kick, ban } how = leave;
-    bool we_left = FALSE;
-
-    if (statuscode == 307)
-      how = kick;
-    else if (statuscode == 301)
-      how = ban;
-
-    // If this is a leave, check if it is ourself
-    if (ournick && !strcmp(rname, ournick)) {
-      we_left = TRUE; // _We_ have left! (kicked, banned, etc.)
-      buddy_setinsideroom(room_elt->data, FALSE);
-      buddy_setnickname(room_elt->data, NULL);
-      buddy_del_all_resources(room_elt->data);
-      buddy_settopic(room_elt->data, NULL);
-      scr_UpdateChatStatus(FALSE);
-      update_roster = TRUE;
-    }
-
-    // The message depends on _who_ left, and _how_
-    if (how) {
-      gchar *mbuf_end;
-      // Forced leave
-      if (actorjid) {
-        mbuf_end = g_strdup_printf("%s from %s by <%s>.\nReason: %s",
-                                   (how == ban ? "banned" : "kicked"),
-                                   roomjid, actorjid, reason);
-      } else {
-        mbuf_end = g_strdup_printf("%s from %s.",
-                                   (how == ban ? "banned" : "kicked"),
-                                   roomjid);
-      }
-      if (we_left)
-        mbuf = g_strdup_printf("You have been %s", mbuf_end);
-      else
-        mbuf = g_strdup_printf("%s has been %s", rname, mbuf_end);
-
-      g_free(mbuf_end);
-    } else {
-      // Natural leave
-      if (we_left) {
-        LmMessageNode *destroynode = lm_message_node_find_child(xmldata,
-                                                                "destroy");
-        if (destroynode) {
-          if ((reason = lm_message_node_get_child_value(destroynode,
-                                                       "reason"))) {
-            mbuf = g_strdup_printf("You have left %s, "
-                                   "the room has been destroyed: %s",
-                                   roomjid, reason);
-          } else {
-            mbuf = g_strdup_printf("You have left %s, "
-                                   "the room has been destroyed", roomjid);
-          }
-        } else {
-          mbuf = g_strdup_printf("You have left %s", roomjid);
-        }
-      } else {
-        if (ust != offline) {
-          // This can happen when a network failure occurs,
-          // this isn't an official leave but the user isn't there anymore.
-          mbuf = g_strdup_printf("%s has disappeared!", rname);
-          ust = offline;
-        } else {
-          if (ustmsg)
-            mbuf = g_strdup_printf("%s has left: %s", rname, ustmsg);
-          else
-            mbuf = g_strdup_printf("%s has left", rname);
-        }
-      }
-    }
-
-    // Display the mbuf message if we're concerned
-    // or if the print_status isn't set to none.
-    if (we_left || printstatus != status_none) {
-      msgflags = HBB_PREFIX_INFO;
-      if (!we_left && settings_opt_get_int("muc_flag_joins") != 2)
-        msgflags |= HBB_PREFIX_NOFLAG;
-      scr_WriteIncomingMessage(roomjid, mbuf, usttime, msgflags, 0);
-    }
-
-    if (log_muc_conf)
-      hlog_write_message(roomjid, 0, -1, mbuf);
-
-    if (we_left) {
-      scr_LogPrint(LPRINT_LOGNORM, "%s", mbuf);
-      g_free(mbuf);
-      return;
-    }
-    g_free(mbuf);
-  } else if (buddy_getstatus(room_elt->data, rname) == offline &&
-             ust != offline) {
-    // Somebody is joining
-    new_member = muc_handle_join(room_elt, rname, roomjid, ournick,
-                                 printstatus, usttime, log_muc_conf);
-  } else {
-    // This is a simple member status change
-
-    if (printstatus == status_all && !nickchange) {
-      mbuf = g_strdup_printf("Member status has changed: %s [%c] %s", rname,
-                             imstatus2char[ust], ((ustmsg) ? ustmsg : ""));
-      scr_WriteIncomingMessage(roomjid, mbuf, usttime,
-                               HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
-      g_free(mbuf);
-    }
-  }
-
-  // Sanity check, shouldn't happen...
-  if (!rname)
-    return;
-
-  // Update room member status
-  roster_setstatus(roomjid, rname, bpprio, ust, ustmsg, usttime,
-                   mbrole, mbaffil, mbjid);
-
-  autowhois = buddy_getautowhois(room_elt->data);
-  if (autowhois == autowhois_default)
-    autowhois = (settings_opt_get_int("muc_auto_whois") ?
-                 autowhois_on : autowhois_off);
-
-  if (new_member && autowhois == autowhois_on) {
-    // FIXME: This will fail for some UTF-8 nicknames.
-    gchar *joiner_nick = from_utf8(rname);
-    cmd_room_whois(room_elt->data, joiner_nick, FALSE);
-    g_free(joiner_nick);
-  }
-
-  scr_DrawRoster();
-}
-
-void roompresence(gpointer room, void *presencedata)
-{
-  const char *bjid;
-  const char *nickname;
-  char *to;
-  struct T_presence *pres = presencedata;
-
-  if (!buddy_getinsideroom(room))
-    return;
-
-  bjid = buddy_getjid(room);
-  if (!bjid) return;
-  nickname = buddy_getnickname(room);
-  if (!nickname) return;
-
-  to = g_strdup_printf("%s/%s", bjid, nickname);
-  xmpp_setstatus(pres->st, to, pres->msg, TRUE);
-  g_free(to);
-}
-
-//  got_invite(from, to, reason, passwd)
-// This function should be called when receiving an invitation from user
-// "from", to enter the room "to".  Optional reason and room password can
-// be provided.
-static void got_invite(const char* from, const char *to, const char* reason,
-                       const char* passwd)
-{
-  eviqs *evn;
-  event_muc_invitation *invitation;
-  GString *sbuf;
-  char *barejid;
-  GSList *room_elt;
-
-  sbuf = g_string_new("");
-  if (reason) {
-    g_string_printf(sbuf,
-                    "Received an invitation to <%s>, from <%s>, reason: %s",
-                    to, from, reason);
-  } else {
-    g_string_printf(sbuf, "Received an invitation to <%s>, from <%s>",
-                    to, from);
-  }
-
-  barejid = jidtodisp(from);
-  scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str);
-
-  evn = evs_new(EVS_TYPE_INVITATION, EVS_MAX_TIMEOUT);
-  if (evn) {
-    evn->callback = &evscallback_invitation;
-    invitation = g_new(event_muc_invitation, 1);
-    invitation->to = g_strdup(to);
-    invitation->from = g_strdup(from);
-    invitation->passwd = g_strdup(passwd);
-    invitation->reason = g_strdup(reason);
-    evn->data = invitation;
-    evn->desc = g_strdup_printf("<%s> invites you to %s ", from, to);
-    g_string_printf(sbuf, "Please use /event %s accept|reject", evn->id);
-  } else {
-    g_string_printf(sbuf, "Unable to create a new event!");
-  }
-  scr_WriteIncomingMessage(barejid, sbuf->str, 0, HBB_PREFIX_INFO, 0);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", sbuf->str);
-  g_string_free(sbuf, TRUE);
-  g_free(barejid);
-
-  // Make sure the MUC room barejid is a room in the roster
-  barejid = jidtodisp(to);
-  room_elt = roster_find(barejid, jidsearch, 0);
-  if (room_elt)
-    buddy_settype(room_elt->data, ROSTER_TYPE_ROOM);
-
-  g_free(barejid);
-}
-
-
-// Specific MUC message handling (for example invitation processing)
-void got_muc_message(const char *from, LmMessageNode *x)
-{
-  LmMessageNode *invite = lm_message_node_get_child(x, "invite");
-  if (invite)
-  {
-    const char *invite_from;
-    const char *reason = NULL;
-    const char *password = NULL;
-
-    invite_from = lm_message_node_get_attribute(invite, "from");
-    reason = lm_message_node_get_child_value(invite, "reason");
-    password = lm_message_node_get_child_value(invite, "password");
-    if (invite_from)
-      got_invite(invite_from, from, reason, password);
-  }
-  // TODO
-  // handle status code = 100 ( not anonymous )
-  // handle status code = 170 ( changement de config )
-  // 10.2.1 Notification of Configuration Changes
-  // declined invitation
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_muc.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-#ifndef __XMPP_MUC_H__
-#define __XMPP_MUC_H__ 1
-
-void roompresence(gpointer room, void *presencedata);
-void got_muc_message(const char *from, LmMessageNode *x);
-void handle_muc_presence(const char *from, LmMessageNode * xmldata,
-                         const char *roomjid, const char *rname,
-                         enum imstatus ust, const char *ustmsg,
-                         time_t usttime, char bpprio);
-
-#endif /* __XMPP_MUC_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_s10n.c	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * xmpp_s10n.c  -- Jabber presence subscription handling
- *
- * Copyright (C) 2008-2009 Frank Zschockelt <mcabber@freakysoft.de>
- * Copyright (C) 2005-2009 Mikael Berthe <mikael@lilotux.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
- */
-
-#include "xmpp_helper.h"
-#include "events.h"
-#include "screen.h"
-#include "hbuf.h"
-#include "settings.h"
-
-//  xmpp_send_s10n(jid, subtype)
-// Send a s10n message with the passed subtype
-void xmpp_send_s10n(const char *bjid, LmMessageSubType type)
-{
-  LmMessage *x = lm_message_new_with_sub_type(bjid,
-                                              LM_MESSAGE_TYPE_PRESENCE,
-                                              type);
-  lm_connection_send(lconnection, x, NULL);
-  lm_message_unref(x);
-}
-
-int evscallback_subscription(eviqs *evp, guint evcontext)
-{
-  char *barejid;
-  char *buf;
-
-  if (evcontext == EVS_CONTEXT_TIMEOUT) {
-    scr_LogPrint(LPRINT_LOGNORM, "Event %s timed out, cancelled.",
-                 evp->id);
-    return 0;
-  }
-  if (evcontext == EVS_CONTEXT_CANCEL) {
-    scr_LogPrint(LPRINT_LOGNORM, "Event %s cancelled.", evp->id);
-    return 0;
-  }
-  if (!(evcontext & EVS_CONTEXT_USER))
-    return 0;
-
-  // Sanity check
-  if (!evp->data) {
-    // Shouldn't happen, data should be set to the barejid.
-    scr_LogPrint(LPRINT_LOGNORM, "Error in evs callback.");
-    return 0;
-  }
-
-  // Ok, let's work now.
-  // evcontext: 0, 1 == reject, accept
-
-  barejid = evp->data;
-
-  if (evcontext & ~EVS_CONTEXT_USER) {
-    // Accept subscription request
-    xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_SUBSCRIBED);
-    buf = g_strdup_printf("<%s> is allowed to receive your presence updates",
-                          barejid);
-  } else {
-    // Reject subscription request
-    xmpp_send_s10n(barejid, LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED);
-    buf = g_strdup_printf("<%s> won't receive your presence updates", barejid);
-    if (settings_opt_get_int("delete_on_reject")) {
-      // Remove the buddy from the roster if there is no current subscription
-      if (roster_getsubscription(barejid) == sub_none)
-        xmpp_delbuddy(barejid);
-    }
-  }
-  scr_WriteIncomingMessage(barejid, buf, 0, HBB_PREFIX_INFO, 0);
-  scr_LogPrint(LPRINT_LOGNORM, "%s", buf);
-  g_free(buf);
-  return 0;
-}
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */
--- a/mcabber/src/xmpp_s10n.h	Tue Feb 02 21:27:26 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-#ifndef __XMPP_S10N_H__
-#define __XMPP_S10N_H__ 1
-
-#include "events.h"
-
-int evscallback_subscription(eviqs *evp, guint evcontext);
-
-#endif /* __XMPP_S10N_H__ */
-
-/* vim: set expandtab cindent cinoptions=>2\:2(0:  For Vim users... */