From 5bec5836a0eca318b6211aaa7a36e20df1c3e978 Mon Sep 17 00:00:00 2001 From: phsultan Date: Fri, 25 Sep 2009 10:54:42 +0000 Subject: [PATCH] Add JABBER_RECEIVE as a dialplan function, implement SendText in Jingle channels JABBER_RECEIVE (along with JabberSend) makes Asterisk interact with users over XMPP to process calls. SendText can be used instead of JabberSend in the context of XMPP based voice channels (chan_gtalk and chan_jingle). (closes issue #12569) Reported by: eech55 Tested by: phsultan, asannucci, lmadsen, jtodd, maxgo Review: https://reviewboard.asterisk.org/r/88/ git-svn-id: http://svn.digium.com/svn/asterisk/trunk@220457 f38db490-d61c-443f-a65b-d21fe96a405b --- CHANGES | 9 +- channels/chan_gtalk.c | 21 ++ channels/chan_jingle.c | 22 ++ configs/jabber.conf.sample | 5 +- doc/jabber.txt | 118 ++++++- include/asterisk/jabber.h | 2 +- res/res_jabber.c | 679 ++++++++++++++++++++++++++++++------- 7 files changed, 720 insertions(+), 136 deletions(-) diff --git a/CHANGES b/CHANGES index 7358bf0cd..5062283a9 100644 --- a/CHANGES +++ b/CHANGES @@ -117,6 +117,8 @@ Dialplan Functions mode=multirow. If rowlimit is set, then additional rows may be retrieved from the same query by using the name of the function which retrieved the first row as an argument to ODBC_FETCH(). + * Added JABBER_RECEIVE, which permits receiving XMPP messages from the + dialplan. This function returns the content of the received message. Dialplan Variables ------------------ @@ -265,6 +267,11 @@ Security Events Framework coming soon. For more information on the security events framework, see the "Security Events" chapter of the included documentation - doc/tex/asterisk.pdf. +Miscellaneous +------------- + * SendText is now implemented in chan_gtalk and chan_jingle. It will simply send + XMPP text messages to the remote JID. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 1.6.1 to Asterisk 1.6.2 ------------- ------------------------------------------------------------------------------ @@ -414,7 +421,7 @@ Miscellaneous can connect calls in passthrough mode, as well as record and play back files. * Successful and unsuccessful call pickup can now be alerted through sounds, by using pickupsound and pickupfailsound in features.conf. - * ASTVARRUNDIR is now set to $(localstatedir)/run/asterisk by default. + * ASTVARRUNDIR is now set to $(localstatedir)/run/asterisk by default. This means the asterisk pid file will now be in /var/run/asterisk/asterisk.pid on LINUX instead of the /var/run/asterisk.pid where it used to be. This will make installs as non-root easier to manage. diff --git a/channels/chan_gtalk.c b/channels/chan_gtalk.c index a0cc42408..25fdc2c63 100644 --- a/channels/chan_gtalk.c +++ b/channels/chan_gtalk.c @@ -168,6 +168,7 @@ AST_MUTEX_DEFINE_STATIC(gtalklock); /*!< Protect the interface list (of gtalk_pv /* Forward declarations */ static struct ast_channel *gtalk_request(const char *type, int format, const struct ast_channel *requestor, void *data, int *cause); static int gtalk_digit(struct ast_channel *ast, char digit, unsigned int duration); +static int gtalk_sendtext(struct ast_channel *ast, const char *text); static int gtalk_digit_begin(struct ast_channel *ast, char digit); static int gtalk_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int gtalk_call(struct ast_channel *ast, char *dest, int timeout); @@ -191,6 +192,7 @@ static const struct ast_channel_tech gtalk_tech = { .description = "Gtalk Channel Driver", .capabilities = AST_FORMAT_AUDIO_MASK, .requester = gtalk_request, + .send_text = gtalk_sendtext, .send_digit_begin = gtalk_digit_begin, .send_digit_end = gtalk_digit_end, .bridge = ast_rtp_instance_bridge, @@ -1499,6 +1501,25 @@ static int gtalk_indicate(struct ast_channel *ast, int condition, const void *da return res; } +static int gtalk_sendtext(struct ast_channel *chan, const char *text) +{ + int res = 0; + struct aji_client *client = NULL; + struct gtalk_pvt *p = chan->tech_pvt; + + if (!p->parent) { + ast_log(LOG_ERROR, "Parent channel not found\n"); + return -1; + } + if (!p->parent->connection) { + ast_log(LOG_ERROR, "XMPP client not found\n"); + return -1; + } + client = p->parent->connection; + res = ast_aji_send_chat(client, p->them, text); + return res; +} + static int gtalk_digit_begin(struct ast_channel *chan, char digit) { return gtalk_digit(chan, digit, 0); diff --git a/channels/chan_jingle.c b/channels/chan_jingle.c index 71113f621..1461b83c7 100644 --- a/channels/chan_jingle.c +++ b/channels/chan_jingle.c @@ -169,6 +169,7 @@ AST_MUTEX_DEFINE_STATIC(jinglelock); /*!< Protect the interface list (of jingle_ /* Forward declarations */ static struct ast_channel *jingle_request(const char *type, int format, const struct ast_channel *requestor, void *data, int *cause); +static int jingle_sendtext(struct ast_channel *ast, const char *text); static int jingle_digit_begin(struct ast_channel *ast, char digit); static int jingle_digit_end(struct ast_channel *ast, char digit, unsigned int duration); static int jingle_call(struct ast_channel *ast, char *dest, int timeout); @@ -190,6 +191,7 @@ static const struct ast_channel_tech jingle_tech = { .description = "Jingle Channel Driver", .capabilities = AST_FORMAT_AUDIO_MASK, .requester = jingle_request, + .send_text = jingle_sendtext, .send_digit_begin = jingle_digit_begin, .send_digit_end = jingle_digit_end, .bridge = ast_rtp_instance_bridge, @@ -1273,6 +1275,26 @@ static int jingle_indicate(struct ast_channel *ast, int condition, const void *d return res; } +static int jingle_sendtext(struct ast_channel *chan, const char *text) +{ + int res = 0; + struct aji_client *client = NULL; + struct jingle_pvt *p = chan->tech_pvt; + + + if (!p->parent) { + ast_log(LOG_ERROR, "Parent channel not found\n"); + return -1; + } + if (!p->parent->connection) { + ast_log(LOG_ERROR, "XMPP client not found\n"); + return -1; + } + client = p->parent->connection; + res = ast_aji_send_chat(client, p->them, text); + return res; +} + static int jingle_digit(struct ast_channel *ast, char digit, unsigned int duration) { struct jingle_pvt *p = ast->tech_pvt; diff --git a/configs/jabber.conf.sample b/configs/jabber.conf.sample index 6cfb755bd..f46450dcf 100644 --- a/configs/jabber.conf.sample +++ b/configs/jabber.conf.sample @@ -20,4 +20,7 @@ ;; xaway, or dnd ;statusmessage="I am available" ;;Have custom status message for ;;Asterisk. -;timeout=100 ;;Timeout on the message stack. +;timeout=5 ;;Timeout (in seconds) on the message stack, defaults to 5. + ;;Messages stored longer than this value will be deleted by Asterisk. + ;;This option applies to incoming messages only, which are intended to + ;;be processed by the JABBER_RECEIVE dialplan function. diff --git a/doc/jabber.txt b/doc/jabber.txt index ca3e0f528..a8f4a9361 100644 --- a/doc/jabber.txt +++ b/doc/jabber.txt @@ -1,15 +1,107 @@ -(res_jabber is very experimental!) - -Jabber(xmpp) is an xml based protocol primarily for presence and messaging. +XMPP (Jabber) is an xml based protocol primarily for presence and messaging. It is an open standard and there are several open server implementations, -ejabberd, jabberd(2), wildfire, and many others, as well as several open source -clients, Psi, gajim, gaim etc. Jabber differs from other IM applications as it -is immensly extendable. This allows us to easily integrate Asterisk with -jabber. The Asterisk Jabber Interface is provided by res_jabber.so. res_jabber -allows for Asterisk to connect to any jabber server via the standard client -protocol or also as a simple client. Several simple functions are exposed to -the dial plan, jabberstatus, jabbersend, and soon jabberrecv. res_jabber is also used -to provide the connection interface for chan_jingle. +ejabberd, jabberd(2), openfire, and many others, as well as several open source +clients, Psi, gajim, gaim etc. XMPP differs from other IM applications as it +is immensly extendable. This allows us to easily integrate Asterisk with +XMPP. The Asterisk XMPP Interface is provided by res_jabber.so. -The maintainer of res_jabber is Matthew O'Gorman or -mog_work on irc or (preferred) mogorman@astjab.org over jabber. +res_jabber allows for Asterisk to connect to any XMPP (Jabber) server and +is also used to provide the connection interface for chan_jingle and +chan_gtalk. + +Functions (JABBER_STATUS, JABBER_RECEIVE) and applications (JabberSend) +are exposed to the dialplan. + +You'll find examples of how to use these functions/applications +hereafter. We assume that 'asterisk-xmpp' is properly configured in +jabber.conf. + +**** JabberSend **** + +JabberSend sends an XMPP message to a buddy. Example : + +context default { + _XXXX => { + JabberSend(asterisk-xmpp,buddy@gmail.com,${CALLERID(name)} is calling ${EXTEN}); + Dial(SIP/${EXTEN}, 30); + Hangup(); + } +} + +**** JABBER_STATUS **** + +Note : as of version 1.6, the corresponding application JabberStatus is still +available, but marked as deprecated in favor of this function. + +JABBER_STATUS stores the status of a buddy in a dialplan variable for +further use. Here is an AEL example of how to use it : + +1234 => { + Set(STATUS=${JABBER_STATUS(asterisk-xmpp,buddy@gmail.com)}); + if (${STATUS}=1) { + NoOp(User is online and active, ring his Gtalk client.); + Dial(Gtalk/asterisk-xmpp/buddy@gmail.com); + } else { + NoOp(Prefer the SIP phone); + Dial(SIP/1234); + } +} + +**** JABBER_RECEIVE **** + +JABBER_RECEIVE waits (up to X seconds) for a XMPP message and returns +its content. Used along with JabberSend (or SendText, +provided it's implemented in the corresponding channel type), +JABBER_RECEIVE helps Asterisk interact with users while calls flow +through the dialplan. + +JABBER_RECEIVE/JabberSend are not tied to the XMPP media modules +chan_gtalk and chan_jingle, and can be used anywhere in the dialplan. +In the following example, calls targeted to extension 1234 (be it +accessed from SIP, DAHDI or whatever channel type) are controlled by +user bob@domain.com. Asterisk notifies him that a call is coming, and +asks him to take an action. This dialog takes place over an XMPP chat. + +context from-ext { + 1234 => { + Answer(); + JabberSend(asterisk-xmpp,bob@jabber.org,Call from $CALLERID(num) - choose an option to process the call); + JabberSend(asterisk-xmpp,bob@jabber.org,1 : forward to cellphone); + JabberSend(asterisk-xmpp,bob@jabber.org,2 : forward to work phone); + JabberSend(asterisk-xmpp,bob@jabber.org,Default action : forward to your voicemail); + Set(OPTION=${JABBER_RECEIVE(asterisk-xmpp,bob@jabber.org,20)}); + switch (${OPTION}) { + case 1: + JabberSend(asterisk-xmpp,bob@jabber.org,(Calling cellphone...); + Dial(SIP/987654321); + break; + case 2: + JabberSend(asterisk-xmpp,bob@jabber.org,(Calling workphone...); + Dial(SIP/${EXTEN}); + break; + default: + Voicemail(${EXTEN}|u) + } + } +} + +When calling from a GoogleTalk or Jingle client, the CALLERID(name) +is set to the XMPP id of the caller (i.e. his JID). In the +following example, Asterisk chats back with the caller identified by the +caller id. We also take advantage of the SendText implementation in +chan_gtalk (available in chan_jingle, and chan_sip as well), to +allow the caller to establish SIP calls from his GoogleTalk client : + +context gtalk-in { + s => { + NoOp(Caller id : ${CALLERID(all)}); + Answer(); + SendText(Please enter the number you wish to call); + Set(NEWEXTEN=${JABBER_RECEIVE(asterisk-xmpp,${CALLERID(name)})}); + SendText(Calling ${NEWEXTEN} ...); + Dial(SIP/${NEWEXTEN); + Hangup(); + } +} + +The maintainer of res_jabber is Philippe Sultan . diff --git a/include/asterisk/jabber.h b/include/asterisk/jabber.h index b56900d14..d64190044 100644 --- a/include/asterisk/jabber.h +++ b/include/asterisk/jabber.h @@ -117,7 +117,7 @@ struct aji_message { char *from; char *message; char id[25]; - time_t arrived; + struct timeval arrived; AST_LIST_ENTRY(aji_message) list; }; diff --git a/res/res_jabber.c b/res/res_jabber.c index 28ce514d0..9e619d283 100644 --- a/res/res_jabber.c +++ b/res/res_jabber.c @@ -34,6 +34,97 @@ iksemel openssl ***/ +/*** DOCUMENTATION + + + Sends an XMPP message to a buddy. + + + + The local named account to listen on (specified in + jabber.conf) + + + Jabber ID of the buddy to send the message to. It can be a + bare JID (username@domain) or a full JID (username@domain/resource). + + + The message to send. + + + + Sends the content of message as text message + from the given account to the buddy identified by + jid + Example: JabberSend(asterisk,bob@domain.com,Hello world) sends "Hello world" + to bob@domain.com as an XMPP message from the account + asterisk, configured in jabber.conf. + + + JABBER_STATUS + JABBER_RECEIVE + + + + + Reads XMPP messages. + + + + The local named account to listen on (specified in + jabber.conf) + + + Jabber ID of the buddy to receive message from. It can be a + bare JID (username@domain) or a full JID (username@domain/resource). + + + In seconds, defaults to 20. + + + + Receives a text message on the given account + from the buddy identified by jid and returns the contents. + Example: ${JABBER_RECEIVE(asterisk,bob@domain.com)} returns an XMPP message + sent from bob@domain.com (or nothing in case of a time out), to + the asterisk XMPP account configured in jabber.conf. + + + JABBER_STATUS + JabberSend + + + + + Retrieves a buddy's status. + + + + The local named account to listen on (specified in + jabber.conf) + + + Jabber ID of the buddy to receive message from. It can be a + bare JID (username@domain) or a full JID (username@domain/resource). + + + + Retrieves the numeric status associated with the buddy identified + by jid. + If the buddy does not exist in the buddylist, returns 7. + Status will be 1-7. + 1=Online, 2=Chatty, 3=Away, 4=XAway, 5=DND, 6=Offline + If not in roster variable will be set to 7. + Example: ${JABBER_STATUS(asterisk,bob@domain.com)} returns 1 if + bob@domain.com is online. asterisk is + the associated XMPP account configured in jabber.conf. + + + JABBER_RECEIVE + JabberSend + + + ***/ #include "asterisk.h" @@ -193,6 +284,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define JABBER_CONFIG "jabber.conf" /*-- Forward declarations */ +static void aji_message_destroy(struct aji_message *obj); static void aji_buddy_destroy(struct aji_buddy *obj); static void aji_client_destroy(struct aji_client *obj); static int aji_is_secure(struct aji_client *client); @@ -255,11 +347,14 @@ static char *app_ajistatus = "JabberStatus"; static struct aji_client_container clients; static struct aji_capabilities *capabilities = NULL; +static ast_cond_t message_received_condition; +static ast_mutex_t messagelock; /*! \brief Global flags, initialized to default values */ static struct ast_flags globalflags = { AJI_AUTOREGISTER }; /*! + * \internal * \brief Deletes the aji_client data structure. * \param obj aji_client The structure we will delete. * \return void. @@ -274,16 +369,14 @@ static void aji_client_destroy(struct aji_client *obj) iks_stack_delete(obj->stack); AST_LIST_LOCK(&obj->messages); while ((tmp = AST_LIST_REMOVE_HEAD(&obj->messages, list))) { - if (tmp->from) - ast_free(tmp->from); - if (tmp->message) - ast_free(tmp->message); + aji_message_destroy(tmp); } AST_LIST_HEAD_DESTROY(&obj->messages); ast_free(obj); } /*! + * \internal * \brief Deletes the aji_buddy data structure. * \param obj aji_buddy The structure we will delete. * \return void. @@ -302,14 +395,32 @@ static void aji_buddy_destroy(struct aji_buddy *obj) } /*! + * \internal + * \brief Deletes the aji_message data structure. + * \param obj aji_message The structure we will delete. + * \return void. + */ +static void aji_message_destroy(struct aji_message *obj) +{ + if (obj->from) { + ast_free(obj->from); + } + if (obj->message) { + ast_free(obj->message); + } + ast_free(obj); +} + +/*! + * \internal * \brief Find version in XML stream and populate our capabilities list - * \param node the node attribute in the caps element we'll look for or add to + * \param node the node attribute in the caps element we'll look for or add to * our list - * \param version the version attribute in the caps element we'll look for or + * \param version the version attribute in the caps element we'll look for or * add to our list * \param pak struct The XML stanza we're processing * \return a pointer to the added or found aji_version structure - */ + */ static struct aji_version *aji_find_version(char *node, char *version, ikspak *pak) { struct aji_capabilities *list = NULL; @@ -317,23 +428,23 @@ static struct aji_version *aji_find_version(char *node, char *version, ikspak *p list = capabilities; - if(!node) + if (!node) node = pak->from->full; - if(!version) + if (!version) version = "none supplied."; while(list) { - if(!strcasecmp(list->node, node)) { + if (!strcasecmp(list->node, node)) { res = list->versions; while(res) { - if(!strcasecmp(res->version, version)) + if (!strcasecmp(res->version, version)) return res; res = res->next; } - /* Specified version not found. Let's add it to + /* Specified version not found. Let's add it to this node in our capabilities list */ - if(!res) { + if (!res) { res = ast_malloc(sizeof(*res)); - if(!res) { + if (!res) { ast_log(LOG_ERROR, "Out of memory!\n"); return NULL; } @@ -348,14 +459,14 @@ static struct aji_version *aji_find_version(char *node, char *version, ikspak *p list = list->next; } /* Specified node not found. Let's add it our capabilities list */ - if(!list) { + if (!list) { list = ast_malloc(sizeof(*list)); - if(!list) { + if (!list) { ast_log(LOG_ERROR, "Out of memory!\n"); return NULL; } res = ast_malloc(sizeof(*res)); - if(!res) { + if (!res) { ast_log(LOG_ERROR, "Out of memory!\n"); ast_free(list); return NULL; @@ -371,7 +482,9 @@ static struct aji_version *aji_find_version(char *node, char *version, ikspak *p } return res; } + /*! + * \internal * \brief Find the aji_resource we want * \param buddy aji_buddy A buddy * \param name @@ -393,6 +506,7 @@ static struct aji_resource *aji_find_resource(struct aji_buddy *buddy, char *nam } /*! + * \internal * \brief Jabber GTalk function * \param node iks * \return 1 on success, 0 on failure. @@ -405,6 +519,7 @@ static int gtalk_yuck(iks *node) } /*! + * \internal * \brief Setup the authentication struct * \param id iksid * \param pass password @@ -433,11 +548,13 @@ static iks *jabber_make_auth(iksid * id, const char *pass, const char *sid) } /*! + * \internal * \brief Dial plan function status(). puts the status of watched user - into a channel variable. + * into a channel variable. * \param chan ast_channel * \param data - * \return 0 on success, -1 on error + * \retval 0 success + * \retval -1 error */ static int aji_status_exec(struct ast_channel *chan, const char *data) { @@ -474,6 +591,10 @@ static int aji_status_exec(struct ast_channel *chan, const char *data) } AST_NONSTANDARD_APP_ARGS(jid, args.jid, '/'); + if (jid.argc < 1 || jid.argc > 2) { + ast_log(LOG_WARNING, "Wrong JID %s, exiting\n", args.jid); + return -1; + } if (!(client = ast_aji_get_client(args.sender))) { ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); @@ -496,6 +617,15 @@ static int aji_status_exec(struct ast_channel *chan, const char *data) return 0; } +/*! + * \internal + * \brief Dial plan funtcion to retrieve the status of a buddy. + * \param channel The associated ast_channel, if there is one + * \param data The account, buddy JID, and optional timeout + * timeout. + * \retval 0 success + * \retval -1 failure + */ static int acf_jabberstatus_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen) { struct aji_client *client = NULL; @@ -523,6 +653,10 @@ static int acf_jabberstatus_read(struct ast_channel *chan, const char *name, cha } AST_NONSTANDARD_APP_ARGS(jid, args.jid, '/'); + if (jid.argc < 1 || jid.argc > 2) { + ast_log(LOG_WARNING, "Wrong JID %s, exiting\n", args.jid); + return -1; + } if (!(client = ast_aji_get_client(args.sender))) { ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); @@ -550,10 +684,237 @@ static struct ast_custom_function jabberstatus_function = { }; /*! + * \internal + * \brief Dial plan function to receive a message. + * \param channel The associated ast_channel, if there is one + * \param data The account, JID, and optional timeout + * timeout. + * \retval 0 success + * \retval -1 failure + */ +static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen) +{ + char *aux = NULL, *parse = NULL; + int timeout; + int jidlen, resourcelen; + struct timeval start; + long diff = 0; + struct aji_client *client = NULL; + int found = 0; + struct aji_message *tmp = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(account); + AST_APP_ARG(jid); + AST_APP_ARG(timeout); + ); + AST_DECLARE_APP_ARGS(jid, + AST_APP_ARG(screenname); + AST_APP_ARG(resource); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "%s requires arguments (account,jid[,timeout])\n", name); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (args.argc < 2 || args.argc > 3) { + ast_log(LOG_WARNING, "%s requires arguments (account,jid[,timeout])\n", name); + return -1; + } + + client = ast_aji_get_client(args.account); + if (!client) { + ast_log(LOG_WARNING, "Could not find client %s, exiting\n", args.account); + return -1; + } + + parse = ast_strdupa(args.jid); + AST_NONSTANDARD_APP_ARGS(jid, parse, '/'); + if (jid.argc < 1 || jid.argc > 2 || strlen(args.jid) > AJI_MAX_JIDLEN) { + ast_log(LOG_WARNING, "Invalid JID : %s\n", parse); + ASTOBJ_UNREF(client, aji_client_destroy); + return -1; + } + + if (ast_strlen_zero(args.timeout)) { + timeout = 20; + } else { + sscanf(args.timeout, "%d", &timeout); + if (timeout <= 0) { + ast_log(LOG_WARNING, "Invalid timeout specified: '%s'\n", args.timeout); + ASTOBJ_UNREF(client, aji_client_destroy); + return -1; + } + } + + jidlen = strlen(jid.screenname); + resourcelen = ast_strlen_zero(jid.resource) ? 0 : strlen(jid.resource); + + ast_debug(3, "Waiting for an XMPP message from %s\n", args.jid); + + start = ast_tvnow(); + + if (ast_autoservice_start(chan) < 0) { + ast_log(LOG_WARNING, "Cannot start autoservice for channel %s\n", chan->name); + return -1; + } + + /* search the messages list, grab the first message that matches with + * the from JID we're expecting, and remove it from the messages list */ + while (diff < timeout) { + struct timespec ts = { 0, }; + struct timeval wait; + int res; + + wait = ast_tvadd(start, ast_tv(timeout, 0)); + ts.tv_sec = wait.tv_sec; + ts.tv_nsec = wait.tv_usec * 1000; + + /* wait up to timeout seconds for an incoming message */ + ast_mutex_lock(&messagelock); + res = ast_cond_timedwait(&message_received_condition, &messagelock, &ts); + ast_mutex_unlock(&messagelock); + if (res == ETIMEDOUT) { + ast_debug(3, "No message received from %s in %d seconds\n", args.jid, timeout); + break; + }; + + AST_LIST_LOCK(&client->messages); + AST_LIST_TRAVERSE_SAFE_BEGIN(&client->messages, tmp, list) { + if (jid.argc == 1) { + /* no resource provided, compare bare JIDs */ + if (strncasecmp(jid.screenname, tmp->from, jidlen)) { + continue; + } + } else { + /* resource appended, compare bare JIDs and resources */ + char *resource = strchr(tmp->from, '/'); + if (!resource || strlen(resource) == 0) { + ast_log(LOG_WARNING, "Remote JID has no resource : %s\n", tmp->from); + if (strncasecmp(jid.screenname, tmp->from, jidlen)) { + continue; + } + } else { + resource ++; + if (strncasecmp(jid.screenname, tmp->from, jidlen) || strncmp(jid.resource, resource, resourcelen)) { + continue; + } + } + } + /* check if the message is not too old */ + if (ast_tvdiff_sec(ast_tvnow(), tmp->arrived) >= client->message_timeout) { + ast_debug(3, "Found old message from %s, deleting it\n", tmp->from); + AST_LIST_REMOVE_CURRENT(list); + aji_message_destroy(tmp); + continue; + } + found = 1; + aux = ast_strdupa(tmp->message); + AST_LIST_REMOVE_CURRENT(list); + aji_message_destroy(tmp); + break; + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&client->messages); + if (found) { + break; + } + + /* check timeout */ + diff = ast_tvdiff_ms(ast_tvnow(), start); + } + + ASTOBJ_UNREF(client, aji_client_destroy); + if (ast_autoservice_stop(chan) < 0) { + ast_log(LOG_WARNING, "Cannot stop autoservice for channel %s\n", chan->name); + } + + /* return if we timed out */ + if (!found) { + ast_log(LOG_NOTICE, "Timed out : no message received from %s\n", args.jid); + return -1; + } + ast_copy_string(buf, aux, buflen); + + return 0; +} + +static struct ast_custom_function jabberreceive_function = { + .name = "JABBER_RECEIVE", + .read = acf_jabberreceive_read, +}; + +/*! + * \internal + * \brief Delete old messages from a given JID + * Messages stored during more than client->message_timeout are deleted + * \param client Asterisk's XMPP client + * \param from the JID we received messages from + * \retval the number of deleted messages + * \retval -1 failure + */ +static int delete_old_messages(struct aji_client *client, char *from) +{ + int deleted = 0; + int isold = 0; + struct aji_message *tmp = NULL; + if (!client) { + ast_log(LOG_ERROR, "Cannot find our XMPP client\n"); + return -1; + } + + /* remove old messages */ + AST_LIST_LOCK(&client->messages); + if (AST_LIST_EMPTY(&client->messages)) { + AST_LIST_UNLOCK(&client->messages); + return 0; + } + + AST_LIST_TRAVERSE_SAFE_BEGIN(&client->messages, tmp, list) { + if (isold) { + if (!from || !strncasecmp(from, tmp->from, strlen(from))) { + AST_LIST_REMOVE_CURRENT(list); + aji_message_destroy(tmp); + deleted ++; + } + } else if (ast_tvdiff_sec(ast_tvnow(), tmp->arrived) >= client->message_timeout) { + isold = 1; + if (!from || !strncasecmp(from, tmp->from, strlen(from))) { + AST_LIST_REMOVE_CURRENT(list); + aji_message_destroy(tmp); + deleted ++; + } + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&client->messages); + + return deleted; +} + +/*! + * \internal + * \brief Delete old messages + * Messages stored during more than client->message_timeout are deleted + * \param client Asterisk's XMPP client + * \retval the number of deleted messages + * \retval -1 failure + */ +static int delete_old_messages_all(struct aji_client *client) +{ + return delete_old_messages(client, NULL); +} + +/*! + * \internal * \brief Dial plan function to send a message. * \param chan ast_channel - * \param data Data is sender|receiver|message. - * \return 0 on success,-1 on error. + * \param data Data is account,jid,message. + * \retval 0 success + * \retval -1 failure */ static int aji_send_exec(struct ast_channel *chan, const char *data) { @@ -566,14 +927,14 @@ static int aji_send_exec(struct ast_channel *chan, const char *data) ); if (!data) { - ast_log(LOG_ERROR, "Usage: JabberSend(,,)\n"); - return 0; + ast_log(LOG_WARNING, "%s requires arguments (account,jid,message)\n", app_ajisend); + return -1; } s = ast_strdupa(data); AST_STANDARD_APP_ARGS(args, s); if (args.argc < 3) { - ast_log(LOG_ERROR, "JabberSend requires 3 arguments: '%s'\n", data); + ast_log(LOG_WARNING, "%s requires arguments (account,jid,message)\n", app_ajisend); return -1; } @@ -581,12 +942,14 @@ static int aji_send_exec(struct ast_channel *chan, const char *data) ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); return -1; } - if (strchr(args.recipient, '@') && !ast_strlen_zero(args.message)) + if (strchr(args.recipient, '@') && !ast_strlen_zero(args.message)) { ast_aji_send_chat(client, args.recipient, args.message); + } return 0; } -/*! +/*! + * \internal * \brief Tests whether the connection is secured or not * \return 0 if the connection is not secured */ @@ -601,6 +964,7 @@ static int aji_is_secure(struct aji_client *client) #ifdef HAVE_OPENSSL /*! + * \internal * \brief Starts the TLS procedure * \param client the configured XMPP client we use to connect to a XMPP server * \return IKS_OK on success, an error code if sending failed, IKS_NET_TLSFAIL @@ -620,6 +984,7 @@ static int aji_start_tls(struct aji_client *client) } /*! + * \internal * \brief TLS handshake, OpenSSL initialization * \param client the configured XMPP client we use to connect to a XMPP server * \return IKS_OK on success, IKS_NET_TLSFAIL on failure @@ -668,13 +1033,15 @@ static int aji_tls_handshake(struct aji_client *client) #endif /* HAVE_OPENSSL */ /*! + * \internal * \brief Secured or unsecured IO socket receiving function * \param client the configured XMPP client we use to connect to a XMPP server * \param buffer the reception buffer * \param buf_len the size of the buffer * \param timeout the select timer - * \return the number of read bytes on success, 0 on timeout expiration, - * -1 on error + * \retval the number of read bytes + * \retval 0 timeout expiration + * \retval -1 error */ static int aji_io_recv(struct aji_client *client, char *buffer, size_t buf_len, int timeout) { @@ -720,13 +1087,16 @@ static int aji_io_recv(struct aji_client *client, char *buffer, size_t buf_len, } /*! + * \internal * \brief Tries to receive data from the Jabber server * \param client the configured XMPP client we use to connect to a XMPP server * \param timeout the timeout value * This function receives (encrypted or unencrypted) data from the XMPP server, * and passes it to the parser. - * \return IKS_OK on success, IKS_NET_RWERR on IO error, IKS_NET_NOCONN, if no - * connection available, IKS_NET_EXPIRED on timeout expiration + * \retval IKS_OK success + * \retval IKS_NET_RWERR IO error + * \retval IKS_NET_NOCONN no connection available + * \retval IKS_NET_EXPIRED timeout expiration */ static int aji_recv (struct aji_client *client, int timeout) { @@ -794,6 +1164,7 @@ static int aji_recv (struct aji_client *client, int timeout) } /*! + * \internal * \brief Sends XMPP header to the server * \param client the configured XMPP client we use to connect to a XMPP server * \param to the target XMPP server @@ -831,6 +1202,7 @@ int ast_aji_send(struct aji_client *client, iks *x) } /*! + * \internal * \brief Sends an XML string over an XMPP connection * \param client the configured XMPP client we use to connect to a XMPP server * \param xmlstr the XML string to send @@ -864,6 +1236,7 @@ static int aji_send_raw(struct aji_client *client, const char *xmlstr) } /*! + * \internal * \brief the debug loop. * \param data void * \param xmpp xml data as string @@ -881,8 +1254,8 @@ static void aji_log_hook(void *data, const char *xmpp, size_t size, int is_incom if (is_incoming) ast_verbose("\nJABBER: %s INCOMING: %s\n", client->name, xmpp); else { - if( strlen(xmpp) == 1) { - if(option_debug > 2 && xmpp[0] == ' ') { + if (strlen(xmpp) == 1) { + if (option_debug > 2 && xmpp[0] == ' ') { ast_verbose("\nJABBER: Keep alive packet\n"); } } else @@ -894,6 +1267,7 @@ static void aji_log_hook(void *data, const char *xmpp, size_t size, int is_incom } /*! + * \internal * \brief A wrapper function for iks_start_sasl * \param client the configured XMPP client we use to connect to a XMPP server * \param type the SASL authentication type. Supported types are PLAIN and MD5 @@ -945,6 +1319,7 @@ static int aji_start_sasl(struct aji_client *client, enum ikssasltype type, char } /*! + * \internal * \brief The action hook parses the inbound packets, constantly running. * \param data aji client structure * \param type type of packet @@ -958,7 +1333,7 @@ static int aji_act_hook(void *data, int type, iks *node) iks *auth = NULL; int features = 0; - if(!node) { + if (!node) { ast_log(LOG_ERROR, "aji_act_hook was called with out a packet\n"); /* most likely cause type is IKS_NODE_ERROR lost connection */ ASTOBJ_UNREF(client, aji_client_destroy); return IKS_HOOK; @@ -1089,7 +1464,7 @@ static int aji_act_hook(void *data, int type, iks *node) handshake = NULL; } client->state = AJI_CONNECTING; - if(aji_recv(client, 1) == 2) /*XXX proper result for iksemel library on iks_recv of XXX*/ + if (aji_recv(client, 1) == 2) /*XXX proper result for iksemel library on iks_recv of XXX*/ client->state = AJI_CONNECTED; else ast_log(LOG_WARNING, "Jabber didn't seem to handshake, failed to authenticate.\n"); @@ -1146,6 +1521,7 @@ static int aji_act_hook(void *data, int type, iks *node) return IKS_OK; } /*! + * \internal * \brief Unknown * \param data void * \param pak ikspak @@ -1189,6 +1565,7 @@ static int aji_register_approve_handler(void *data, ikspak *pak) return IKS_FILTER_EAT; } /*! + * \internal * \brief register handler for incoming querys (IQ's) * \param data incoming aji_client request * \param pak ikspak @@ -1212,7 +1589,7 @@ static int aji_register_query_handler(void *data, ikspak *pak) query = iks_new("query"); error = iks_new("error"); notacceptable = iks_new("not-acceptable"); - if(iq && query && error && notacceptable) { + if (iq && query && error && notacceptable) { iks_insert_attrib(iq, "type", "error"); iks_insert_attrib(iq, "from", client->user); iks_insert_attrib(iq, "to", pak->from->full); @@ -1260,6 +1637,7 @@ static int aji_register_query_handler(void *data, ikspak *pak) } /*! + * \internal * \brief Handles stuff * \param data void * \param pak ikspak @@ -1354,7 +1732,9 @@ static int aji_ditems_handler(void *data, ikspak *pak) return IKS_FILTER_EAT; } + /*! + * \internal * \brief Handle add extra info * \param data void * \param pak ikspak @@ -1414,7 +1794,9 @@ static int aji_client_info_handler(void *data, ikspak *pak) ASTOBJ_UNREF(client, aji_client_destroy); return IKS_FILTER_EAT; } + /*! + * \internal * \brief Handler of the return info packet * \param data aji_client * \param pak ikspak @@ -1556,9 +1938,10 @@ static int aji_dinfo_handler(void *data, ikspak *pak) } /*! - * \brief Handles \verbatim \endverbatim tags. + * \internal + * \brief Handles \verbatim \endverbatim stanzas. * \param client the configured XMPP client we use to connect to a XMPP server - * \param node iks + * \param node iks * \return void. */ static void aji_handle_iq(struct aji_client *client, iks *node) @@ -1567,58 +1950,69 @@ static void aji_handle_iq(struct aji_client *client, iks *node) } /*! - * \brief Handles presence packets. + * \internal + * \brief Handles \verbatim \endverbatim stanzas. + * Adds the incoming message to the client's message list. * \param client the configured XMPP client we use to connect to a XMPP server * \param pak ikspak the node */ static void aji_handle_message(struct aji_client *client, ikspak *pak) { - struct aji_message *insert, *tmp; - int flag = 0; - - if (!(insert = ast_calloc(1, sizeof(*insert)))) + struct aji_message *insert; + int deleted = 0; + + ast_debug(3, "client %s received a message\n", client->name); + + if (!(insert = ast_calloc(1, sizeof(*insert)))) { return; - time(&insert->arrived); - if (iks_find_cdata(pak->x, "body")) - insert->message = ast_strdup(iks_find_cdata(pak->x, "body")); - if (pak->id) - ast_copy_string(insert->id, pak->id, sizeof(insert->message)); - if (pak->from) - insert->from = ast_strdup(pak->from->full); - AST_LIST_LOCK(&client->messages); - AST_LIST_TRAVERSE_SAFE_BEGIN(&client->messages, tmp, list) { - if (flag) { - AST_LIST_REMOVE_CURRENT(list); - if (tmp->from) - ast_free(tmp->from); - if (tmp->message) - ast_free(tmp->message); - } else if (difftime(time(NULL), tmp->arrived) >= client->message_timeout) { - flag = 1; - AST_LIST_REMOVE_CURRENT(list); - if (tmp->from) - ast_free(tmp->from); - if (tmp->message) - ast_free(tmp->message); - } } - AST_LIST_TRAVERSE_SAFE_END; + + insert->arrived = ast_tvnow(); + + /* wake up threads waiting for messages */ + ast_mutex_lock(&messagelock); + ast_cond_broadcast(&message_received_condition); + ast_mutex_unlock(&messagelock); + + if (iks_find_cdata(pak->x, "body")) { + insert->message = ast_strdup(iks_find_cdata(pak->x, "body")); + } + if (pak->id) { + ast_copy_string(insert->id, pak->id, sizeof(insert->id)); + } + if (pak->from){ + /* insert will furtherly be added to message list */ + insert->from = ast_strdup(pak->from->full); + if (!insert->from) { + ast_log(LOG_ERROR, "Memory allocation failure\n"); + return; + } + ast_debug(3, "message comes from %s\n", insert->from); + } + + /* remove old messages received from this JID + * and insert received message */ + deleted = delete_old_messages(client, pak->from->partial); + ast_debug(3, "Deleted %d messages for client %s from JID %s\n", deleted, client->name, pak->from->partial); + AST_LIST_LOCK(&client->messages); AST_LIST_INSERT_HEAD(&client->messages, insert, list); AST_LIST_UNLOCK(&client->messages); } + /*! - * \brief Check the presence info + * \internal + * \brief handles \verbatim \endverbatim stanzas. * \param client the configured XMPP client we use to connect to a XMPP server * \param pak ikspak -*/ + */ static void aji_handle_presence(struct aji_client *client, ikspak *pak) { int status, priority; struct aji_buddy *buddy; struct aji_resource *tmp = NULL, *last = NULL, *found = NULL; char *ver, *node, *descrip, *type; - - if(client->state != AJI_CONNECTED) + + if (client->state != AJI_CONNECTED) aji_create_buddy(pak->from->partial, client); buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, pak->from->partial); @@ -1631,7 +2025,7 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak) return; } type = iks_find_attrib(pak->x, "type"); - if(client->component && type &&!strcasecmp("probe", type)) { + if (client->component && type &&!strcasecmp("probe", type)) { aji_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), client->status, client->statusmessage); ast_verbose("what i was looking for \n"); } @@ -1761,18 +2155,18 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak) } /* retrieve capabilites of the new resource */ - if(status !=6 && found && !found->cap) { + if (status != 6 && found && !found->cap) { found->cap = aji_find_version(node, ver, pak); - if(gtalk_yuck(pak->x)) /* gtalk should do discover */ + if (gtalk_yuck(pak->x)) /* gtalk should do discover */ found->cap->jingle = 1; - if(found->cap->jingle && option_debug > 4) { + if (found->cap->jingle && option_debug > 4) { ast_debug(1,"Special case for google till they support discover.\n"); } else { iks *iq, *query; iq = iks_new("iq"); query = iks_new("query"); - if(query && iq) { + if (query && iq) { iks_insert_attrib(iq, "type", "get"); iks_insert_attrib(iq, "to", pak->from->full); iks_insert_attrib(iq,"from", client->jid->full); @@ -1824,6 +2218,7 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak) } /*! + * \internal * \brief handles subscription requests. * \param client the configured XMPP client we use to connect to a XMPP server * \param pak ikspak iksemel packet. @@ -1872,25 +2267,30 @@ static void aji_handle_subscribe(struct aji_client *client, ikspak *pak) * \param client the configured XMPP client we use to connect to a XMPP server * \param address * \param message - * \return 1. + * \retval IKS_OK success + * \retval -1 failure */ int ast_aji_send_chat(struct aji_client *client, const char *address, const char *message) { int res = 0; iks *message_packet = NULL; - if (client->state == AJI_CONNECTED) { - message_packet = iks_make_msg(IKS_TYPE_CHAT, address, message); - if (message_packet) { - iks_insert_attrib(message_packet, "from", client->jid->full); - res = ast_aji_send(client, message_packet); - } else { - ast_log(LOG_ERROR, "Out of memory.\n"); - } - iks_delete(message_packet); - } else + if (client->state != AJI_CONNECTED) { ast_log(LOG_WARNING, "JABBER: Not connected can't send\n"); - return 1; + return -1; + } + + message_packet = iks_make_msg(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); + res = ast_aji_send(client, message_packet); + iks_delete(message_packet); + + return res; } /*! @@ -1986,8 +2386,8 @@ int ast_aji_invite_chat(struct aji_client *client, char *user, char *room, char return res; } - /*! + * \internal * \brief receive message loop. * \param data void * \return void. @@ -2019,22 +2419,26 @@ static void *aji_recv_loop(void *data) pthread_exit(NULL); } - /* Decrease timeout if no data received */ - if (res == IKS_NET_EXPIRED) + /* Decrease timeout if no data received, and delete + * old messages globally */ + if (res == IKS_NET_EXPIRED) { client->timeout--; - - if (res == IKS_HOOK) + delete_old_messages_all(client); + } + if (res == IKS_HOOK) { ast_log(LOG_WARNING, "JABBER: Got hook event.\n"); - else if (res == IKS_NET_TLSFAIL) + } else if (res == IKS_NET_TLSFAIL) { ast_log(LOG_ERROR, "JABBER: Failure in TLS.\n"); - else if (client->timeout == 0 && client->state == AJI_CONNECTED) { + } else if (client->timeout == 0 && client->state == AJI_CONNECTED) { res = client->keepalive ? aji_send_raw(client, " ") : IKS_OK; - if(res == IKS_OK) + if (res == IKS_OK) { client->timeout = 50; - else + } else { ast_log(LOG_WARNING, "JABBER: Network Timeout\n"); - } else if (res == IKS_NET_RWERR) + } + } else if (res == IKS_NET_RWERR) { ast_log(LOG_WARNING, "JABBER: socket read error\n"); + } } while (client); ASTOBJ_UNREF(client, aji_client_destroy); return 0; @@ -2148,6 +2552,7 @@ static int aji_register_transport2(void *data, ikspak *pak) #endif /*! + * \internal * \brief goes through roster and prunes users not needed in list, or adds them accordingly. * \param client the configured XMPP client we use to connect to a XMPP server * \return void. @@ -2202,6 +2607,7 @@ static void aji_pruneregister(struct aji_client *client) } /*! + * \internal * \brief filters the roster packet we get back from server. * \param data void * \param pak ikspak iksemel packet. @@ -2261,11 +2667,11 @@ static int aji_filter_roster(void *data, ikspak *pak) ASTOBJ_WRLOCK(buddy); ast_copy_string(buddy->name, iks_find_attrib(x, "jid"), sizeof(buddy->name)); ast_clear_flag(&buddy->flags, AST_FLAGS_ALL); - if(ast_test_flag(&client->flags, AJI_AUTOPRUNE)) { + if (ast_test_flag(&client->flags, AJI_AUTOPRUNE)) { ast_set_flag(&buddy->flags, AJI_AUTOPRUNE); ASTOBJ_MARK(buddy); } else if (!iks_strcmp(iks_find_attrib(x, "subscription"), "none") || !iks_strcmp(iks_find_attrib(x, "subscription"), "from")) { - /* subscribe to buddy's presence only + /* subscribe to buddy's presence only if we really need to */ ast_set_flag(&buddy->flags, AJI_AUTOREGISTER); } @@ -2286,6 +2692,7 @@ static int aji_filter_roster(void *data, ikspak *pak) } /*! + * \internal * \brief reconnect to jabber server * \param client the configured XMPP client we use to connect to a XMPP server * \return res. @@ -2308,6 +2715,7 @@ static int aji_reconnect(struct aji_client *client) } /*! + * \internal * \brief Get the roster of jabber users * \param client the configured XMPP client we use to connect to a XMPP server * \return 1. @@ -2317,7 +2725,7 @@ static int aji_get_roster(struct aji_client *client) iks *roster = NULL; roster = iks_make_iq(IKS_TYPE_GET, IKS_NS_ROSTER); - if(roster) { + if (roster) { iks_insert_attrib(roster, "id", "roster"); aji_set_presence(client, NULL, client->jid->full, client->status, client->statusmessage); ast_aji_send(client, roster); @@ -2329,6 +2737,7 @@ static int aji_get_roster(struct aji_client *client) } /*! + * \internal * \brief connects as a client to jabber server. * \param data void * \param pak ikspak iksemel packet @@ -2345,7 +2754,7 @@ static int aji_client_connect(void *data, ikspak *pak) client->state = AJI_CONNECTING; client->jid = (iks_find_cdata(pak->query, "jid")) ? iks_id_new(client->stack, iks_find_cdata(pak->query, "jid")) : client->jid; iks_filter_remove_hook(client->f, aji_client_connect); - if(!client->component) /*client*/ + if (!client->component) /*client*/ aji_get_roster(client); } } else @@ -2356,6 +2765,7 @@ static int aji_client_connect(void *data, ikspak *pak) } /*! + * \internal * \brief prepares client for connect. * \param client the configured XMPP client we use to connect to a XMPP server * \return 1. @@ -2407,6 +2817,7 @@ int ast_aji_disconnect(struct aji_client *client) } /*! + * \internal * \brief set presence of client. * \param client the configured XMPP client we use to connect to a XMPP server * \param to user send it to @@ -2424,9 +2835,9 @@ static void aji_set_presence(struct aji_client *client, char *to, char *from, in char priorityS[10]; if (presence && cnode && client && priority) { - if(to) + if (to) iks_insert_attrib(presence, "to", to); - if(from) + if (from) iks_insert_attrib(presence, "from", from); snprintf(priorityS, sizeof(priorityS), "%d", client->priority); iks_insert_cdata(priority, priorityS, strlen(priorityS)); @@ -2446,6 +2857,7 @@ static void aji_set_presence(struct aji_client *client, char *to, char *from, in } /*! + * \internal * \brief Turn on/off console debugging. * \return CLI_SUCCESS. */ @@ -2486,6 +2898,7 @@ static char *aji_do_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a } /*! + * \internal * \brief Reload jabber module. * \return CLI_SUCCESS. */ @@ -2508,6 +2921,7 @@ static char *aji_do_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args } /*! + * \internal * \brief Show client status. * \return CLI_SUCCESS. */ @@ -2553,6 +2967,7 @@ static char *aji_show_clients(struct ast_cli_entry *e, int cmd, struct ast_cli_a } /*! + * \internal * \brief Show buddy lists * \return CLI_SUCCESS. */ @@ -2580,10 +2995,10 @@ static char *aji_show_buddies(struct ast_cli_entry *e, int cmd, struct ast_cli_a ASTOBJ_RDLOCK(iterator); ast_cli(a->fd,"\tBuddy:\t%s\n", iterator->name); if (!iterator->resources) - ast_cli(a->fd,"\t\tResource: None\n"); + ast_cli(a->fd,"\t\tResource: None\n"); for (resource = iterator->resources; resource; resource = resource->next) { ast_cli(a->fd,"\t\tResource: %s\n", resource->resource); - if(resource->cap) { + if (resource->cap) { ast_cli(a->fd,"\t\t\tnode: %s\n", resource->cap->parent->node); ast_cli(a->fd,"\t\t\tversion: %s\n", resource->cap->version); ast_cli(a->fd,"\t\t\tJingle capable: %s\n", resource->cap->jingle ? "yes" : "no"); @@ -2599,6 +3014,7 @@ static char *aji_show_buddies(struct ast_cli_entry *e, int cmd, struct ast_cli_a } /*! + * \internal * \brief Send test message for debugging. * \return CLI_SUCCESS,CLI_FAILURE. */ @@ -2638,21 +3054,21 @@ static char *aji_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) ast_verbose("User: %s\n", iterator->name); for (resource = iterator->resources; resource; resource = resource->next) { ast_verbose("Resource: %s\n", resource->resource); - if(resource->cap) { + if (resource->cap) { ast_verbose(" client: %s\n", resource->cap->parent->node); ast_verbose(" version: %s\n", resource->cap->version); ast_verbose(" Jingle Capable: %d\n", resource->cap->jingle); } ast_verbose(" Priority: %d\n", resource->priority); - ast_verbose(" Status: %d\n", resource->status); - ast_verbose(" Message: %s\n", S_OR(resource->description,"")); + ast_verbose(" Status: %d\n", resource->status); + ast_verbose(" Message: %s\n", S_OR(resource->description,"")); } ASTOBJ_UNLOCK(iterator); }); ast_verbose("\nOooh a working message stack!\n"); AST_LIST_LOCK(&client->messages); AST_LIST_TRAVERSE(&client->messages, tmp, list) { - ast_verbose(" Message from: %s with id %s @ %s %s\n",tmp->from, S_OR(tmp->id,""), ctime(&tmp->arrived), S_OR(tmp->message, "")); + //ast_verbose(" Message from: %s with id %s @ %s %s\n",tmp->from, S_OR(tmp->id,""), ctime(&tmp->arrived), S_OR(tmp->message, "")); } AST_LIST_UNLOCK(&client->messages); ASTOBJ_UNREF(client, aji_client_destroy); @@ -2661,6 +3077,7 @@ static char *aji_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) } /*! + * \internal * \brief creates aji_client structure. * \param label * \param var ast_variable @@ -2701,7 +3118,7 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug) client->forcessl = 0; client->keepalive = 1; client->timeout = 50; - client->message_timeout = 100; + client->message_timeout = 5; AST_LIST_HEAD_INIT(&client->messages); client->component = 0; ast_copy_string(client->statusmessage, "Online and Available", sizeof(client->statusmessage)); @@ -2845,7 +3262,7 @@ static int aji_create_transport(char *label, struct aji_client *client) buddy = ASTOBJ_CONTAINER_FIND(&client->buddies,label); if (!buddy) { buddy = ast_calloc(1, sizeof(*buddy)); - if(!buddy) { + if (!buddy) { ast_log(LOG_WARNING, "Out of memory\n"); return 0; } @@ -2882,6 +3299,7 @@ static int aji_create_transport(char *label, struct aji_client *client) #endif /*! + * \internal * \brief creates buddy. * \param label char. * \param client the configured XMPP client we use to connect to a XMPP server @@ -2895,7 +3313,7 @@ static int aji_create_buddy(char *label, struct aji_client *client) if (!buddy) { flag = 1; buddy = ast_calloc(1, sizeof(*buddy)); - if(!buddy) { + if (!buddy) { ast_log(LOG_WARNING, "Out of memory\n"); return 0; } @@ -2904,7 +3322,7 @@ static int aji_create_buddy(char *label, struct aji_client *client) ASTOBJ_WRLOCK(buddy); ast_copy_string(buddy->name, label, sizeof(buddy->name)); ASTOBJ_UNLOCK(buddy); - if(flag) + if (flag) ASTOBJ_CONTAINER_LINK(&client->buddies, buddy); else { ASTOBJ_UNMARK(buddy); @@ -2989,6 +3407,7 @@ struct aji_client_container *ast_aji_get_clients(void) } /*! + * \internal * \brief Send a Jabber Message via call from the Manager * \param s mansession Manager session * \param m message Message to send @@ -3034,7 +3453,10 @@ static int manager_jabber_send(struct mansession *s, const struct message *m) return 0; } -/*! \brief Reload the jabber module */ +/*! + * \internal + * \brief Reload the jabber module + */ static int aji_reload(int reload) { int res; @@ -3049,7 +3471,7 @@ static int aji_reload(int reload) ASTOBJ_CONTAINER_PRUNE_MARKED(&clients, aji_client_destroy); ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { ASTOBJ_RDLOCK(iterator); - if(iterator->state == AJI_DISCONNECTED) { + if (iterator->state == AJI_DISCONNECTED) { if (!iterator->thread) ast_pthread_create_background(&iterator->thread, NULL, aji_recv_loop, iterator); } else if (iterator->state == AJI_CONNECTING) @@ -3060,7 +3482,10 @@ static int aji_reload(int reload) return 1; } -/*! \brief Unload the jabber module */ +/*! + * \internal + * \brief Unload the jabber module + */ static int unload_module(void) { @@ -3069,7 +3494,8 @@ static int unload_module(void) ast_unregister_application(app_ajistatus); ast_manager_unregister("JabberSend"); ast_custom_function_unregister(&jabberstatus_function); - + ast_custom_function_unregister(&jabberreceive_function); + ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, { ASTOBJ_RDLOCK(iterator); ast_debug(3, "JABBER: Releasing and disconnecting client: %s\n", iterator->name); @@ -3081,25 +3507,38 @@ static int unload_module(void) ASTOBJ_CONTAINER_DESTROYALL(&clients, aji_client_destroy); ASTOBJ_CONTAINER_DESTROY(&clients); + + ast_cond_destroy(&message_received_condition); + ast_mutex_destroy(&messagelock); + return 0; } -/*! \brief Unload the jabber module */ +/*! + * \internal + * \brief Unload the jabber module + */ static int load_module(void) { ASTOBJ_CONTAINER_INIT(&clients); - if(!aji_reload(0)) + if (!aji_reload(0)) 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_ajistatus, aji_status_exec); ast_cli_register_multiple(aji_cli, ARRAY_LEN(aji_cli)); ast_custom_function_register(&jabberstatus_function); + ast_custom_function_register(&jabberreceive_function); + ast_mutex_init(&messagelock); + ast_cond_init(&message_received_condition, NULL); return 0; } -/*! \brief Wrapper for aji_reload */ +/*! + * \internal + * \brief Wrapper for aji_reload + */ static int reload(void) { aji_reload(1);