view mcabber/mcabber/xmpp_iq.c @ 2268:f5402d705f67

Fix FSF addresses in all files
author Mikael Berthe <mikael@lilotux.net>
date Sun, 05 Jun 2016 20:06:46 +0200
parents ea90906cb691
children 6e1ead98930d
line wrap: on
line source

/*
 * xmpp_iq.c    -- Jabber protocol IQ-related stuff
 *
 * Copyright (C) 2008-2010 Frank Zschockelt <mcabber@freakysoft.de>
 * Copyright (C) 2005-2010 Mikael Berthe <mikael@lilotux.net>
 * Parts come from the centericq project:
 * Copyright (C) 2002-2005 by Konstantin Klyagin <konst@konst.org.ua>
 * Some small parts come from the Pidgin project <http://pidgin.im/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <string.h>
#include <sys/utsname.h>

#include "xmpp_helper.h"
#include "commands.h"
#include "screen.h"
#include "utils.h"
#include "logprint.h"
#include "settings.h"
#include "caps.h"
#include "main.h"

extern struct xmpp_error xmpp_errors[];

static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h,
                                                    LmConnection *c,
                                                    LmMessage *m,
                                                    gpointer ud);

static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h,
                                                          LmConnection *c,
                                                          LmMessage *m,
                                                          gpointer ud);

inline double seconds_since_last_use(void);

struct adhoc_command {
  char *name;
  char *description;
  bool only_for_self;
  LmHandleMessageFunction callback;
};

const struct adhoc_command adhoc_command_list[] = {
  { "http://jabber.org/protocol/rc#set-status",
    "Change client status",
    1,
    &handle_iq_command_set_status },
  { "http://jabber.org/protocol/rc#leave-groupchats",
    "Leave groupchat(s)",
    1,
    &handle_iq_command_leave_groupchats },
  { NULL, NULL, 0, NULL },
};

struct adhoc_status {
  char *name;   // the name used by adhoc
  char *description;
  char *status; // the string, used by setstus
};
// It has to match imstatus of roster.h!
const struct adhoc_status adhoc_status_list[] = {
  {"offline", "Offline", "offline"},
  {"online", "Online", "avail"},
  {"chat", "Chat", "free"},
  {"dnd", "Do not disturb", "dnd"},
  {"xa", "Extended away", "notavail"},
  {"away", "Away", "away"},
#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
  {"invisible", "Invisible", "invisible"},
#endif
  {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;

  if (G_UNLIKELY(!m)) return NULL;

  for (i = 0; xmpp_errors[i].code; ++i)
    if (xmpp_errors[i].code == error)
      break;
  g_return_val_if_fail(xmpp_errors[i].code > 0, NULL);

  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_ERROR);
  err = lm_message_node_add_child(r->node, "error", NULL);
  lm_message_node_set_attribute(err, "code", xmpp_errors[i].code_str);
  lm_message_node_set_attribute(err, "type", xmpp_errors[i].type);
  lm_message_node_set_attribute
          (lm_message_node_add_child(err,
                                     xmpp_errors[i].condition, NULL),
           "xmlns", NS_XMPP_STANZAS);

  return r;
}

void send_iq_error(LmConnection *c, LmMessage *m, guint error)
{
  LmMessage *r;
  r = lm_message_new_iq_error(m, error);
  if (r) {
    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);
}

// Dummy handler to ignore IQ response
LmHandlerResult handle_iq_dummy(LmMessageHandler *h, LmConnection *c,
                                 LmMessage *m, gpointer ud)
{
  LmMessageSubType mstype = lm_message_get_sub_type(m);
  if (mstype == LM_MESSAGE_SUB_TYPE_ERROR) {
    display_server_error(lm_message_node_get_child(m->node, "error"),
                         lm_message_get_from(m));
  }
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

static LmHandlerResult handle_iq_commands_list(LmMessageHandler *h,
                                               LmConnection *c,
                                               LmMessage *m, gpointer ud)
{
  LmMessage *iq;
  LmMessageNode *query;
  const char *requester_jid;
  const struct adhoc_command *command;
  const char *node;
  gboolean from_self;

  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  query = lm_message_node_add_child(iq->node, "query", NULL);
  lm_message_node_set_attribute(query, "xmlns", NS_COMMANDS);
  node = lm_message_node_get_attribute
          (lm_message_node_get_child(m->node, "query"),
           "node");
  if (node)
    lm_message_node_set_attribute(query, "node", node);

  requester_jid = lm_message_get_from(m);
  from_self = jid_equal(lm_connection_get_jid(c), requester_jid);

  for (command = adhoc_command_list ; command->name ; command++) {
    if (!command->only_for_self || from_self) {
      lm_message_node_set_attributes
              (lm_message_node_add_child(query, "item", NULL),
               "node", command->name,
               "name", command->description,
               "jid", lm_connection_get_jid(c),
               NULL);
    }
  }

  lm_connection_send(c, iq, NULL);
  lm_message_unref(iq);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

static LmHandlerResult handle_iq_command_set_status(LmMessageHandler *h,
                                                    LmConnection *c,
                                                    LmMessage *m, gpointer ud)
{
  const char *action, *node;
  char *sessionid;
  LmMessage *iq;
  LmMessageNode *command, *x, *y;
  const struct adhoc_status *s;

  x = lm_message_node_get_child(m->node, "command");
  action = lm_message_node_get_attribute(x, "action");
  node = lm_message_node_get_attribute(x, "node");
  sessionid = (char *)lm_message_node_get_attribute(x, "sessionid");

  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  command = lm_message_node_add_child(iq->node, "command", NULL);
  lm_message_node_set_attribute(command, "node", node);
  lm_message_node_set_attribute(command, "xmlns", NS_COMMANDS);

  if (!sessionid) {
    sessionid = generate_session_id("set-status");
    lm_message_node_set_attribute(command, "sessionid", sessionid);
    g_free(sessionid);
    sessionid = NULL;
    lm_message_node_set_attribute(command, "status", "executing");

    x = lm_message_node_add_child(command, "x", NULL);
    lm_message_node_set_attribute(x, "type", "form");
    lm_message_node_set_attribute(x, "xmlns", "jabber:x:data");

    lm_message_node_add_child(x, "title", "Change Status");

    lm_message_node_add_child(x, "instructions",
                              "Choose the status and status message");

    // TODO see if factorisation is possible
    y = lm_message_node_add_child(x, "field", NULL);
    lm_message_node_set_attribute(y, "type", "hidden");
    lm_message_node_set_attribute(y, "var", "FORM_TYPE");

    lm_message_node_add_child(y, "value", "http://jabber.org/protocol/rc");

    y = lm_message_node_add_child(x, "field", NULL);
    lm_message_node_set_attributes(y,
                                   "type", "list-single",
                                   "var", "status",
                                   "label", "Status",
                                   NULL);
    lm_message_node_add_child(y, "required", NULL);

    // XXX: ugly
    lm_message_node_add_child(y, "value",
                              adhoc_status_list[xmpp_getstatus()].name);
    for (s = adhoc_status_list; s->name; s++) {
        LmMessageNode *option = lm_message_node_add_child(y, "option", NULL);
        lm_message_node_add_child(option, "value", s->name);
        lm_message_node_set_attribute(option, "label", s->description);
    }
    // TODO add priority ?
    // I do not think this is useful, user should not have to care of the
    // priority like gossip and gajim do (misc)
    lm_message_node_set_attributes
            (lm_message_node_add_child(x, "field", NULL),
             "type", "text-multi",
             "var", "status-message",
             "label", "Message",
             NULL);
  } else if (action && !strcmp(action, "cancel")) {
    lm_message_node_set_attribute(command, "status", "canceled");
  } else  { // (if sessionid and not canceled)
    y = lm_message_node_find_xmlns(x, "jabber:x:data"); //x?xmlns=jabber:x:data
    if (y) {
      const char *value=NULL, *message=NULL;
      LmMessageNode *fields, *field;
      field = fields = lm_message_node_get_child(y, "field"); //field?var=status
      while (field && strcmp("status",
                             lm_message_node_get_attribute(field, "var")))
        field = field->next;
      field = lm_message_node_get_child(field, "value");
      if (field)
        value = lm_message_node_get_value(field);
      field = fields; //field?var=status-message
      while (field && strcmp("status-message",
                             lm_message_node_get_attribute(field, "var")))
        field = field->next;
      field = lm_message_node_get_child(field, "value");
      if (field)
        message = lm_message_node_get_value(field);
      if (value) {
        for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++);
        if (s->name) {
          char *status = g_strdup_printf("%s %s", s->status,
                                         message ? message : "");
          cmd_setstatus(NULL, status);
          g_free(status);
          lm_message_node_set_attribute(command, "status", "completed");
          lm_message_node_add_dataform_result(command,
                                              "Status has been changed");
        }
      }
    }
  }
  if (sessionid)
    lm_message_node_set_attribute(command, "sessionid", sessionid);
  lm_connection_send(c, iq, NULL);
  lm_message_unref(iq);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

static void _callback_foreach_buddy_groupchat(gpointer rosterdata, void *param)
{
  LmMessageNode *field, *option;
  const char *room_jid, *nickname;
  char *desc;

  room_jid = buddy_getjid(rosterdata);
  if (!room_jid) return;
  nickname = buddy_getnickname(rosterdata);
  if (!nickname) return;
  field = param;

  option = lm_message_node_add_child(field, "option", NULL);
  lm_message_node_add_child(option, "value", room_jid);
  desc = g_strdup_printf("%s on %s", nickname, room_jid);
  lm_message_node_set_attribute(option, "label", desc);
  g_free(desc);
}

static LmHandlerResult handle_iq_command_leave_groupchats(LmMessageHandler *h,
                                                          LmConnection *c,
                                                          LmMessage *m,
                                                          gpointer ud)
{
  const char *action, *node;
  char *sessionid;
  LmMessage *iq;
  LmMessageNode *command, *x;

  x = lm_message_node_get_child(m->node, "command");
  if (!x)
    return LM_HANDLER_RESULT_REMOVE_MESSAGE;

  action = lm_message_node_get_attribute(x, "action");
  node = lm_message_node_get_attribute(x, "node");
  sessionid = (char*)lm_message_node_get_attribute(x, "sessionid");

  iq = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  command = lm_message_node_add_child(iq->node, "command", NULL);
  lm_message_node_set_attributes(command,
                                 "node", node,
                                 "xmlns", NS_COMMANDS,
                                 NULL);

  if (!sessionid) {
    LmMessageNode *field;

    sessionid = generate_session_id("leave-groupchats");
    lm_message_node_set_attribute(command, "sessionid", sessionid);
    g_free(sessionid);
    sessionid = NULL;
    lm_message_node_set_attribute(command, "status", "executing");

    x = lm_message_node_add_child(command, "x", NULL);
    lm_message_node_set_attributes(x,
                                   "type", "form",
                                   "xmlns", "jabber:x:data",
                                   NULL);

    lm_message_node_add_child(x, "title", "Leave groupchat(s)");

    lm_message_node_add_child(x, "instructions",
                              "What groupchats do you want to leave?");

    field = lm_message_node_add_child(x, "field", NULL);
    lm_message_node_set_attributes(field,
                                   "type", "hidden",
                                   "var", "FORM_TYPE",
                                   NULL);

    lm_message_node_add_child(field, "value",
                              "http://jabber.org/protocol/rc");

    field = lm_message_node_add_child(x, "field", NULL);
    lm_message_node_set_attributes(field,
                                   "type", "list-multi",
                                   "var", "groupchats",
                                   "label", "Groupchats: ",
                                   NULL);
    lm_message_node_add_child(field, "required", NULL);

    foreach_buddy(ROSTER_TYPE_ROOM, &_callback_foreach_buddy_groupchat, field);
    // TODO: return an error if we are not connected to groupchats
  } else if (action && !strcmp(action, "cancel")) {
    lm_message_node_set_attribute(command, "status", "canceled");
  } else  { // (if sessionid and not canceled)
    LmMessageNode *form = lm_message_node_find_xmlns(x, "jabber:x:data");// TODO
    if (form) {
      LmMessageNode *field;

      lm_message_node_set_attribute(command, "status", "completed");
      // TODO: implement sth. like "field?var=groupchats" in xmlnode...
      field  = lm_message_node_get_child(form, "field");
      while (field && strcmp("groupchats",
                             lm_message_node_get_attribute(field, "var")))
        field = field->next;

      if (field)
        for (x = field->children ; x ; x = x->next)
        {
          if (!strcmp (x->name, "value")) {
            GList* b = buddy_search_jid(lm_message_node_get_value(x));
            if (b)
              cmd_room_leave(b->data, "Requested by remote command");
          }
        }
      lm_message_node_add_dataform_result(command,
                                          "Groupchats have been left");
    }
  }
  if (sessionid)
    lm_message_node_set_attribute(command, "sessionid", sessionid);
  lm_connection_send(c, iq, NULL);
  lm_message_unref(iq);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

LmHandlerResult handle_iq_commands(LmMessageHandler *h,
                                   LmConnection *c,
                                   LmMessage *m, gpointer ud)
{
  const char *requester_jid = NULL;
  LmMessageNode *cmd;
  const struct adhoc_command *command;

  // mcabber has only partial XEP-0146 support...
  if (LM_MESSAGE_SUB_TYPE_SET != lm_message_get_sub_type(m))
    return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;

  requester_jid = lm_message_get_from(m);

  cmd = lm_message_node_get_child(m->node, "command");
  if (!cmd) {
    //send_iq_error(c, m, XMPP_ERROR_BAD_REQUEST);
    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
  }
  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);
      if (r) {
        err = lm_message_node_get_child(r->node, "error");
        lm_message_node_set_attribute
          (lm_message_node_add_child(err, "malformed-action", NULL),
           "xmlns", NS_COMMANDS);
        lm_connection_send(c, r, NULL);
        lm_message_unref(r);
      }
    }
  } else {
    send_iq_error(c, m, XMPP_ERROR_FORBIDDEN);
  }
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}


LmHandlerResult handle_iq_disco_items(LmMessageHandler *h,
                                      LmConnection *c,
                                      LmMessage *m, gpointer ud)
{
  LmMessageNode *query;
  const char *node = NULL;
  query = lm_message_node_get_child(m->node, "query");
  if (query)
    node = lm_message_node_get_attribute(query, "node");
  if (node) {
    if (!strcmp(node, NS_COMMANDS)) {
      return handle_iq_commands_list(NULL, c, m, ud);
    } else {
      send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED);
    }
  } else {
    // not sure about this one
    send_iq_error(c, m, XMPP_ERROR_NOT_IMPLEMENTED);
  }
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}


void _disco_add_feature_helper(gpointer data, gpointer user_data)
{
  LmMessageNode *node = user_data;
  lm_message_node_set_attribute
          (lm_message_node_add_child(node, "feature", NULL), "var", data);
}

//  disco_info_set_caps(ansquery, entitycaps)
// Add features attributes to ansquery.  entitycaps should either be a
// valid capabilities hash or NULL. If it is NULL, the node attribute won't
// be added to the query child and Entity Capabilities will be announced
// as a feature.
// Please change the entity version string if you modify mcabber disco
// source code, so that it doesn't conflict with the upstream client.
static void disco_info_set_caps(LmMessageNode *ansquery,
                                const char *entitycaps)
{
  if (entitycaps) {
    char *eversion;
    eversion = g_strdup_printf("%s#%s", MCABBER_CAPS_NODE, entitycaps);
    lm_message_node_set_attribute(ansquery, "node", eversion);
    g_free(eversion);
  }

  lm_message_node_set_attributes
          (lm_message_node_add_child(ansquery, "identity", NULL),
           "category", "client",
           "name", PACKAGE_STRING,
           "type", "pc",
           NULL);

  if (entitycaps)
    caps_foreach_feature(entitycaps, _disco_add_feature_helper, ansquery);
  else
    caps_foreach_feature(entity_version(xmpp_getstatus()),
                         _disco_add_feature_helper,
                         ansquery);
}

LmHandlerResult handle_iq_disco_info(LmMessageHandler *h,
                                     LmConnection *c,
                                     LmMessage *m, gpointer ud)
{
  LmMessage *r;
  LmMessageNode *query, *tmp;
  const char *node = NULL;
  const char *param = NULL;

  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_RESULT)
    return LM_HANDLER_RESULT_REMOVE_MESSAGE;

  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  query = lm_message_node_add_child(r->node, "query", NULL);
  lm_message_node_set_attribute(query, "xmlns", NS_DISCO_INFO);
  tmp = lm_message_node_find_child(m->node, "query");
  if (tmp) {
    node = lm_message_node_get_attribute(tmp, "node");
    param = node+strlen(MCABBER_CAPS_NODE)+1;
  }
  if (node && startswith(node, MCABBER_CAPS_NODE "#", FALSE))
    disco_info_set_caps(query, param);  // client#version
  else
    // Basic discovery request
    disco_info_set_caps(query, NULL);

  lm_connection_send(c, r, NULL);
  lm_message_unref(r);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

LmHandlerResult handle_iq_roster(LmMessageHandler *h, LmConnection *c,
                                 LmMessage *m, gpointer ud)
{
  LmMessageNode *y;
  const char *fjid, *name, *group, *sub, *ask;
  char *cleanalias;
  enum subscr esub;
  int need_refresh = FALSE;
  guint roster_type;

  y = lm_message_node_find_child(lm_message_node_find_xmlns(m->node, NS_ROSTER),
                                 "item");
  for ( ; 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);
  }

  // Acknowledge IQ message
  if (lm_message_get_sub_type(m) == LM_MESSAGE_SUB_TYPE_SET) {
    LmMessage *result;
    result = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
    lm_connection_send(c, result, NULL);
    lm_message_unref(result);
  }

  buddylist_build();
  update_roster = TRUE;
  if (need_refresh)
    scr_update_buddy_window();
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

LmHandlerResult handle_iq_ping(LmMessageHandler *h, LmConnection *c,
                               LmMessage *m, gpointer ud)
{
  LmMessage *r;

  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  lm_connection_send(c, r, NULL);
  lm_message_unref(r);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

double seconds_since_last_use(void)
{
  return difftime(time(NULL), iqlast);
}

LmHandlerResult handle_iq_last(LmMessageHandler *h, LmConnection *c,
                               LmMessage *m, gpointer ud)
{
  LmMessage *r;
  LmMessageNode *query;
  char *seconds;

  if (!settings_opt_get_int("iq_hide_requests")) {
    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ last time request from <%s>",
                 lm_message_get_from(m));
  }

  if (settings_opt_get_int("iq_last_disable") ||
      (settings_opt_get_int("iq_last_disable_when_notavail") &&
       xmpp_getstatus() == notavail))
  {
    send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
  }

  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  query = lm_message_node_add_child(r->node, "query", NULL);
  lm_message_node_set_attribute(query, "xmlns", NS_LAST);
  seconds = g_strdup_printf("%.0f", seconds_since_last_use());
  lm_message_node_set_attribute(query, "seconds", seconds);
  g_free(seconds);

  lm_connection_send(c, r, NULL);
  lm_message_unref(r);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

LmHandlerResult handle_iq_version(LmMessageHandler *h, LmConnection *c,
                                  LmMessage *m, gpointer ud)
{
  LmMessage *r;
  LmMessageNode *query;

  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")) {
    send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
  }

  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);

  query = lm_message_node_add_child(r->node, "query", NULL);
  lm_message_node_set_attribute(query, "xmlns", NS_VERSION);

  lm_message_node_add_child(query, "name", PACKAGE_NAME);

  // MCabber version
  if (!settings_opt_get_int("iq_version_hide_version")) {
    char *ver = mcabber_version();
    lm_message_node_add_child(query, "version", ver);
    g_free(ver);
  }

  // OS details
  if (!settings_opt_get_int("iq_version_hide_os")) {
    char *os;
    struct utsname osinfo;
    uname(&osinfo);
    os = g_strdup_printf("%s %s %s", osinfo.sysname, osinfo.release,
                         osinfo.machine);
    lm_message_node_add_child(query, "os", os);
    g_free(os);
  }

  lm_connection_send(c, r, NULL);
  lm_message_unref(r);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

// This function borrows some code from the Pidgin project
LmHandlerResult handle_iq_time(LmMessageHandler *h, LmConnection *c,
                               LmMessage *m, gpointer ud)
{
  LmMessage *r;
  LmMessageNode *query;
  char *buf, *utf8_buf;
  time_t now_t;
  struct tm *now;

  time(&now_t);

  if (!settings_opt_get_int("iq_hide_requests")) {
    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>",
                 lm_message_get_from(m));
  }

  if (settings_opt_get_int("iq_time_hide")) {
    send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
  }

  buf = g_new0(char, 512);

  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  query = lm_message_node_add_child(r->node, "query", NULL);
  lm_message_node_set_attribute(query, "xmlns", NS_TIME);

  now = gmtime(&now_t);

  strftime(buf, 512, "%Y%m%dT%T", now);
  lm_message_node_add_child(query, "utc", buf);

  now = localtime(&now_t);

  strftime(buf, 512, "%Z", now);
  if ((utf8_buf = to_utf8(buf))) {
    lm_message_node_add_child(query, "tz", utf8_buf);
    g_free(utf8_buf);
  }

  strftime(buf, 512, "%d %b %Y %T", now);
  if ((utf8_buf = to_utf8(buf))) {
    lm_message_node_add_child(query, "display", utf8_buf);
    g_free(utf8_buf);
  }

  lm_connection_send(c, r, NULL);
  lm_message_unref(r);
  g_free(buf);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

// This function borrows some code from the Pidgin project
LmHandlerResult handle_iq_time202(LmMessageHandler *h, LmConnection *c,
                                  LmMessage *m, gpointer ud)
{
  LmMessage *r;
  LmMessageNode *query;
  char *buf, *utf8_buf;
  time_t now_t;
  struct tm *now;
  char const *sign;
  int diff = 0;

  time(&now_t);

  if (!settings_opt_get_int("iq_hide_requests")) {
    scr_LogPrint(LPRINT_LOGNORM, "Received an IQ time request from <%s>",
                 lm_message_get_from(m));
  }

  if (settings_opt_get_int("iq_time_hide")) {
    send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
    return LM_HANDLER_RESULT_REMOVE_MESSAGE;
  }

  buf = g_new0(char, 512);

  r = lm_message_new_iq_from_query(m, LM_MESSAGE_SUB_TYPE_RESULT);
  query = lm_message_node_add_child(r->node, "time", NULL);
  lm_message_node_set_attribute(query, "xmlns", NS_XMPP_TIME);

  now = localtime(&now_t);

  if (now->tm_isdst >= 0) {
#if defined HAVE_TM_GMTOFF
    diff = now->tm_gmtoff;
#elif defined HAVE_TIMEZONE
    tzset();
    diff = -timezone;
#endif
  }

  if (diff < 0) {
    sign = "-";
    diff = -diff;
  } else {
    sign = "+";
  }
  diff /= 60;
  snprintf(buf, 512, "%c%02d:%02d", *sign, diff / 60, diff % 60);
  if ((utf8_buf = to_utf8(buf))) {
    lm_message_node_add_child(query, "tzo", utf8_buf);
    g_free(utf8_buf);
  }

  now = gmtime(&now_t);

  strftime(buf, 512, "%Y-%m-%dT%TZ", now);
  lm_message_node_add_child(query, "utc", buf);

  lm_connection_send(c, r, NULL);
  lm_message_unref(r);
  g_free(buf);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

LmHandlerResult handle_iq_vcard(LmMessageHandler *h, LmConnection *c,
                                LmMessage *m, gpointer ud)
{
  send_iq_error(c, m, XMPP_ERROR_SERVICE_UNAVAILABLE);
  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

/* vim: set et cindent cinoptions=>2\:2(0 ts=2 sw=2:  For Vim users... */