--- 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)