From 26daf5086340b314f25323eb79e5b5b479b49637 Mon Sep 17 00:00:00 2001 From: Jeff Peeler <jpeeler@digium.com> Date: Mon, 7 Dec 2009 17:59:46 +0000 Subject: [PATCH] Add applications JabberJoin, JabberLeave, JabberSendGroup for XMPP groupchat (closes issue #14352) Reported by: fiddur Patches: trunk-14352-2.diff uploaded by phsultan (license 73) Tested by: fiddur git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@233468 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- CHANGES | 2 + include/asterisk/jabber.h | 10 +- res/res_jabber.c | 463 +++++++++++++++++++++++++++++--------- 3 files changed, 370 insertions(+), 105 deletions(-) diff --git a/CHANGES b/CHANGES index 69789fb3f9..0a3b2f07e8 100644 --- a/CHANGES +++ b/CHANGES @@ -111,6 +111,8 @@ Applications using the imapfolder option. * Voicemail now allows the pager date format to be specified separately from the email date format. + * New applications JabberJoin, JabberLeave, and JabberSendGroup have been added + to allow joining, leaving, and sending text to group chats. Dialplan Functions ------------------ diff --git a/include/asterisk/jabber.h b/include/asterisk/jabber.h index d641900445..da43463052 100644 --- a/include/asterisk/jabber.h +++ b/include/asterisk/jabber.h @@ -73,6 +73,8 @@ #define AJI_MAX_JIDLEN 3071 #define AJI_MAX_RESJIDLEN 1023 +#define MUC_NS "http://jabber.org/protocol/muc" + enum aji_state { AJI_DISCONNECTING, AJI_DISCONNECTED, @@ -185,6 +187,9 @@ struct aji_client_container{ int ast_aji_send(struct aji_client *client, iks *x); /*! Send jabber chat message from connected client to jabber URI */ int ast_aji_send_chat(struct aji_client *client, const char *address, const char *message); +/*! Send jabber chat message from connected client to a groupchat using + * a given nickname */ +int ast_aji_send_groupchat(struct aji_client *client, const char *nick, const char *address, const char *message); /*! Disconnect jabber client */ int ast_aji_disconnect(struct aji_client *client); int ast_aji_check_roster(void); @@ -193,8 +198,9 @@ void ast_aji_increment_mid(char *mid); int ast_aji_create_chat(struct aji_client *client,char *room, char *server, char *topic); /*! Invite to opened Chat session */ int ast_aji_invite_chat(struct aji_client *client, char *user, char *room, char *message); -/*! Join existing Chat session */ -int ast_aji_join_chat(struct aji_client *client,char *room); +/*! Join/leave existing Chat session */ +int ast_aji_join_chat(struct aji_client *client, char *room, char *nick); +int ast_aji_leave_chat(struct aji_client *client, char *room, char *nick); struct aji_client *ast_aji_get_client(const char *name); struct aji_client_container *ast_aji_get_clients(void); diff --git a/res/res_jabber.c b/res/res_jabber.c index 9e619d2832..01f9adbdc7 100644 --- a/res/res_jabber.c +++ b/res/res_jabber.c @@ -34,6 +34,31 @@ <depend>iksemel</depend> <use>openssl</use> ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <ctype.h> +#include <iksemel.h> + +#include "asterisk/channel.h" +#include "asterisk/jabber.h" +#include "asterisk/file.h" +#include "asterisk/config.h" +#include "asterisk/callerid.h" +#include "asterisk/lock.h" +#include "asterisk/cli.h" +#include "asterisk/app.h" +#include "asterisk/pbx.h" +#include "asterisk/md5.h" +#include "asterisk/acl.h" +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/astobj.h" +#include "asterisk/astdb.h" +#include "asterisk/manager.h" + /*** DOCUMENTATION <application name="JabberSend" language="en_US"> <synopsis> @@ -124,114 +149,87 @@ <ref type="application">JabberSend</ref> </see-also> </function> - ***/ - -#include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - -#include <ctype.h> -#include <iksemel.h> - -#include "asterisk/channel.h" -#include "asterisk/jabber.h" -#include "asterisk/file.h" -#include "asterisk/config.h" -#include "asterisk/callerid.h" -#include "asterisk/lock.h" -#include "asterisk/cli.h" -#include "asterisk/app.h" -#include "asterisk/pbx.h" -#include "asterisk/md5.h" -#include "asterisk/acl.h" -#include "asterisk/utils.h" -#include "asterisk/module.h" -#include "asterisk/astobj.h" -#include "asterisk/astdb.h" -#include "asterisk/manager.h" - -/*** DOCUMENTATION - <application name="JabberSend" language="en_US"> + <application name="JabberSendGroup" language="en_US"> <synopsis> - Send a Jabber Message + Send a Jabber Message to a specified chat room </synopsis> <syntax> <parameter name="Jabber" required="true"> <para>Client or transport Asterisk uses to connect to Jabber.</para> </parameter> - <parameter name="JID" required="true"> - <para>XMPP/Jabber JID (Name) of recipient.</para> + <parameter name="RoomJID" required="true"> + <para>XMPP/Jabber JID (Name) of chat room.</para> </parameter> <parameter name="Message" required="true"> - <para>Message to be sent to the buddy.</para> + <para>Message to be sent to the chat room.</para> + </parameter> + <parameter name="Nickname" required="false"> + <para>The nickname Asterisk uses in the chat room.</para> </parameter> </syntax> <description> - <para>Allows user to send a message to a receipent via XMPP.</para> + <para>Allows user to send a message to a chat room via XMPP.</para> + <note><para>To be able to send messages to a chat room, a user must have previously joined it. Use the <replaceable>JabberJoin</replaceable> function to do so.</para></note> </description> </application> - <application name="JabberStatus" language="en_US"> + <application name="JabberJoin" language="en_US"> <synopsis> - Retrieve the status of a jabber list member + <para>Join a chat room</para> </synopsis> <syntax> <parameter name="Jabber" required="true"> - <para>Client or transport Asterisk users to connect to Jabber.</para> + <para>Client or transport Asterisk uses to connect to Jabber.</para> </parameter> - <parameter name="JID" required="true"> - <para>XMPP/Jabber JID (Name) of recipient.</para> + <parameter name="RoomJID" required="true"> + <para>XMPP/Jabber JID (Name) of chat room.</para> </parameter> - <parameter name="Variable" required="true"> - <para>Variable to store the status of requested user.</para> + <parameter name="Nickname" required="false"> + <para>The nickname Asterisk will use in the chat room.</para> + <note><para>If a different nickname is supplied to an already joined room, the old nick will be changed to the new one.</para></note> </parameter> </syntax> <description> - <para>This application is deprecated. Please use the JABBER_STATUS() function instead.</para> - <para>Retrieves the numeric status associated with the specified buddy <replaceable>JID</replaceable>. - The return value in the <replaceable>Variable</replaceable>will be one of the following.</para> - <enumlist> - <enum name="1"> - <para>Online.</para> - </enum> - <enum name="2"> - <para>Chatty.</para> - </enum> - <enum name="3"> - <para>Away.</para> - </enum> - <enum name="4"> - <para>Extended Away.</para> - </enum> - <enum name="5"> - <para>Do Not Disturb.</para> - </enum> - <enum name="6"> - <para>Offline.</para> - </enum> - <enum name="7"> - <para>Not In Roster.</para> - </enum> - </enumlist> + <para>Allows Asterisk to join a chat room.</para> </description> </application> - <function name="JABBER_STATUS" language="en_US"> + <application name="JabberLeave" language="en_US"> + <synopsis> + <para>Leave a chat room</para> + </synopsis> + <syntax> + <parameter name="Jabber" required="true"> + <para>Client or transport Asterisk uses to connect to Jabber.</para> + </parameter> + <parameter name="RoomJID" required="true"> + <para>XMPP/Jabber JID (Name) of chat room.</para> + </parameter> + <parameter name="Nickname" required="false"> + <para>The nickname Asterisk uses in the chat room.</para> + </parameter> + </syntax> + <description> + <para>Allows Asterisk to leave a chat room.</para> + </description> + </application> + <application name="JabberStatus" language="en_US"> <synopsis> Retrieve the status of a jabber list member </synopsis> <syntax> - <parameter name="sender" required="true"> - <para>XMPP/Jabber ID (Name) of sender.</para> + <parameter name="Jabber" required="true"> + <para>Client or transport Asterisk users to connect to Jabber.</para> </parameter> - <parameter name="buddy" required="true"> + <parameter name="JID" required="true"> <para>XMPP/Jabber JID (Name) of recipient.</para> </parameter> - <parameter name="resource"> - <para>Client or transport Asterisk users to connect to Jabber.</para> + <parameter name="Variable" required="true"> + <para>Variable to store the status of requested user.</para> </parameter> </syntax> <description> + <para>This application is deprecated. Please use the JABBER_STATUS() function instead.</para> <para>Retrieves the numeric status associated with the specified buddy <replaceable>JID</replaceable>. - The return value will be one of the following.</para> + The return value in the <replaceable>Variable</replaceable>will be one of the following.</para> <enumlist> <enum name="1"> <para>Online.</para> @@ -256,7 +254,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </enum> </enumlist> </description> - </function> + </application> <manager name="JabberSend" language="en_US"> <synopsis> Sends a message to a Jabber Client. @@ -303,10 +301,12 @@ static void aji_handle_iq(struct aji_client *client, iks *node); static void aji_handle_message(struct aji_client *client, ikspak *pak); static void aji_handle_presence(struct aji_client *client, ikspak *pak); static void aji_handle_subscribe(struct aji_client *client, ikspak *pak); +static int aji_send_raw_chat(struct aji_client *client, int groupchat, const char *nick, const char *address, const char *message); static void *aji_recv_loop(void *data); static int aji_initialize(struct aji_client *client); static int aji_client_connect(void *data, ikspak *pak); static void aji_set_presence(struct aji_client *client, char *to, char *from, int level, char *desc); +static int aji_set_group_presence(struct aji_client *client, char *room, int level, char *nick, char *desc); static char *aji_do_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *aji_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *aji_show_clients(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); @@ -342,8 +342,10 @@ static struct ast_cli_entry aji_cli[] = { }; static char *app_ajisend = "JabberSend"; - +static char *app_ajisendgroup = "JabberSendGroup"; static char *app_ajistatus = "JabberStatus"; +static char *app_ajijoin = "JabberJoin"; +static char *app_ajileave = "JabberLeave"; static struct aji_client_container clients; static struct aji_capabilities *capabilities = NULL; @@ -908,6 +910,125 @@ static int delete_old_messages_all(struct aji_client *client) return delete_old_messages(client, NULL); } +/*! +* \brief Application to join a chat room +* \param chan ast_channel +* \param data Data is sender|jid|nickname. +* \retval 0 success +* \retval -1 error +*/ +static int aji_join_exec(struct ast_channel *chan, const char *data) +{ + struct aji_client *client = NULL; + char *s; + char nick[AJI_MAX_RESJIDLEN]; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + AST_APP_ARG(nick); + ); + + if (!data) { + ast_log(LOG_ERROR, "%s requires arguments (sender,jid[,nickname])\n", app_ajijoin); + return -1; + } + s = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, s); + if (args.argc < 2 || args.argc > 3) { + ast_log(LOG_ERROR, "%s requires arguments (sender,jid[,nickname])\n", app_ajijoin); + return -1; + } + + if (!(client = ast_aji_get_client(args.sender))) { + ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + if (strchr(args.jid, '/')) { + ast_log(LOG_ERROR, "Invalid room name : resource must not be appended\n"); + ASTOBJ_UNREF(client, aji_client_destroy); + return -1; + } + + if (!ast_strlen_zero(args.nick)) { + snprintf(nick, AJI_MAX_RESJIDLEN, "%s", args.nick); + } else { + if (client->component) { + sprintf(nick, "asterisk"); + } else { + snprintf(nick, AJI_MAX_RESJIDLEN, "%s", client->jid->user); + } + } + + if (!ast_strlen_zero(args.jid) && strchr(args.jid, '@')) { + ast_aji_join_chat(client, args.jid, nick); + } else { + ast_log(LOG_ERROR, "Problem with specified jid of '%s'\n", args.jid); + } + + ASTOBJ_UNREF(client, aji_client_destroy); + return 0; +} + +/*! +* \brief Application to leave a chat room +* \param chan ast_channel +* \param data Data is sender|jid|nickname. +* \retval 0 success +* \retval -1 error +*/ +static int aji_leave_exec(struct ast_channel *chan, const char *data) +{ + struct aji_client *client = NULL; + char *s; + char nick[AJI_MAX_RESJIDLEN]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + AST_APP_ARG(nick); + ); + + if (!data) { + ast_log(LOG_ERROR, "%s requires arguments (sender,jid[,nickname])\n", app_ajileave); + return -1; + } + s = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, s); + if (args.argc < 2 || args.argc > 3) { + ast_log(LOG_ERROR, "%s requires arguments (sender,jid[,nickname])\n", app_ajileave); + return -1; + } + + if (!(client = ast_aji_get_client(args.sender))) { + ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + if (strchr(args.jid, '/')) { + ast_log(LOG_ERROR, "Invalid room name, resource must not be appended\n"); + ASTOBJ_UNREF(client, aji_client_destroy); + return -1; + } + if (!ast_strlen_zero(args.nick)) { + snprintf(nick, AJI_MAX_RESJIDLEN, "%s", args.nick); + } else { + if (client->component) { + sprintf(nick, "asterisk"); + } else { + snprintf(nick, AJI_MAX_RESJIDLEN, "%s", client->jid->user); + } + } + + if (!ast_strlen_zero(args.jid) && strchr(args.jid, '@')) { + ast_aji_leave_chat(client, args.jid, nick); + } + ASTOBJ_UNREF(client, aji_client_destroy); + return 0; +} + /*! * \internal * \brief Dial plan function to send a message. @@ -948,6 +1069,64 @@ static int aji_send_exec(struct ast_channel *chan, const char *data) return 0; } +/*! +* \brief Application to send a message to a groupchat. +* \param chan ast_channel +* \param data Data is sender|groupchat|message. +* \retval 0 success +* \retval -1 error +*/ +static int aji_sendgroup_exec(struct ast_channel *chan, const char *data) +{ + struct aji_client *client = NULL; + char *s; + char nick[AJI_MAX_RESJIDLEN]; + int res = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(groupchat); + AST_APP_ARG(message); + AST_APP_ARG(nick); + ); + + if (!data) { + ast_log(LOG_ERROR, "%s requires arguments (sender,groupchatid,message[,nickname])\n", app_ajisendgroup); + return -1; + } + s = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, s); + if (args.argc < 3 || args.argc > 4) { + ast_log(LOG_ERROR, "%s requires arguments (sender,groupchatid,message[,nickname])\n", app_ajisendgroup); + return -1; + } + + if (!(client = ast_aji_get_client(args.sender))) { + ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + if (ast_strlen_zero(args.nick) || args.argc == 3) { + if (client->component) { + sprintf(nick, "asterisk"); + } else { + snprintf(nick, AJI_MAX_RESJIDLEN, "%s", client->jid->user); + } + } else { + snprintf(nick, AJI_MAX_RESJIDLEN, "%s", args.nick); + } + + if (strchr(args.groupchat, '@') && !ast_strlen_zero(args.message)) { + res = ast_aji_send_groupchat(client, nick, args.groupchat, args.message); + } + + ASTOBJ_UNREF(client, aji_client_destroy); + if (res != IKS_OK) { + return -1; + } + return 0; +} + /*! * \internal * \brief Tests whether the connection is secured or not @@ -2271,25 +2450,57 @@ static void aji_handle_subscribe(struct aji_client *client, ikspak *pak) * \retval -1 failure */ int ast_aji_send_chat(struct aji_client *client, const char *address, const char *message) +{ + return aji_send_raw_chat(client, 0, NULL, address, message); +} + +/*! +* \brief sends message to a groupchat +* Prior to sending messages to a groupchat, one must be connected to it. +* \param client the configured XMPP client we use to connect to a XMPP server +* \param nick the nickname we use in the chatroom +* \param address the user the messages must be sent to +* \param message the message to send +* \return IKS_OK on success, any other value on failure +*/ +int ast_aji_send_groupchat(struct aji_client *client, const char *nick, const char *address, const char *message) { + return aji_send_raw_chat(client, 1, nick, address, message); +} + +/*! +* \brief sends messages. +* \param client the configured XMPP client we use to connect to a XMPP server +* \param nick the nickname we use in chatrooms +* \param address +* \param message +* \return IKS_OK on success, any other value on failure +*/ +static int aji_send_raw_chat(struct aji_client *client, int groupchat, const char *nick, const char *address, const char *message) { int res = 0; iks *message_packet = NULL; - + char from[AJI_MAX_JIDLEN]; + /* the nickname is used only in component mode */ + if (nick && client->component) { + snprintf(from, AJI_MAX_JIDLEN, "%s@%s/%s", nick, client->jid->full, nick); + } else { + snprintf(from, AJI_MAX_JIDLEN, "%s", client->jid->full); + } + if (client->state != AJI_CONNECTED) { ast_log(LOG_WARNING, "JABBER: Not connected can't send\n"); return -1; - } - - message_packet = iks_make_msg(IKS_TYPE_CHAT, address, message); + } + + message_packet = iks_make_msg(groupchat ? IKS_TYPE_GROUPCHAT : IKS_TYPE_CHAT, address, message); if (!message_packet) { ast_log(LOG_ERROR, "Out of memory.\n"); return -1; } - - iks_insert_attrib(message_packet, "from", client->jid->full); + iks_insert_attrib(message_packet, "from", from); res = ast_aji_send(client, message_packet); iks_delete(message_packet); - + return res; } @@ -2325,31 +2536,25 @@ int ast_aji_create_chat(struct aji_client *client, char *room, char *server, cha * \brief join a chatroom. * \param client the configured XMPP client we use to connect to a XMPP server * \param room room to join - * \return res. + * \param nick the nickname to use in this room + * \return IKS_OK on success, any other value on failure. */ -int ast_aji_join_chat(struct aji_client *client, char *room) +int ast_aji_join_chat(struct aji_client *client, char *room, char *nick) { - int res = 0; - iks *presence = NULL, *priority = NULL; - presence = iks_new("presence"); - priority = iks_new("priority"); - if (presence && priority && client) { - iks_insert_cdata(priority, "0", 1); - iks_insert_attrib(presence, "to", room); - iks_insert_node(presence, priority); - res = ast_aji_send(client, presence); - iks_insert_cdata(priority, "5", 1); - iks_insert_attrib(presence, "to", room); - res = ast_aji_send(client, presence); - } else - ast_log(LOG_ERROR, "Out of memory.\n"); - - iks_delete(presence); - iks_delete(priority); - - return res; + return aji_set_group_presence(client, room, IKS_SHOW_AVAILABLE, nick, NULL); } +/*! + * \brief leave a chatroom. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param room room to leave + * \param nick the nickname used in this room + * \return IKS_OK on success, any other value on failure. + */ +int ast_aji_leave_chat(struct aji_client *client, char *room, char *nick) +{ + return aji_set_group_presence(client, room, IKS_SHOW_UNAVAILABLE, nick, NULL); +} /*! * \brief invite to a chatroom. * \param client the configured XMPP client we use to connect to a XMPP server @@ -2856,6 +3061,52 @@ static void aji_set_presence(struct aji_client *client, char *to, char *from, in iks_delete(priority); } +/* +* \brief set the presence of the client in a groupchat context. +* \param client the configured XMPP client we use to connect to a XMPP server +* \param room the groupchat identifier in the from roomname@service +* \param from user it came from +* \param level the type of action, i.e. join or leave the chatroom +* \param nick the nickname to use in the chatroom +* \param desc a text that details the action to be taken +* \return res. +*/ +static int aji_set_group_presence(struct aji_client *client, char *room, int level, char *nick, char *desc) +{ + int res = 0; + iks *presence = NULL, *x = NULL; + char from[AJI_MAX_JIDLEN]; + char roomid[AJI_MAX_JIDLEN]; + + presence = iks_make_pres(level, NULL); + x = iks_new("x"); + + if (client->component) { + snprintf(from, AJI_MAX_JIDLEN, "%s@%s/%s", nick, client->jid->full, nick); + snprintf(roomid, AJI_MAX_JIDLEN, "%s/%s", room, nick); + } else { + snprintf(from, AJI_MAX_JIDLEN, "%s", client->jid->full); + snprintf(roomid, AJI_MAX_JIDLEN, "%s/%s", room, nick ? nick : client->jid->user); + } + + if (!presence || !x || !client) { + ast_log(LOG_ERROR, "Out of memory.\n"); + res = -1; + goto safeout; + } else { + iks_insert_attrib(presence, "to", roomid); + iks_insert_attrib(presence, "from", from); + iks_insert_attrib(x, "xmlns", MUC_NS); + iks_insert_node(presence, x); + res = ast_aji_send(client, presence); + } + +safeout: + iks_delete(presence); + iks_delete(x); + return res; +} + /*! * \internal * \brief Turn on/off console debugging. @@ -3491,7 +3742,10 @@ static int unload_module(void) ast_cli_unregister_multiple(aji_cli, ARRAY_LEN(aji_cli)); ast_unregister_application(app_ajisend); + ast_unregister_application(app_ajisendgroup); ast_unregister_application(app_ajistatus); + ast_unregister_application(app_ajijoin); + ast_unregister_application(app_ajileave); ast_manager_unregister("JabberSend"); ast_custom_function_unregister(&jabberstatus_function); ast_custom_function_unregister(&jabberreceive_function); @@ -3525,7 +3779,10 @@ static int load_module(void) return AST_MODULE_LOAD_DECLINE; ast_manager_register_xml("JabberSend", EVENT_FLAG_SYSTEM, manager_jabber_send); ast_register_application_xml(app_ajisend, aji_send_exec); + ast_register_application_xml(app_ajisendgroup, aji_sendgroup_exec); ast_register_application_xml(app_ajistatus, aji_status_exec); + ast_register_application_xml(app_ajijoin, aji_join_exec); + ast_register_application_xml(app_ajileave, aji_leave_exec); ast_cli_register_multiple(aji_cli, ARRAY_LEN(aji_cli)); ast_custom_function_register(&jabberstatus_function); ast_custom_function_register(&jabberreceive_function); -- GitLab