diff mcabber/src/xmpp_iq.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_iq.c	Sun Oct 11 15:38:32 2009 +0200
@@ -0,0 +1,820 @@
+/* See xmpp.c file for copyright and license details. */
+
+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;
+}
+
+static 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);
+  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, *value, *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);
+  value = 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;
+
+      for (x = field->children ; x ; x = x->next)
+      {
+        LmMessageNode *to_leave = lm_message_node_get_child(x, "value");
+        if (to_leave) {
+          GList* b = buddy_search_jid(lm_message_node_get_value(to_leave));
+          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;
+}
+
+static 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;
+}
+
+
+static 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;
+}
+
+
+//  disco_info_set_ext(ansquery, ext)
+// Add features attributes to ansquery for extension ext.
+static void disco_info_set_ext(LmMessageNode *ansquery, const char *ext)
+{
+  char *nodename = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, ext);
+  lm_message_node_set_attribute(ansquery, "node", nodename);
+  g_free(nodename);
+  if (!strcasecmp(ext, "csn")) {
+    // I guess it's ok to send this even if it's not compiled in.
+    lm_message_node_set_attribute(lm_message_node_add_child(ansquery,
+                                                            "feature", NULL),
+                                  "var", NS_CHATSTATES);
+  }
+  if (!strcasecmp(ext, "iql")) {
+    // I guess it's ok to send this even if it's not compiled in.
+    lm_message_node_set_attribute(lm_message_node_add_child(ansquery,
+                                                            "feature", NULL),
+                                  "var", NS_LAST);
+  }
+}
+
+//  disco_info_set_default(ansquery, entitycaps)
+// Add features attributes to ansquery.  If entitycaps is TRUE, assume
+// that we're answering an Entity Caps request (if not, the request was
+// a basic discovery query).
+// 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_default(LmMessageNode *ansquery, guint entitycaps)
+{
+  LmMessageNode *y;
+  char *eversion;
+
+  eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entity_version());
+  lm_message_node_set_attribute(ansquery, "node", eversion);
+  g_free(eversion);
+
+  y = lm_message_node_add_child(ansquery, "identity", NULL);
+
+  lm_message_node_set_attributes(y,
+                                 "category", "client",
+                                 "type", "pc",
+                                 "name", PACKAGE_NAME,
+                                 NULL);
+
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(ansquery, "feature", NULL),
+           "var", NS_DISCO_INFO);
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(ansquery, "feature", NULL),
+           "var", NS_MUC);
+#ifdef JEP0085
+  // Advertise ChatStates only if we're not using Entity Capabilities
+  if (!entitycaps)
+    lm_message_node_set_attribute
+            (lm_message_node_add_child(ansquery, "feature", NULL),
+             "var", NS_CHATSTATES);
+#endif
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(ansquery, "feature", NULL),
+           "var", NS_TIME);
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(ansquery, "feature", NULL),
+           "var", NS_XMPP_TIME);
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(ansquery, "feature", NULL),
+           "var", NS_VERSION);
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(ansquery, "feature", NULL),
+           "var", NS_PING);
+  lm_message_node_set_attribute
+          (lm_message_node_add_child(ansquery, "feature", NULL),
+           "var", NS_COMMANDS);
+  if (!entitycaps)
+    lm_message_node_set_attribute
+            (lm_message_node_add_child(ansquery, "feature", NULL),
+             "var", NS_LAST);
+}
+
+static LmHandlerResult handle_iq_disco_info(LmMessageHandler *h,
+                                            LmConnection *c,
+                                            LmMessage *m, gpointer ud)
+{
+  LmMessage *r;
+  LmMessageNode *query, *tmp;
+  const char *node = NULL;
+
+  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");
+  if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE)) {
+    const char *param = node+strlen(MCABBER_CAPS_NODE)+1;
+    if (!strcmp(param, entity_version()))
+      disco_info_set_default(query, TRUE);  // client#version
+    else
+      disco_info_set_ext(query, param);     // client#extension
+  } else {
+    // Basic discovery request
+    disco_info_set_default(query, FALSE);
+  }
+
+  lm_connection_send(c, r, NULL);
+  lm_message_unref(r);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static 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;
+}
+
+static 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);
+}
+
+static 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);
+  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;
+}
+
+static 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_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
+static 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);
+
+  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
+static 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;
+}