changeset 1158:c30c315dc447

XEP-0146 support (Remote Controlling Clients)
author misc@mandriva.org
date Thu, 15 Feb 2007 21:49:39 +0100
parents 5c857f0f0ab8
children 53c0c5be43fa
files mcabber/libjabber/jabber.h mcabber/src/commands.c mcabber/src/commands.h mcabber/src/jab_iq.c
diffstat 4 files changed, 442 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/mcabber/libjabber/jabber.h	Thu Feb 15 12:45:03 2007 +0100
+++ b/mcabber/libjabber/jabber.h	Thu Feb 15 21:49:39 2007 +0100
@@ -283,6 +283,7 @@
 #define NS_REGISTER_FEATURE "http://jabber.org/features/iq-register"
 #define NS_MUC       "http://jabber.org/protocol/muc"
 #define NS_CHATSTATES "http://jabber.org/protocol/chatstates"
+#define NS_COMMANDS   "http://jabber.org/protocol/commands"
 
 #define NS_XDBGINSERT "jabber:xdb:ginsert"
 #define NS_XDBNSLIST  "jabber:xdb:nslist"
--- a/mcabber/src/commands.c	Thu Feb 15 12:45:03 2007 +0100
+++ b/mcabber/src/commands.c	Thu Feb 15 21:49:39 2007 +0100
@@ -678,7 +678,7 @@
 // Set your Jabber status.
 // - if recipient is not NULL, the status is sent to this contact only
 // - arg must be "status message" (message is optional)
-static void setstatus(const char *recipient, const char *arg)
+void setstatus(const char *recipient, const char *arg)
 {
   char **paramlst;
   char *status;
@@ -1991,7 +1991,7 @@
   free_arg_lst(paramlst);
 }
 
-static void room_leave(gpointer bud, char *arg)
+void room_leave(gpointer bud, char *arg)
 {
   gchar *roomid, *desc;
   const char *nickname;
--- a/mcabber/src/commands.h	Thu Feb 15 12:45:03 2007 +0100
+++ b/mcabber/src/commands.h	Thu Feb 15 21:49:39 2007 +0100
@@ -21,6 +21,8 @@
 extern void mcabber_connect(void);
 
 void room_whois(gpointer bud, char *nick_locale, guint interactive);
+void room_leave(gpointer bud, char *arg);
+void setstatus(const char *recipient, const char *arg);
 
 #endif /* __COMMANDS_H__ */
 
--- a/mcabber/src/jab_iq.c	Thu Feb 15 12:45:03 2007 +0100
+++ b/mcabber/src/jab_iq.c	Thu Feb 15 21:49:39 2007 +0100
@@ -32,6 +32,7 @@
 #include "screen.h"
 #include "settings.h"
 #include "hbuf.h"
+#include "commands.h"
 
 
 // Bookmarks for IQ:private storage
@@ -53,6 +54,50 @@
   vcard_pref    = 1<<7,
 };
 
+static void handle_iq_command_set_status(jconn conn, char *from,
+                                         const char *id, xmlnode xmldata);
+
+static void handle_iq_command_leave_groupchats(jconn conn, char *from,
+                                               const char *id, xmlnode xmldata);
+
+typedef void (*adhoc_command_callback)(jconn, char*, const char*, xmlnode);
+
+struct adhoc_command {
+  char *name;
+  char *description;
+  bool only_for_self;
+  adhoc_command_callback callback;
+};
+
+const struct adhoc_command adhoc_command_list[] = {
+  { "http://jabber.org/protocol/rc#set-status",
+    "Set the client as away",
+    1,
+    &handle_iq_command_set_status },
+  { "http://jabber.org/protocol/rc#leave-groupchats",
+    "Leave groupchats",
+    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
+};
+
+const struct adhoc_status adhoc_status_list[] = {
+  {"online", "Online", "avail"},
+  {"chat", "Chat", "free"},
+  {"away", "Away", "away"},
+  {"xd", "Extended away", "notavail"},
+  {"dnd", "Do not disturb", "dnd"},
+  {"invisible", "Invisible", "invisible"},
+  {"offline", "Offline", "offline"},
+  {NULL, NULL, NULL},
+};
+
 //  iqs_new(type, namespace, prefix, timeout)
 // Create a query (GET, SET) IQ structure.  This function should not be used
 // for RESULT packets.
@@ -794,6 +839,381 @@
   }
 }
 
