diff mcabber/src/xmpp.c @ 1598:a087125d8fc8

Replace libjabber with loudmouth
author franky
date Sun, 11 Oct 2009 15:38:32 +0200
parents mcabber/src/jabglue.c@1802b926e3fa
children dcd5d4c75199
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mcabber/src/xmpp.c	Sun Oct 11 15:38:32 2009 +0200
@@ -0,0 +1,2154 @@
+/*
+ * xmpp.c    -- Jabber protocol handling
+ *
+ * Copyright (C) 2008 Frank Zschockelt <mcabber@freakysoft.de>
+ * Copyright (C) 2005-2008 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 <sys/utsname.h>
+
+#include "commands.h"
+#include "events.h"
+#include "histolog.h"
+#include "hooks.h"
+#include "logprint.h"
+#include "otr.h"
+#include "roster.h"
+#include "screen.h"
+#include "settings.h"
+#include "utils.h"
+#include "xmpp.h"
+#include "xmpp_helper.h"
+#include "xmpp_defines.h"
+
+#define RECONNECTION_TIMEOUT    60L
+
+LmConnection* lconnection;
+static guint AutoConnection;
+
+inline void update_last_use(void);
+inline gboolean xmpp_reconnect();
+
+static enum imstatus mystatus = offline;
+static enum imstatus mywantedstatus = available;
+static gchar *mystatusmsg;
+
+char imstatus2char[imstatus_size+1] = {
+    '_', 'o', 'f', 'd', 'n', 'a', 'i', '\0'
+};
+
+static char *imstatus_showmap[] = {
+  "",
+  "",
+  "chat",
+  "dnd",
+  "xa",
+  "away",
+  ""
+};
+
+static LmMessageNode *bookmarks = NULL;
+static LmMessageNode *rosternotes = NULL;
+
+struct xmpp_error {
+  guint code;
+  const char *code_str;
+  const char *meaning;
+  const char *condition;
+  const char *type;
+} 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}
+};
+
+#include "xmpp_helper.c"
+#include "xmpp_iq.c"
+#include "xmpp_iqrequest.c"
+#include "xmpp_muc.c"
+#include "xmpp_s10n.c"
+
+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},
+  {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);
+}
+
+//  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)
+{
+  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);
+  }
+
+#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();
+  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)
+{
+  const char *username, *password, *resource, *servername;
+  GError *error;
+
+  if (success) {
+    servername = settings_opt_get("server");
+    username   = settings_opt_get("username");
+    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);
+      _try_to_reconnect();
+    }
+  } 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) {
+      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;
+
+  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 (lm_message_get_sub_type(m) != 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 (lm_message_get_sub_type(m) == 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(lm_message_get_sub_type(m), from, body, enc, subject, timestamp,
+               lm_message_node_find_xmlns(m->node, NS_SIGNED));
+
+  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 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;
+
+  //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);
+
+  if (lm_message_get_sub_type(m) == 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 (lm_message_get_sub_type(m) == 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);
+  }
+
+  g_free(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+
+static LmHandlerResult handle_iq(LmMessageHandler *handler,
+                                 LmConnection *connection,
+                                 LmMessage *m, gpointer user_data)
+{
+  int i;
+  const char *xmlns = NULL;
+  LmMessageNode *x;
+
+  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 (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_ERROR) {
+    display_server_error(lm_message_node_get_child(m->node, "error"));
+    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+  }
+
+  if ((lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_SET) ||
+      (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_GET))
+    send_iq_error(connection, m, XMPP_ERROR_NOT_IMPLEMENTED);
+
+  scr_LogPrint(LPRINT_NORMAL|LPRINT_DEBUG, "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);
+
+  r = jidtodisp(from);
+
+  newbuddy = !roster_find(r, jidsearch, 0);
+
+  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_SUBSCRIBE) {
+    /* The sender wishes to subscribe to our presence */
+    const char *msg;
+    int isagent;
+    eviqs *evn;
+
+    isagent = (roster_gettype(r) & ROSTER_TYPE_AGENT) != 0;
+    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 (lm_message_get_sub_type(m) == 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 (lm_message_get_sub_type(m) == 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 (lm_message_get_sub_type(m) == 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 {
+    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 (settings_opt_get_int("tracelog_level") != 2)
+    return;
+  if (message) {
+    char *msg;
+    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 ((LmLogLevelFlags)log_level & LM_LOG_LEVEL_NET) {
+      scr_LogPrint(LPRINT_DEBUG, "LM-NET: %s", msg);
+    }
+    else if (log_level & LM_LOG_LEVEL_PARSER) {
+      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 *username, *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");
+  username   = settings_opt_get("username");
+  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 (!servername) {
+    scr_LogPrint(LPRINT_LOGNORM, "Server name has not been specified!");
+    return;
+  }
+
+  if (!username) {
+    scr_LogPrint(LPRINT_LOGNORM, "User name has not been specified!");
+    return;
+  }
+  if (!password) {
+    scr_LogPrint(LPRINT_LOGNORM, "Password has not been specified!");
+    return;
+  }
+
+  lconnection = lm_connection_new_with_context
+          (NULL, g_main_loop_get_context(main_loop));
+
+  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);
+  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(username, servername, resource);
+  lm_connection_set_jid(lconnection, fjid);
+#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);
+  scr_LogPrint(LPRINT_LOGNORM, "Port: %i\n", 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);
+  }
+}
+
+//  insert_entity_capabilities(presence_stanza)
+// Entity Capabilities (XEP-0115)
+static void insert_entity_capabilities(LmMessageNode * x)
+{
+  LmMessageNode *y;
+  const char *ver = entity_version();
+  char *exts, *exts2;
+
+  exts = NULL;
+
+  y = lm_message_node_add_child(x, "c", NULL);
+  lm_message_node_set_attribute(y, "xmlns", NS_CAPS);
+  lm_message_node_set_attribute(y, "node", MCABBER_CAPS_NODE);
+  lm_message_node_set_attribute(y, "ver", ver);
+#ifdef JEP0085
+  if (!chatstates_disabled) {
+    exts2 = g_strjoin(" ", "csn", exts, NULL);
+    g_free(exts);
+    exts = exts2;
+  }
+#endif
+  if (!settings_opt_get_int("iq_last_disable")) {
+    exts2 = g_strjoin(" ", "iql", exts, NULL);
+    g_free(exts);
+    exts = exts2;
+  }
+  if (exts) {
+    lm_message_node_set_attribute(y, "ext", exts);
+    g_free(exts);
+  }
+}
+
+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); // 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_add_child(x, "autowhois",
+                                (awhois == autowhois_on) ? "1" : "0");
+    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.");
+}