# HG changeset patch # User misc@mandriva.org # Date 1171572579 -3600 # Node ID c30c315dc447a300f1630f8cd55ff20a4960e2a2 # Parent 5c857f0f0ab855d313187af5a50c956ca938362e XEP-0146 support (Remote Controlling Clients) diff -r 5c857f0f0ab8 -r c30c315dc447 mcabber/libjabber/jabber.h --- 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" diff -r 5c857f0f0ab8 -r c30c315dc447 mcabber/src/commands.c --- 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; diff -r 5c857f0f0ab8 -r c30c315dc447 mcabber/src/commands.h --- 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__ */ diff -r 5c857f0f0ab8 -r c30c315dc447 mcabber/src/jab_iq.c --- 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)