From 26daf5086340b314f25323eb79e5b5b479b49637 Mon Sep 17 00:00:00 2001 From: Jeff Peeler 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 | 469 +++++++++++++++++++++++++++++--------- 3 files changed, 373 insertions(+), 108 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 @@ iksemel openssl ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include + +#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 @@ -124,50 +149,66 @@ JabberSend - ***/ - -#include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - -#include -#include - -#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 - + - Send a Jabber Message + Send a Jabber Message to a specified chat room Client or transport Asterisk uses to connect to Jabber. - - XMPP/Jabber JID (Name) of recipient. + + XMPP/Jabber JID (Name) of chat room. - Message to be sent to the buddy. + Message to be sent to the chat room. + + + The nickname Asterisk uses in the chat room. - Allows user to send a message to a receipent via XMPP. + Allows user to send a message to a chat room via XMPP. + To be able to send messages to a chat room, a user must have previously joined it. Use the JabberJoin function to do so. + + + + + Join a chat room + + + + Client or transport Asterisk uses to connect to Jabber. + + + XMPP/Jabber JID (Name) of chat room. + + + The nickname Asterisk will use in the chat room. + If a different nickname is supplied to an already joined room, the old nick will be changed to the new one. + + + + Allows Asterisk to join a chat room. + + + + + Leave a chat room + + + + Client or transport Asterisk uses to connect to Jabber. + + + XMPP/Jabber JID (Name) of chat room. + + + The nickname Asterisk uses in the chat room. + + + + Allows Asterisk to leave a chat room. @@ -213,50 +254,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - - - - Retrieve the status of a jabber list member - - - - XMPP/Jabber ID (Name) of sender. - - - XMPP/Jabber JID (Name) of recipient. - - - Client or transport Asterisk users to connect to Jabber. - - - - Retrieves the numeric status associated with the specified buddy JID. - The return value will be one of the following. - - - Online. - - - Chatty. - - - Away. - - - Extended Away. - - - Do Not Disturb. - - - Offline. - - - Not In Roster. - - - - + 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);