+// FIXME  highly duplicated code
+// factorisation is doable
+static void send_iq_not_implemented(jconn conn, char *from, xmlnode xmldata)
+{
+  xmlnode x, y, z;
+  // Not implemented.
+  x = xmlnode_dup(xmldata);
+  xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
+  xmlnode_hide_attrib(x, "from");
+
+  xmlnode_put_attrib(x, "type", TMSG_ERROR);
+  y = xmlnode_insert_tag(x, TMSG_ERROR);
+  xmlnode_put_attrib(y, "code", "501");
+  xmlnode_put_attrib(y, "type", "cancel");
+  z = xmlnode_insert_tag(y, "feature-not-implemented");
+  xmlnode_put_attrib(z, "xmlns", NS_XMPP_STANZAS);
+
+  jab_send(conn, x);
+  xmlnode_free(x);
+}
+
+/*
+static void send_iq_commands_bad_action(jconn conn, char *from, xmlnode xmldata)
+{
+  xmlnode x, y, z;
+
+  x = xmlnode_dup(xmldata);
+  xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
+  xmlnode_hide_attrib(x, "from");
+
+  xmlnode_put_attrib(x, "type", TMSG_ERROR);
+  y = xmlnode_insert_tag(x, TMSG_ERROR);
+  xmlnode_put_attrib(y, "code", "400");
+  xmlnode_put_attrib(y, "type", "modify");
+  z = xmlnode_insert_tag(y, "bad-request");
+  xmlnode_put_attrib(z, "xmlns", NS_XMPP_STANZAS);
+  z = xmlnode_insert_tag(y, "bad-action");
+  xmlnode_put_attrib(z, "xmlns", NS_COMMANDS);
+
+  jab_send(conn, x);
+  xmlnode_free(x);
+}
+*/
+
+static void send_iq_forbidden(jconn conn, char *from, xmlnode xmldata)
+{
+  xmlnode x, y, z;
+
+  x = xmlnode_dup(xmldata);
+  xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
+  xmlnode_hide_attrib(x, "from");
+
+  xmlnode_put_attrib(x, "type", TMSG_ERROR);
+  y = xmlnode_insert_tag(x, TMSG_ERROR);
+  xmlnode_put_attrib(y, "code", "403");
+  xmlnode_put_attrib(y, "type", "cancel");
+  z = xmlnode_insert_tag(y, "forbidden");
+  xmlnode_put_attrib(z, "xmlns", NS_XMPP_STANZAS);
+
+  jab_send(conn, x);
+  xmlnode_free(x);
+}
+
+static void send_iq_commands_malformed_action(jconn conn, char *from,
+                                              xmlnode xmldata)
+{
+  xmlnode x, y, z;
+
+  x = xmlnode_dup(xmldata);
+  xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
+  xmlnode_hide_attrib(x, "from");
+
+  xmlnode_put_attrib(x, "type", TMSG_ERROR);
+  y = xmlnode_insert_tag(x, TMSG_ERROR);
+  xmlnode_put_attrib(y, "code", "400");
+  xmlnode_put_attrib(y, "type", "modify");
+  z = xmlnode_insert_tag(y, "bad-request");
+  xmlnode_put_attrib(z, "xmlns", NS_XMPP_STANZAS);
+  z = xmlnode_insert_tag(y, "malformed-action");
+  xmlnode_put_attrib(z, "xmlns", NS_COMMANDS);
+
+  jab_send(conn, x);
+  xmlnode_free(x);
+}
+
+static void handle_iq_commands_list(jconn conn, char *from, const char *id,
+                                    xmlnode xmldata)
+{
+  xmlnode x;
+  xmlnode myquery;
+  jid requester_jid;
+  const struct adhoc_command *command;
+  bool from_self;
+  x = jutil_iqnew(JPACKET__RESULT, NS_DISCO_ITEMS);
+  xmlnode_put_attrib(x, "id", id);
+  xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
+  myquery = xmlnode_get_tag(x, "query");
+
+  requester_jid = jid_new(conn->p, xmlnode_get_attrib(xmldata, "from"));
+  from_self = !jid_cmpx(conn->user, requester_jid, JID_USER | JID_SERVER);
+
+  for (command = adhoc_command_list ; command->name ; command++) {
+    if (!command->only_for_self || from_self) {
+      xmlnode item;
+      item = xmlnode_insert_tag(myquery, "item");
+      xmlnode_put_attrib(item, "node", command->name);
+      xmlnode_put_attrib(item, "name", command->description);
+      xmlnode_put_attrib(item, "jid", jid_full(conn->user));
+    }
+  }
+
+  jab_send(jc, x);
+  xmlnode_free(x);
+}
+
+static void xmlnode_insert_dataform_result_message(xmlnode node, char *message)
+{
+  xmlnode x = xmlnode_insert_tag(node, "x");
+  xmlnode_put_attrib(x, "type", "result");
+  xmlnode_put_attrib(x, "xmlns", "jabber:x:data");
+
+  xmlnode field = xmlnode_insert_tag(x, "field");
+  xmlnode_put_attrib(field, "type", "text-single");
+  xmlnode_put_attrib(field, "var", "message");
+
+  xmlnode value = xmlnode_insert_tag(field, "value");
+  xmlnode_insert_cdata(value, message, -1);
+}
+
+static char *generate_session_id(char *prefix)
+{
+  char *result;
+  static int counter = 0;
+  counter++;
+  // TODO better use timezone ?
+  result = g_strdup_printf("%s-%i", prefix, counter);
+  return result;
+}
+
+static void handle_iq_command_set_status(jconn conn, char *from, const char *id,
+                                          xmlnode xmldata)
+{
+  char *action, *node, *sessionid;
+  xmlnode iq, command, x, y;
+  const struct adhoc_status *s;
+
+  x = xmlnode_get_tag(xmldata, "command");
+  action = xmlnode_get_attrib(x, "action");
+  node = xmlnode_get_attrib(x, "node");
+  sessionid = xmlnode_get_attrib(x, "sessionid");
+
+  iq = xmlnode_new_tag("iq");
+  command = xmlnode_insert_tag(iq, "command");
+  xmlnode_put_attrib(command, "node", node);
+  xmlnode_put_attrib(command, "xmlns", NS_COMMANDS);
+
+  if (!sessionid) {
+    sessionid = generate_session_id("set-status");
+    xmlnode_put_attrib(command, "sessionid", sessionid);
+    g_free(sessionid);
+    xmlnode_put_attrib(command, "status", "executing");
+
+    x = xmlnode_insert_tag(command, "x");
+    xmlnode_put_attrib(x, "type", "form");
+    xmlnode_put_attrib(x, "xmlns", "jabber:x:data");
+
+    y = xmlnode_insert_tag(x, "title");
+    xmlnode_insert_cdata(y, "Change Status", -1);
+
+    y = xmlnode_insert_tag(x, "instructions");
+    xmlnode_insert_cdata(y, "Choose the status and status message", -1);
+
+    // TODO see if factorisation is possible
+    // (with xmlnode_insert_dataform_result_message)
+    y = xmlnode_insert_tag(x, "field");
+    xmlnode_put_attrib(y, "type", "hidden");
+    xmlnode_put_attrib(y, "var", "FORM_TYPE");
+
+    xmlnode value = xmlnode_insert_tag(y, "value");
+    xmlnode_insert_cdata(value, "http://jabber.org/protocol/rc", -1);
+
+    y = xmlnode_insert_tag(x, "field");
+    xmlnode_put_attrib(y, "type", "list-single");
+    xmlnode_put_attrib(y, "var", "status");
+    xmlnode_put_attrib(y, "label", "Status");
+    xmlnode_insert_tag(y, "required");
+
+    value = xmlnode_insert_tag(y, "value");
+    // TODO current status
+    xmlnode_insert_cdata(value, "online", -1);
+    for (s = adhoc_status_list; s->name; s++) {
+        xmlnode option = xmlnode_insert_tag(y, "option");
+        value = xmlnode_insert_tag(option, "value");
+        xmlnode_insert_cdata(value, s->name, -1);
+        xmlnode_put_attrib(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)
+    y = xmlnode_insert_tag(x, "field");
+    xmlnode_put_attrib(y, "type", "text-multi");
+    xmlnode_put_attrib(y, "var", "status-message");
+    xmlnode_put_attrib(y, "label", "Message");
+  }
+  else // (if sessionid)
+  {
+    y = xmlnode_get_tag(x, "x?xmlns=jabber:x:data");
+    if (y) {
+      char *value, *message;
+      value = xmlnode_get_tag_data(xmlnode_get_tag(y, "field?var=status"),
+                                   "value");
+      message = xmlnode_get_tag_data(xmlnode_get_tag(y,
+                                   "field?var=status-message"), "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);
+        xmlnode_put_attrib(command, "status", "completed");
+
+        setstatus(NULL, status);
+        g_free(status);
+        xmlnode_put_attrib(iq, "type", "result");
+        xmlnode_insert_dataform_result_message(command, "Status was changed");
+      }
+    }
+  }
+  xmlnode_put_attrib(iq, "to", xmlnode_get_attrib(xmldata, "from"));
+  xmlnode_put_attrib(iq, "id", id);
+  jab_send(jc, iq);
+  xmlnode_free(iq);
+}
+
+static void _callback_foreach_buddy_groupchat(gpointer rosterdata, void *param)
+{
+  xmlnode value;
+  xmlnode *field;
+  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;
+
+  xmlnode option = xmlnode_insert_tag(*field, "option");
+  value = xmlnode_insert_tag(option, "value");
+  xmlnode_insert_cdata(value, room_jid, -1);
+  desc = g_strdup_printf("%s on %s", nickname, room_jid);
+  xmlnode_put_attrib(option, "label", desc);
+  g_free(desc);
+}
+
+static void handle_iq_command_leave_groupchats(jconn conn, char *from, const char *id,
+                                          xmlnode xmldata)
+{
+  char *action, *node, *sessionid;
+  xmlnode iq, command, x;
+
+  x = xmlnode_get_tag(xmldata, "command");
+  action = xmlnode_get_attrib(x, "action");
+  node = xmlnode_get_attrib(x, "node");
+  sessionid = xmlnode_get_attrib(x, "sessionid");
+
+  iq = xmlnode_new_tag("iq");
+  command = xmlnode_insert_tag(iq, "command");
+  xmlnode_put_attrib(command, "node", node);
+  xmlnode_put_attrib(command, "xmlns", NS_COMMANDS);
+
+  if (!sessionid) {
+    sessionid = generate_session_id("leave-groupchats");
+    xmlnode_put_attrib(command, "sessionid", sessionid);
+    g_free(sessionid);
+    xmlnode_put_attrib(command, "status", "executing");
+
+    x = xmlnode_insert_tag(command, "x");
+    xmlnode_put_attrib(x, "type", "form");
+    xmlnode_put_attrib(x, "xmlns", "jabber:x:data");
+
+    xmlnode title = xmlnode_insert_tag(x, "title");
+    xmlnode_insert_cdata(title, "Leave groupchats", -1);
+
+    xmlnode instructions = xmlnode_insert_tag(x, "instructions");
+    xmlnode_insert_cdata(instructions, "What groupchats do you want to leave ?",
+                         -1);
+
+    xmlnode field = xmlnode_insert_tag(x, "field");
+    xmlnode_put_attrib(field, "type", "hidden");
+    xmlnode_put_attrib(field, "var", "FORM_TYPE");
+
+    xmlnode value = xmlnode_insert_tag(field, "value");
+    xmlnode_insert_cdata(value, "http://jabber.org/protocol/rc", -1);
+
+    field = xmlnode_insert_tag(x, "field");
+    xmlnode_put_attrib(field, "type", "list-multi");
+    xmlnode_put_attrib(field, "var", "groupchats");
+    xmlnode_put_attrib(field, "label", "Groupchats : ");
+    xmlnode_insert_tag(field, "required");
+
+    foreach_buddy(ROSTER_TYPE_ROOM, &_callback_foreach_buddy_groupchat, &field);
+  }
+  else // (if sessionid)
+  {
+    xmlnode form = xmlnode_get_tag(x, "x?xmlns=jabber:x:data");
+    if (form) {
+      xmlnode_put_attrib(command, "status", "completed");
+      xmlnode gc = xmlnode_get_tag(form, "field?var=groupchats");
+      xmlnode x;
+
+      for (x = xmlnode_get_firstchild(gc) ; x ; x = xmlnode_get_nextsibling(x)) {
+        char* to_leave = xmlnode_get_tag_data(x, "value");
+        if (to_leave) {
+          GList* b = buddy_search_jid(to_leave);
+          if (b)
+            room_leave(b->data, "Asked by remote command");
+        }
+      }
+      xmlnode_put_attrib(iq, "type", "result");
+      xmlnode_insert_dataform_result_message(command, "Groupchats were leaved");
+    }
+  }
+  xmlnode_put_attrib(iq, "to", xmlnode_get_attrib(xmldata, "from"));
+  xmlnode_put_attrib(iq, "id", id);
+  jab_send(jc, iq);
+  xmlnode_free(iq);
+}
+
+static void handle_iq_commands(jconn conn, char *from, const char *id,
+                               xmlnode xmldata)
+{
+  jid requester_jid;
+  xmlnode x;
+  const struct adhoc_command *command;
+
+  requester_jid = jid_new(conn->p, xmlnode_get_attrib(xmldata, "from"));
+  x = xmlnode_get_tag(xmldata, "command");
+  if (!jid_cmpx(conn->user, requester_jid, JID_USER | JID_SERVER) ) {
+    char *action, *node;
+    action = xmlnode_get_attrib(x, "action");
+    node = xmlnode_get_attrib(x, "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(conn, from, id, xmldata);
+      }
+      // "prev" action will get there, as we do not implement it, and do not autorise it
+    } else {
+      send_iq_commands_malformed_action(conn, from, xmldata);
+    }
+  } else {
+    send_iq_forbidden(conn, from, xmldata);
+  }
+}
+
+static void handle_iq_disco_items(jconn conn, char *from, const char *id,
+                                  xmlnode xmldata)
+{
+  xmlnode x;
+  char *node;
+  x = xmlnode_get_tag(xmldata, "query");
+  node = xmlnode_get_attrib(x, "node");
+  if (node) {
+    if (!strcmp(node, NS_COMMANDS)) {
+      handle_iq_commands_list(conn, from, id, xmldata);
+    } else {
+      send_iq_not_implemented(conn, from, xmldata);
+    }
+  } else {
+    // not sure about this one
+    send_iq_not_implemented(conn, from, xmldata);
+  }
+}
+
 static void handle_iq_disco_info(jconn conn, char *from, const char *id,
                                  xmlnode xmldata)
 {
@@ -822,6 +1242,8 @@
                      "var", NS_VERSION);
   xmlnode_put_attrib(xmlnode_insert_tag(myquery, "feature"),
                      "var", NS_PING);
+  xmlnode_put_attrib(xmlnode_insert_tag(myquery, "feature"),
+                     "var", NS_COMMANDS);
   jab_send(jc, x);
   xmlnode_free(x);
 }
@@ -919,7 +1341,7 @@
 static void handle_iq_get(jconn conn, char *from, xmlnode xmldata)
 {
   const char *id, *ns;
-  xmlnode x, y, z;
+  xmlnode x;
   guint iq_not_implemented = FALSE;
 
   id = xmlnode_get_attrib(xmldata, "id");
@@ -939,6 +1361,8 @@
   ns = xmlnode_get_attrib(x, "xmlns");
   if (ns && !strcmp(ns, NS_DISCO_INFO)) {
     handle_iq_disco_info(conn, from, id, xmldata);
+  } else if (ns && !strcmp(ns, NS_DISCO_ITEMS)) {
+    handle_iq_disco_items(conn, from, id, xmldata);
   } else if (ns && !strcmp(ns, NS_VERSION)) {
     handle_iq_version(conn, from, id, xmldata);
   } else if (ns && !strcmp(ns, NS_TIME)) {
@@ -950,26 +1374,13 @@
   if (!iq_not_implemented)
     return;
 
-  // Not implemented.
-  x = xmlnode_dup(xmldata);
-  xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
-  xmlnode_hide_attrib(x, "from");
-
-  xmlnode_put_attrib(x, "type", TMSG_ERROR);
-  y = xmlnode_insert_tag(x, TMSG_ERROR);
-  xmlnode_put_attrib(y, "code", "501");
-  xmlnode_put_attrib(y, "type", "cancel");
-  z = xmlnode_insert_tag(y, "feature-not-implemented");
-  xmlnode_put_attrib(z, "xmlns", NS_XMPP_STANZAS);
-
-  jab_send(conn, x);
-  xmlnode_free(x);
+  send_iq_not_implemented(conn, from, xmldata);
 }
 
 static void handle_iq_set(jconn conn, char *from, xmlnode xmldata)
 {
   const char *id, *ns;
-  xmlnode x, y, z;
+  xmlnode x;
   guint iq_not_implemented = FALSE;
 
   id = xmlnode_get_attrib(xmldata, "id");
@@ -981,7 +1392,13 @@
   if (ns && !strcmp(ns, NS_ROSTER)) {
     handle_iq_roster(x);
   } else {
-    iq_not_implemented = TRUE;
+    x = xmlnode_get_tag(xmldata, "command");
+    ns = xmlnode_get_attrib(x, "xmlns");
+    if (ns && !strcmp(ns, NS_COMMANDS)) {
+      handle_iq_commands(conn, from, id, xmldata);
+    } else {
+      iq_not_implemented = TRUE;
+    }
   }
 
   if (!id) return;
@@ -991,22 +1408,11 @@
     xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
     xmlnode_put_attrib(x, "type", "result");
     xmlnode_put_attrib(x, "id", id);
+    jab_send(conn, x);
+    xmlnode_free(x);
   } else {
-    /* Not implemented yet: send an error stanza */
-    x = xmlnode_dup(xmldata);
-    xmlnode_put_attrib(x, "to", xmlnode_get_attrib(xmldata, "from"));
-    xmlnode_hide_attrib(x, "from");
-    xmlnode_put_attrib(x, "type", "result");
-    xmlnode_put_attrib(x, "type", TMSG_ERROR);
-    y = xmlnode_insert_tag(x, TMSG_ERROR);
-    xmlnode_put_attrib(y, "code", "501");
-    xmlnode_put_attrib(y, "type", "cancel");
-    z = xmlnode_insert_tag(y, "feature-not-implemented");
-    xmlnode_put_attrib(z, "xmlns", NS_XMPP_STANZAS);
+    send_iq_not_implemented(conn, from, xmldata);
   }
-
-  jab_send(conn, x);
-  xmlnode_free(x);
 }
 
 void handle_packet_iq(jconn conn, char *type, char *from, xmlnode xmldata)