diff --git a/configs/cli_aliases.conf.sample b/configs/cli_aliases.conf.sample index 78bec99371..edd6a2f17f 100644 --- a/configs/cli_aliases.conf.sample +++ b/configs/cli_aliases.conf.sample @@ -186,3 +186,16 @@ soft hangup=channel request hangup [asterisk14](asterisk14_tpl) ; add any additional custom commands you want below here. + +[asterisk11_tpl](!) +jabber list nodes=xmpp list nodes +jabber purge nodes=xmpp purge nodes +jabber delete node=xmpp delete node +jabber create collection=xmpp create collection +jabber create leaf=xmpp create leaf +jabber set debug=xmpp set debug +jabber show connections=xmpp show connections +jabber show buddies=xmpp show buddies + +[asterisk11](asterisk11_tpl) +; add any additional custom commands you want below here. diff --git a/configs/xmpp.conf.sample b/configs/xmpp.conf.sample new file mode 100644 index 0000000000..a838568678 --- /dev/null +++ b/configs/xmpp.conf.sample @@ -0,0 +1,39 @@ +[general] +;debug=yes ; Enable debugging (disabled by default). +;autoprune=yes ; Auto remove users from buddy list. Depending on your + ; setup (ie, using your personal Gtalk account for a test) + ; you might lose your contacts list. Default is 'no'. +;autoregister=yes ; Auto register users from buddy list. +;collection_nodes=yes ; Enable support for XEP-0248 for use with + ; distributed device state. Default is 'no'. +;pubsub_autocreate=yes ; Whether or not the PubSub server supports/is using + ; auto-create for nodes. If it is, we have to + ; explicitly pre-create nodes before publishing them. + ; Default is 'no'. +;auth_policy=accept ; Auto accept users' subscription requests (default). + ; Set to deny for auto denial. +;[asterisk] +;type=client ; Client or Component connection +;serverhost=astjab.org ; Route to server for example, talk.google.com +;pubsub_node=pubsub.astjab.org ; Node to use for publishing events via PubSub +;username=asterisk@astjab.org/asterisk ; Username with optional resource. +;secret=blah ; Password +;priority=1 ; Resource priority +;port=5222 ; Port to use defaults to 5222 +;usetls=yes ; Use tls or not +;usesasl=yes ; Use sasl or not +;buddy=mogorman@astjab.org ; Manual addition of buddy to list. + ; For distributed events, these buddies are + ; automatically added in the whitelist as + ; 'owners' of the node(s). +;distribute_events=yes ; Whether or not to distribute events using + ; this connection. Default is 'no'. +;status=available ; One of: chat, available, away, xaway, or dnd +;statusmessage="I am available" ; Have custom status message for Asterisk +;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. +;sendtodialplan=yes ; Send incoming messages into the dialplan. Off by default. +;context=messages ; Dialplan context to send incoming messages to. If not set, + ; "default" will be used. diff --git a/include/asterisk/xmpp.h b/include/asterisk/xmpp.h new file mode 100644 index 0000000000..ab21987bcc --- /dev/null +++ b/include/asterisk/xmpp.h @@ -0,0 +1,262 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Digium, Inc. + * + * Joshua Colp + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * \brief XMPP Interface + * \author Joshua Colp + * \extref IKSEMEL http://iksemel.jabberstudio.org + */ + +#ifndef _ASTERISK_XMPP_H +#define _ASTERISK_XMPP_H + +#ifdef HAVE_OPENSSL + +#include +#include +#define TRY_SECURE 2 +#define SECURE 4 + +#endif /* HAVE_OPENSSL */ + +/* file is read by blocks with this size */ +#define NET_IO_BUF_SIZE 4096 + +/* Return value for timeout connection expiration */ +#define IKS_NET_EXPIRED 12 + +#include + +#include "asterisk/utils.h" +#include "asterisk/astobj2.h" +#include "asterisk/linkedlists.h" +#include "asterisk/stringfields.h" +#include "asterisk/pbx.h" + +/* + * As per RFC 3920 - section 3.1, the maximum length for a full Jabber ID + * is 3071 bytes. + * The ABNF syntax for jid : + * jid = [node "@" ] domain [ "/" resource ] + * Each allowable portion of a JID (node identifier, domain identifier, + * and resource identifier) MUST NOT be more than 1023 bytes in length, + * resulting in a maximum total size (including the '@' and '/' separators) + * of 3071 bytes. + */ +#define XMPP_MAX_JIDLEN 3071 + +/*! \brief Maximum size of a resource JID */ +#define XMPP_MAX_RESJIDLEN 1023 + +/*! \brief Maximum size of an attribute */ +#define XMPP_MAX_ATTRLEN 256 + +/*! \brief Client connection states */ +enum xmpp_state { + XMPP_STATE_DISCONNECTING, /*!< Client is disconnecting */ + XMPP_STATE_DISCONNECTED, /*!< Client is disconnected */ + XMPP_STATE_CONNECTING, /*!< Client is connecting */ + XMPP_STATE_REQUEST_TLS, /*!< Client should request TLS */ + XMPP_STATE_REQUESTED_TLS, /*!< Client has requested TLS */ + XMPP_STATE_AUTHENTICATE, /*!< Client needs to authenticate */ + XMPP_STATE_AUTHENTICATING, /*!< Client is authenticating */ + XMPP_STATE_ROSTER, /*!< Client is currently getting the roster */ + XMPP_STATE_CONNECTED, /*!< Client is fully connected */ +}; + +/*! \brief Resource capabilities */ +struct ast_xmpp_capabilities { + char node[200]; /*!< Node string from the capabilities stanza in presence notification */ + char version[50]; /*!< Version string from the capabilities stanza in presence notification */ + unsigned int jingle:1; /*!< Set if the resource supports Jingle */ + unsigned int google:1; /*!< Set if the resource supports Google Talk */ +}; + +/*! \brief XMPP Resource */ +struct ast_xmpp_resource { + char resource[XMPP_MAX_RESJIDLEN]; /*!< JID of the resource */ + int status; /*!< Current status of the resource */ + char *description; /*!< Description of the resource */ + int priority; /*!< Priority, used for deciding what resource to use */ + struct ast_xmpp_capabilities caps; /*!< Capabilities of the resource */ +}; + +/*! \brief XMPP Message */ +struct ast_xmpp_message { + char *from; /*!< Who the message is from */ + char *message; /*!< Message contents */ + char id[25]; /*!< Identifier for the message */ + struct timeval arrived; /*!< When the message arrived */ + AST_LIST_ENTRY(ast_xmpp_message) list; /*!< Linked list information */ +}; + +/*! \brief XMPP Buddy */ +struct ast_xmpp_buddy { + char id[XMPP_MAX_JIDLEN]; /*!< JID of the buddy */ + struct ao2_container *resources; /*!< Resources for the buddy */ + unsigned int subscribe:1; /*!< Need to subscribe to get their status */ +}; + +/*! \brief XMPP Client Connection */ +struct ast_xmpp_client { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); /*!< Name of the client configuration */ + ); + char mid[6]; /* Message ID */ + iksid *jid; + iksparser *parser; + iksfilter *filter; + ikstack *stack; +#ifdef HAVE_OPENSSL + SSL_CTX *ssl_context; + SSL *ssl_session; + const SSL_METHOD *ssl_method; + unsigned int stream_flags; +#endif /* HAVE_OPENSSL */ + enum xmpp_state state; + struct ao2_container *buddies; + AST_LIST_HEAD(, ast_xmpp_message) messages; + pthread_t thread; + int timeout; + unsigned int reconnect:1; /*!< Reconnect this client */ +}; + +/*! + * \brief Find an XMPP client connection using a given name + * + * \param name Name of the client connection + * + * \retval non-NULL on success + * \retval NULL on failure + * + * \note This will return the client connection with the reference count incremented by one. + */ +struct ast_xmpp_client *ast_xmpp_client_find(const char *name); + +/*! + * \brief Disconnect an XMPP client connection + * + * \param client Pointer to the client + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_xmpp_client_disconnect(struct ast_xmpp_client *client); + +/*! + * \brief Release XMPP client connection reference + * + * \param client Pointer to the client + */ +void ast_xmpp_client_unref(struct ast_xmpp_client *client); + +/*! + * \brief Lock an XMPP client connection + * + * \param client Pointer to the client + */ +void ast_xmpp_client_lock(struct ast_xmpp_client *client); + +/*! + * \brief Unlock an XMPP client connection + * + * \param client Pointer to the client + */ +void ast_xmpp_client_unlock(struct ast_xmpp_client *client); + +/*! + * \brief Send an XML stanza out using an established XMPP client connection + * + * \param client Pointer to the client + * \param stanza Pointer to the Iksemel stanza + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_xmpp_client_send(struct ast_xmpp_client *client, iks *stanza); + +/*! + * \brief Send a message to a given user using an established XMPP client connection + * + * \param client Pointer to the client + * \param user User the message should be sent to + * \param message The message to send + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_xmpp_client_send_message(struct ast_xmpp_client *client, const char *user, const char *message); + +/*! + * \brief Invite a user to an XMPP multi-user chatroom + * + * \param client Pointer to the client + * \param user JID of the user + * \param room Name of the chatroom + * \param message Message to send with the invitation + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_xmpp_chatroom_invite(struct ast_xmpp_client *client, const char *user, const char *room, const char *message); + +/*! + * \brief Join an XMPP multi-user chatroom + * + * \param client Pointer to the client + * \param room Name of the chatroom + * \param nickname Nickname to use + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_xmpp_chatroom_join(struct ast_xmpp_client *client, const char *room, const char *nickname); + +/*! + * \brief Send a message to an XMPP multi-user chatroom + * + * \param client Pointer to the client + * \param nickname Nickname to use + * \param Address Address of the room + * \param message Message itself + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_xmpp_chatroom_send(struct ast_xmpp_client *client, const char *nickname, const char *address, const char *message); + +/*! + * \brief Leave an XMPP multi-user chatroom + * + * \param client Pointer to the client + * \param room Name of the chatroom + * \param nickname Nickname being used + * + * \retval 0 on success + * \retval -1 on failure + */ +int ast_xmpp_chatroom_leave(struct ast_xmpp_client *client, const char *room, const char *nickname); + +/*! + * \brief Helper function which increments the message identifier + * + * \param mid Pointer to a string containing the message identifier + */ +void ast_xmpp_increment_mid(char *mid); + +#endif diff --git a/res/res_xmpp.c b/res/res_xmpp.c new file mode 100644 index 0000000000..c8ba09f850 --- /dev/null +++ b/res/res_xmpp.c @@ -0,0 +1,4320 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Digium, Inc. + * + * Joshua Colp + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief XMPP client and component module. + * + * \author Joshua Colp + * + * \extref Iksemel http://code.google.com/p/iksemel/ + * + * A refereouce module for interfacting Asterisk directly as a client or component with + * an XMPP/Jabber compliant server. + * + * This module is based upon the original res_jabber as done by Matt O'Gorman. + * + */ + +/*** MODULEINFO + no + iksemel + openssl + core + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include +#include + +#include "asterisk/xmpp.h" +#include "asterisk/module.h" +#include "asterisk/manager.h" +#include "asterisk/app.h" +#include "asterisk/message.h" +#include "asterisk/manager.h" +#include "asterisk/event.h" +#include "asterisk/cli.h" +#include "asterisk/config_options.h" + +/*** DOCUMENTATION + + + Sends an XMPP message to a buddy. + + + + The local named account to listen on (specified in + xmpp.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 xmpp.conf. + + + JABBER_STATUS + JABBER_RECEIVE + + + + + Reads XMPP messages. + + + + The local named account to listen on (specified in + xmpp.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 xmpp.conf. + + + JABBER_STATUS + JabberSend + + + + + Retrieves a buddy's status. + + + + The local named account to listen on (specified in + xmpp.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 xmpp.conf. + + + JABBER_RECEIVE + JabberSend + + + + + Send a Jabber Message to a specified chat room + + + + Client or transport Asterisk uses to connect to Jabber. + + + XMPP/Jabber JID (Name) of chat room. + + + Message to be sent to the chat room. + + + The nickname Asterisk uses in the chat room. + + + + 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. + + + + + Retrieve the status of a jabber list member + + + + Client or transport Asterisk users to connect to Jabber. + + + XMPP/Jabber JID (Name) of recipient. + + + Variable to store the status of requested user. + + + + This application is deprecated. Please use the JABBER_STATUS() function instead. + Retrieves the numeric status associated with the specified buddy JID. + The return value in the Variablewill be one of the following. + + + Online. + + + Chatty. + + + Away. + + + Extended Away. + + + Do Not Disturb. + + + Offline. + + + Not In Roster. + + + + + + + Sends a message to a Jabber Client. + + + + + Client or transport Asterisk uses to connect to JABBER. + + + XMPP/Jabber JID (Name) of recipient. + + + Message to be sent to the buddy. + + + + Sends a message to a Jabber Client. + + +***/ + +/*! \brief Supported general configuration flags */ +enum { + XMPP_AUTOPRUNE = (1 << 0), + XMPP_AUTOREGISTER = (1 << 1), + XMPP_AUTOACCEPT = (1 << 2), + XMPP_DEBUG = (1 << 3), + XMPP_USETLS = (1 << 4), + XMPP_USESASL = (1 << 5), + XMPP_FORCESSL = (1 << 6), + XMPP_KEEPALIVE = (1 << 7), + XMPP_COMPONENT = (1 << 8), + XMPP_SEND_TO_DIALPLAN = (1 << 9), + XMPP_DISTRIBUTE_EVENTS = (1 << 10), +}; + +/*! \brief Supported pubsub configuration flags */ +enum { + XMPP_XEP0248 = (1 << 0), + XMPP_PUBSUB = (1 << 1), + XMPP_PUBSUB_AUTOCREATE = (1 << 2), +}; + +/*! \brief Number of buckets for client connections */ +#define CLIENT_BUCKETS 53 + +/*! \brief Number of buckets for buddies (per client) */ +#define BUDDY_BUCKETS 53 + +/*! \brief Number of buckets for resources (per buddy) */ +#define RESOURCE_BUCKETS 53 + +/*! \brief Namespace for TLS support */ +#define XMPP_TLS_NS "urn:ietf:params:xml:ns:xmpp-tls" + +/*! \brief Status for a disappearing buddy */ +#define STATUS_DISAPPEAR 6 + +/*! \brief Global debug status */ +static int debug; + +/*! \brief XMPP Global Configuration */ +struct ast_xmpp_global_config { + struct ast_flags general; /*!< General configuration options */ + struct ast_flags pubsub; /*!< Pubsub related configuration options */ +}; + +/*! \brief XMPP Client Configuration */ +struct ast_xmpp_client_config { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(name); /*!< Name of the client connection */ + AST_STRING_FIELD(user); /*!< Username to use for authentication */ + AST_STRING_FIELD(password); /*!< Password to use for authentication */ + AST_STRING_FIELD(server); /*!< Server hostname */ + AST_STRING_FIELD(statusmsg); /*!< Status message for presence */ + AST_STRING_FIELD(pubsubnode); /*!< Pubsub node */ + AST_STRING_FIELD(context); /*!< Context for incoming messages */ + ); + int port; /*!< Port to use when connecting to server */ + int message_timeout; /*!< Timeout for messages */ + int priority; /*!< Resource priority */ + struct ast_flags flags; /*!< Various options that have been set */ + enum ikshowtype status; /*!< Presence status */ + struct ast_xmpp_client *client; /*!< Pointer to the client */ + struct ao2_container *buddies; /*!< Configured buddies */ +}; + +struct xmpp_config { + struct ast_xmpp_global_config *global; /*!< Global configuration options */ + struct ao2_container *clients; /*!< Configured clients */ +}; + +static AO2_GLOBAL_OBJ_STATIC(globals); + +static int xmpp_client_request_tls(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node); +static int xmpp_client_requested_tls(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node); +static int xmpp_client_authenticate(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node); +static int xmpp_client_authenticating(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node); + +static int xmpp_component_authenticate(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node); +static int xmpp_component_authenticating(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node); + +/*! \brief Defined handlers for XMPP client states */ +static const struct xmpp_state_handler { + int state; + int component; + int (*handler)(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node); +} xmpp_state_handlers[] = { + { XMPP_STATE_REQUEST_TLS, 0, xmpp_client_request_tls, }, + { XMPP_STATE_REQUESTED_TLS, 0, xmpp_client_requested_tls, }, + { XMPP_STATE_AUTHENTICATE, 0, xmpp_client_authenticate, }, + { XMPP_STATE_AUTHENTICATING, 0, xmpp_client_authenticating, }, + { XMPP_STATE_AUTHENTICATE, 1, xmpp_component_authenticate, }, + { XMPP_STATE_AUTHENTICATING, 1, xmpp_component_authenticating, }, +}; + +static int xmpp_pak_message(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, iks *node, ikspak *pak); +static int xmpp_pak_presence(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, iks *node, ikspak *pak); +static int xmpp_pak_s10n(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, iks *node, ikspak *pak); + +/*! \brief Defined handlers for different PAK types */ +static const struct xmpp_pak_handler { + int type; + int (*handler)(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, iks *node, ikspak *pak); +} xmpp_pak_handlers[] = { + { IKS_PAK_MESSAGE, xmpp_pak_message, }, + { IKS_PAK_PRESENCE, xmpp_pak_presence, }, + { IKS_PAK_S10N, xmpp_pak_s10n, }, +}; + +static const char *app_ajisend = "JabberSend"; +static const char *app_ajisendgroup = "JabberSendGroup"; +static const char *app_ajistatus = "JabberStatus"; +static const char *app_ajijoin = "JabberJoin"; +static const char *app_ajileave = "JabberLeave"; + +static struct ast_event_sub *mwi_sub = NULL; +static struct ast_event_sub *device_state_sub = NULL; + +static ast_cond_t message_received_condition; +static ast_mutex_t messagelock; + +static int xmpp_client_config_post_apply(void *obj, void *arg, int flags); + +/*! \brief Destructor function for configuration */ +static void ast_xmpp_client_config_destructor(void *obj) +{ + struct ast_xmpp_client_config *cfg = obj; + ast_string_field_free_memory(cfg); + ao2_cleanup(cfg->client); + ao2_cleanup(cfg->buddies); +} + +/*! \brief Destroy function for XMPP messages */ +static void xmpp_message_destroy(struct ast_xmpp_message *message) +{ + if (message->from) { + ast_free(message->from); + } + if (message->message) { + ast_free(message->message); + } + + ast_free(message); +} + +/*! \brief Destructor callback function for XMPP client */ +static void xmpp_client_destructor(void *obj) +{ + struct ast_xmpp_client *client = obj; + struct ast_xmpp_message *message; + + ast_xmpp_client_disconnect(client); + + if (client->stack) { + iks_stack_delete(client->stack); + } + + ao2_cleanup(client->buddies); + + while ((message = AST_LIST_REMOVE_HEAD(&client->messages, list))) { + xmpp_message_destroy(message); + } + AST_LIST_HEAD_DESTROY(&client->messages); +} + +/*! \brief Hashing function for XMPP buddy */ +static int xmpp_buddy_hash(const void *obj, const int flags) +{ + const struct ast_xmpp_buddy *buddy = obj; + const char *id = obj; + + return ast_str_hash(flags & OBJ_KEY ? id : buddy->id); +} + +/*! \brief Comparator function for XMPP buddy */ +static int xmpp_buddy_cmp(void *obj, void *arg, int flags) +{ + struct ast_xmpp_buddy *buddy1 = obj, *buddy2 = arg; + const char *id = arg; + + return !strcmp(buddy1->id, flags & OBJ_KEY ? id : buddy2->id) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Allocator function for ast_xmpp_client */ +static struct ast_xmpp_client *xmpp_client_alloc(const char *name) +{ + struct ast_xmpp_client *client; + + if (!(client = ao2_alloc(sizeof(*client), xmpp_client_destructor))) { + return NULL; + } + + AST_LIST_HEAD_INIT(&client->messages); + client->thread = AST_PTHREADT_NULL; + + if (!(client->buddies = ao2_container_alloc(BUDDY_BUCKETS, xmpp_buddy_hash, xmpp_buddy_cmp))) { + ast_log(LOG_ERROR, "Could not initialize buddy container for '%s'\n", name); + ao2_ref(client, -1); + return NULL; + } + + if (ast_string_field_init(client, 512)) { + ast_log(LOG_ERROR, "Could not initialize stringfields for '%s'\n", name); + ao2_ref(client, -1); + return NULL; + } + + if (!(client->stack = iks_stack_new(8192, 8192))) { + ast_log(LOG_ERROR, "Could not create an Iksemel stack for '%s'\n", name); + ao2_ref(client, -1); + return NULL; + } + + ast_string_field_set(client, name, name); + + client->timeout = 50; + client->state = XMPP_STATE_DISCONNECTED; + ast_copy_string(client->mid, "aaaaa", sizeof(client->mid)); + + return client; +} + +/*! \brief Find function for configuration */ +static void *xmpp_config_find(struct ao2_container *tmp_container, const char *category) +{ + return ao2_find(tmp_container, category, OBJ_KEY); +} + +/*! \brief Look up existing client or create a new one */ +static void *xmpp_client_find_or_create(const char *category) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, category))) { + return xmpp_client_alloc(category); + } + + ao2_ref(clientcfg->client, +1); + return clientcfg->client; +} + +/*! \brief Allocator function for configuration */ +static void *ast_xmpp_client_config_alloc(const char *cat) +{ + struct ast_xmpp_client_config *cfg; + + if (!(cfg = ao2_alloc(sizeof(*cfg), ast_xmpp_client_config_destructor))) { + return NULL; + } + + if (ast_string_field_init(cfg, 512)) { + ao2_ref(cfg, -1); + return NULL; + } + + if (!(cfg->client = xmpp_client_find_or_create(cat))) { + ao2_ref(cfg, -1); + return NULL; + } + + if (!(cfg->buddies = ao2_container_alloc(BUDDY_BUCKETS, xmpp_buddy_hash, xmpp_buddy_cmp))) { + ao2_ref(cfg, -1); + return NULL; + } + + ast_string_field_set(cfg, name, cat); + + return cfg; +} + +/*! \brief Destructor for XMPP configuration */ +static void xmpp_config_destructor(void *obj) +{ + struct xmpp_config *cfg = obj; + ao2_cleanup(cfg->global); + ao2_cleanup(cfg->clients); +} + +/*! \brief Hashing function for configuration */ +static int xmpp_config_hash(const void *obj, const int flags) +{ + const struct ast_xmpp_client_config *cfg = obj; + const char *name = (flags & OBJ_KEY) ? obj : cfg->name; + return ast_str_case_hash(name); +} + +/*! \brief Comparator function for configuration */ +static int xmpp_config_cmp(void *obj, void *arg, int flags) +{ + struct ast_xmpp_client_config *one = obj, *two = arg; + const char *match = (flags & OBJ_KEY) ? arg : two->name; + return strcasecmp(one->name, match) ? 0 : (CMP_MATCH | CMP_STOP); +} + +/*! \brief Allocator for XMPP configuration */ +static void *xmpp_config_alloc(void) +{ + struct xmpp_config *cfg; + + if (!(cfg = ao2_alloc(sizeof(*cfg), xmpp_config_destructor))) { + return NULL; + } + + if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), NULL))) { + goto error; + } + + ast_set_flag(&cfg->global->general, XMPP_AUTOREGISTER | XMPP_AUTOACCEPT | XMPP_USETLS | XMPP_USESASL | XMPP_KEEPALIVE); + + if (!(cfg->clients = ao2_container_alloc(1, xmpp_config_hash, xmpp_config_cmp))) { + goto error; + } + + return cfg; +error: + ao2_ref(cfg, -1); + return NULL; +} + +static int xmpp_config_prelink(void *newitem) +{ + struct ast_xmpp_client_config *clientcfg = newitem; + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, oldclientcfg, NULL, ao2_cleanup); + + if (ast_strlen_zero(clientcfg->user)) { + ast_log(LOG_ERROR, "No user specified on client '%s'\n", clientcfg->name); + return -1; + } else if (ast_strlen_zero(clientcfg->password)) { + ast_log(LOG_ERROR, "No password specified on client '%s'\n", clientcfg->name); + return -1; + } else if (ast_strlen_zero(clientcfg->server)) { + ast_log(LOG_ERROR, "No server specified on client '%s'\n", clientcfg->name); + return -1; + } + + /* If this is a new connection force a reconnect */ + if (!cfg || !cfg->clients || !(oldclientcfg = xmpp_config_find(cfg->clients, clientcfg->name))) { + clientcfg->client->reconnect = 1; + return 0; + } + + /* If any configuration options are changing that would require reconnecting set the bit so we will do so if possible */ + if (strcmp(clientcfg->user, oldclientcfg->user) || + strcmp(clientcfg->password, oldclientcfg->password) || + strcmp(clientcfg->server, oldclientcfg->server) || + (clientcfg->port != oldclientcfg->port) || + (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT) != ast_test_flag(&oldclientcfg->flags, XMPP_COMPONENT)) || + (clientcfg->priority != oldclientcfg->priority)) { + clientcfg->client->reconnect = 1; + } else { + clientcfg->client->reconnect = 0; + } + + return 0; +} + +static void xmpp_config_post_apply(void) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + + ao2_callback(cfg->clients, OBJ_NODATA | OBJ_MULTIPLE, xmpp_client_config_post_apply, NULL); +} + +static struct aco_type global_option = { + .type = ACO_GLOBAL, + .item_offset = offsetof(struct xmpp_config, global), + .category_match = ACO_WHITELIST, + .category = "^general$", +}; + +struct aco_type *global_options[] = ACO_TYPES(&global_option); + +static struct aco_type client_option = { + .type = ACO_ITEM, + .category_match = ACO_BLACKLIST, + .category = "^(general)$", + .item_alloc = ast_xmpp_client_config_alloc, + .item_find = xmpp_config_find, + .item_prelink = xmpp_config_prelink, + .item_offset = offsetof(struct xmpp_config, clients), +}; + +struct aco_type *client_options[] = ACO_TYPES(&client_option); + +struct aco_file res_xmpp_conf = { + .filename = "xmpp.conf", + .alias = "jabber.conf", + .types = ACO_TYPES(&global_option, &client_option), +}; + +CONFIG_INFO_STANDARD(cfg_info, globals, xmpp_config_alloc, + .files = ACO_FILES(&res_xmpp_conf), + .post_apply_config = xmpp_config_post_apply, + ); + +/*! \brief Destructor callback function for XMPP resource */ +static void xmpp_resource_destructor(void *obj) +{ + struct ast_xmpp_resource *resource = obj; + + if (resource->description) { + ast_free(resource->description); + } +} + +/*! \brief Hashing function for XMPP resource */ +static int xmpp_resource_hash(const void *obj, const int flags) +{ + const struct ast_xmpp_resource *resource = obj; + + return flags & OBJ_KEY ? -1 : resource->priority; +} + +/*! \brief Comparator function for XMPP resource */ +static int xmpp_resource_cmp(void *obj, void *arg, int flags) +{ + struct ast_xmpp_resource *resource1 = obj, *resource2 = arg; + const char *resource = arg; + + return !strcmp(resource1->resource, flags & OBJ_KEY ? resource : resource2->resource) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief Destructor callback function for XMPP buddy */ +static void xmpp_buddy_destructor(void *obj) +{ + struct ast_xmpp_buddy *buddy = obj; + + if (buddy->resources) { + ao2_ref(buddy->resources, -1); + } +} + +/*! \brief Helper function which returns whether an XMPP client connection is secure or not */ +static int xmpp_is_secure(struct ast_xmpp_client *client) +{ +#ifdef HAVE_OPENSSL + return client->stream_flags & SECURE; +#else + return 0; +#endif +} + +struct ast_xmpp_client *ast_xmpp_client_find(const char *name) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, name))) { + return NULL; + } + + ao2_ref(clientcfg->client, +1); + return clientcfg->client; +} + +void ast_xmpp_client_unref(struct ast_xmpp_client *client) +{ + ao2_ref(client, -1); +} + +void ast_xmpp_client_lock(struct ast_xmpp_client *client) +{ + ao2_lock(client); +} + +void ast_xmpp_client_unlock(struct ast_xmpp_client *client) +{ + ao2_unlock(client); +} + +/*! \brief Internal function used to send a message to a user or chatroom */ +static int xmpp_client_send_message(struct ast_xmpp_client *client, int group, const char *nick, const char *address, const char *message) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + int res = 0; + char from[XMPP_MAX_JIDLEN]; + iks *message_packet; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || + !(message_packet = iks_make_msg(group ? IKS_TYPE_GROUPCHAT : IKS_TYPE_CHAT, address, message))) { + return -1; + } + + if (!ast_strlen_zero(nick) && ast_test_flag(&clientcfg->flags, XMPP_COMPONENT)) { + snprintf(from, sizeof(from), "%s@%s/%s", nick, client->jid->full, nick); + } else { + snprintf(from, sizeof(from), "%s", client->jid->full); + } + + iks_insert_attrib(message_packet, "from", from); + + res = ast_xmpp_client_send(client, message_packet); + + iks_delete(message_packet); + + return res; +} + +int ast_xmpp_client_send_message(struct ast_xmpp_client *client, const char *user, const char *message) +{ + return xmpp_client_send_message(client, 0, NULL, user, message); +} + +int ast_xmpp_chatroom_invite(struct ast_xmpp_client *client, const char *user, const char *room, const char *message) +{ + int res = 0; + iks *invite, *body = NULL, *namespace = NULL; + + if (!(invite = iks_new("message")) || !(body = iks_new("body")) || !(namespace = iks_new("x"))) { + res = -1; + goto done; + } + + iks_insert_attrib(invite, "to", user); + ast_xmpp_client_lock(client); + iks_insert_attrib(invite, "id", client->mid); + ast_xmpp_increment_mid(client->mid); + ast_xmpp_client_unlock(client); + iks_insert_cdata(body, message, 0); + iks_insert_node(invite, body); + iks_insert_attrib(namespace, "xmlns", "jabber:x:conference"); + iks_insert_attrib(namespace, "jid", room); + iks_insert_node(invite, namespace); + + res = ast_xmpp_client_send(client, invite); + +done: + iks_delete(namespace); + iks_delete(body); + iks_delete(invite); + + return res; +} + +static int xmpp_client_set_group_presence(struct ast_xmpp_client *client, const char *room, int level, const char *nick) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + int res = 0; + iks *presence = NULL, *x = NULL; + char from[XMPP_MAX_JIDLEN], roomid[XMPP_MAX_JIDLEN]; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || + !(presence = iks_make_pres(level, NULL)) || !(x = iks_new("x"))) { + res = -1; + goto done; + } + + if (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT)) { + snprintf(from, sizeof(from), "%s@%s/%s", nick, client->jid->full, nick); + snprintf(roomid, sizeof(roomid), "%s/%s", room, nick); + } else { + snprintf(from, sizeof(from), "%s", client->jid->full); + snprintf(roomid, sizeof(roomid), "%s/%s", room, S_OR(nick, client->jid->user)); + } + + iks_insert_attrib(presence, "to", roomid); + iks_insert_attrib(presence, "from", from); + iks_insert_attrib(x, "xmlns", "http://jabber.org/protocol/muc"); + iks_insert_node(presence, x); + + res = ast_xmpp_client_send(client, presence); + +done: + iks_delete(x); + iks_delete(presence); + + return res; +} + +int ast_xmpp_chatroom_join(struct ast_xmpp_client *client, const char *room, const char *nickname) +{ + return xmpp_client_set_group_presence(client, room, IKS_SHOW_AVAILABLE, nickname); +} + +int ast_xmpp_chatroom_send(struct ast_xmpp_client *client, const char *nickname, const char *address, const char *message) +{ + return xmpp_client_send_message(client, 1, nickname, address, message); +} + +int ast_xmpp_chatroom_leave(struct ast_xmpp_client *client, const char *room, const char *nickname) +{ + return xmpp_client_set_group_presence(client, room, IKS_SHOW_UNAVAILABLE, nickname); +} + +void ast_xmpp_increment_mid(char *mid) +{ + int i = 0; + + for (i = strlen(mid) - 1; i >= 0; i--) { + if (mid[i] != 'z') { + mid[i] = mid[i] + 1; + i = 0; + } else { + mid[i] = 'a'; + } + } +} + +/*! + * \brief Create an IQ packet + * \param client the configured XMPP client we use to connect to a XMPP server + * \param type the type of IQ packet to create + * \return iks* + */ +static iks* xmpp_pubsub_iq_create(struct ast_xmpp_client *client, const char *type) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + iks *request; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || + !(request = iks_new("iq"))) { + return NULL; + } + + + iks_insert_attrib(request, "to", clientcfg->pubsubnode); + iks_insert_attrib(request, "from", client->jid->full); + iks_insert_attrib(request, "type", type); + ast_xmpp_client_lock(client); + ast_xmpp_increment_mid(client->mid); + iks_insert_attrib(request, "id", client->mid); + ast_xmpp_client_unlock(client); + + return request; +} + +/*! + * \brief Build the skeleton of a publish + * \param client the configured XMPP client we use to connect to a XMPP server + * \param node Name of the node that will be published to + * \param event_type + * \return iks * + */ +static iks* xmpp_pubsub_build_publish_skeleton(struct ast_xmpp_client *client, const char *node, + const char *event_type) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + iks *request, *pubsub, *publish, *item; + + if (!cfg || !cfg->global || !(request = xmpp_pubsub_iq_create(client, "set"))) { + return NULL; + } + + pubsub = iks_insert(request, "pubsub"); + iks_insert_attrib(pubsub, "xmlns", "http://jabber.org/protocol/pubsub"); + publish = iks_insert(pubsub, "publish"); + iks_insert_attrib(publish, "node", ast_test_flag(&cfg->global->pubsub, XMPP_XEP0248) ? node : event_type); + item = iks_insert(publish, "item"); + iks_insert_attrib(item, "id", node); + + return item; + +} + +static iks* xmpp_pubsub_build_node_config(iks *pubsub, const char *node_type, const char *collection_name) +{ + iks *configure, *x, *field_owner, *field_node_type, *field_node_config, + *field_deliver_payload, *field_persist_items, *field_access_model, + *field_pubsub_collection; + configure = iks_insert(pubsub, "configure"); + x = iks_insert(configure, "x"); + iks_insert_attrib(x, "xmlns", "jabber:x:data"); + iks_insert_attrib(x, "type", "submit"); + field_owner = iks_insert(x, "field"); + iks_insert_attrib(field_owner, "var", "FORM_TYPE"); + iks_insert_attrib(field_owner, "type", "hidden"); + iks_insert_cdata(iks_insert(field_owner, "value"), + "http://jabber.org/protocol/pubsub#owner", 39); + if (node_type) { + field_node_type = iks_insert(x, "field"); + iks_insert_attrib(field_node_type, "var", "pubsub#node_type"); + iks_insert_cdata(iks_insert(field_node_type, "value"), node_type, strlen(node_type)); + } + field_node_config = iks_insert(x, "field"); + iks_insert_attrib(field_node_config, "var", "FORM_TYPE"); + iks_insert_attrib(field_node_config, "type", "hidden"); + iks_insert_cdata(iks_insert(field_node_config, "value"), + "http://jabber.org/protocol/pubsub#node_config", 45); + field_deliver_payload = iks_insert(x, "field"); + iks_insert_attrib(field_deliver_payload, "var", "pubsub#deliver_payloads"); + iks_insert_cdata(iks_insert(field_deliver_payload, "value"), "1", 1); + field_persist_items = iks_insert(x, "field"); + iks_insert_attrib(field_persist_items, "var", "pubsub#persist_items"); + iks_insert_cdata(iks_insert(field_persist_items, "value"), "1", 1); + field_access_model = iks_insert(x, "field"); + iks_insert_attrib(field_access_model, "var", "pubsub#access_model"); + iks_insert_cdata(iks_insert(field_access_model, "value"), "whitelist", 9); + if (node_type && !strcasecmp(node_type, "leaf")) { + field_pubsub_collection = iks_insert(x, "field"); + iks_insert_attrib(field_pubsub_collection, "var", "pubsub#collection"); + iks_insert_cdata(iks_insert(field_pubsub_collection, "value"), collection_name, + strlen(collection_name)); + } + return configure; +} + +/*! + * \brief Add Owner affiliations for pubsub node + * \param client the configured XMPP client we use to connect to a XMPP server + * \param node the name of the node to which to add affiliations + * \return void + */ +static void xmpp_pubsub_create_affiliations(struct ast_xmpp_client *client, const char *node) +{ + iks *modify_affiliates = xmpp_pubsub_iq_create(client, "set"); + iks *pubsub, *affiliations, *affiliate; + struct ao2_iterator i; + struct ast_xmpp_buddy *buddy; + + if (!modify_affiliates) { + ast_log(LOG_ERROR, "Could not create IQ for creating affiliations on client '%s'\n", client->name); + return; + } + + pubsub = iks_insert(modify_affiliates, "pubsub"); + iks_insert_attrib(pubsub, "xmlns", "http://jabber.org/protocol/pubsub#owner"); + affiliations = iks_insert(pubsub, "affiliations"); + iks_insert_attrib(affiliations, "node", node); + + i = ao2_iterator_init(client->buddies, 0); + while ((buddy = ao2_iterator_next(&i))) { + affiliate = iks_insert(affiliations, "affiliation"); + iks_insert_attrib(affiliate, "jid", buddy->id); + iks_insert_attrib(affiliate, "affiliation", "owner"); + ao2_ref(buddy, -1); + } + ao2_iterator_destroy(&i); + + ast_xmpp_client_send(client, modify_affiliates); + iks_delete(modify_affiliates); +} + +/*! + * \brief Create a pubsub node + * \param client the configured XMPP client we use to connect to a XMPP server + * \param node_type the type of node to create + * \param name the name of the node to create + * \return void + */ +static void xmpp_pubsub_create_node(struct ast_xmpp_client *client, const char *node_type, const + char *name, const char *collection_name) +{ + iks *node, *pubsub, *create; + + if (!(node = xmpp_pubsub_iq_create(client, "set"))) { + return; + } + + pubsub = iks_insert(node, "pubsub"); + iks_insert_attrib(pubsub, "xmlns", "http://jabber.org/protocol/pubsub"); + create = iks_insert(pubsub, "create"); + iks_insert_attrib(create, "node", name); + xmpp_pubsub_build_node_config(pubsub, node_type, collection_name); + ast_xmpp_client_send(client, node); + xmpp_pubsub_create_affiliations(client, name); + iks_delete(node); +} + +/*! + * \brief Delete a PubSub node + * \param client the configured XMPP client we use to connect to a XMPP server + * \param node_name the name of the node to delete + * return void + */ +static void xmpp_pubsub_delete_node(struct ast_xmpp_client *client, const char *node_name) +{ + iks *request, *pubsub, *delete; + + if (!(request = xmpp_pubsub_iq_create(client, "set"))) { + return; + } + + pubsub = iks_insert(request, "pubsub"); + iks_insert_attrib(pubsub, "xmlns", "http://jabber.org/protocol/pubsub#owner"); + delete = iks_insert(pubsub, "delete"); + iks_insert_attrib(delete, "node", node_name); + ast_xmpp_client_send(client, request); + + iks_delete(delete); + iks_delete(pubsub); + iks_delete(request); +} + +/*! + * \brief Create a PubSub collection node. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param collection_name The name to use for this collection + * \return void. + */ +static void xmpp_pubsub_create_collection(struct ast_xmpp_client *client, const char *collection_name) +{ + xmpp_pubsub_create_node(client, "collection", collection_name, NULL); +} + + +/*! + * \brief Create a PubSub leaf node. + * \param client the configured XMPP client we use to connect to a XMPP server + * \param leaf_name The name to use for this collection + * \return void. + */ +static void xmpp_pubsub_create_leaf(struct ast_xmpp_client *client, const char *collection_name, + const char *leaf_name) +{ + xmpp_pubsub_create_node(client, "leaf", leaf_name, collection_name); +} + +/*! + * \brief Publish MWI to a PubSub node + * \param client the configured XMPP client we use to connect to a XMPP server + * \param device the name of the device whose state to publish + * \param device_state the state to publish + * \return void + */ +static void xmpp_pubsub_publish_mwi(struct ast_xmpp_client *client, const char *mailbox, + const char *context, const char *oldmsgs, const char *newmsgs) +{ + char full_mailbox[AST_MAX_EXTENSION+AST_MAX_CONTEXT], eid_str[20]; + iks *mailbox_node, *request; + + snprintf(full_mailbox, sizeof(full_mailbox), "%s@%s", mailbox, context); + + if (!(request = xmpp_pubsub_build_publish_skeleton(client, full_mailbox, "message_waiting"))) { + return; + } + + ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default); + mailbox_node = iks_insert(request, "mailbox"); + iks_insert_attrib(mailbox_node, "xmlns", "http://asterisk.org"); + iks_insert_attrib(mailbox_node, "eid", eid_str); + iks_insert_cdata(iks_insert(mailbox_node, "NEWMSGS"), newmsgs, strlen(newmsgs)); + iks_insert_cdata(iks_insert(mailbox_node, "OLDMSGS"), oldmsgs, strlen(oldmsgs)); + + ast_xmpp_client_send(client, iks_root(request)); + + iks_delete(request); +} + +/*! + * \brief Publish device state to a PubSub node + * \param client the configured XMPP client we use to connect to a XMPP server + * \param device the name of the device whose state to publish + * \param device_state the state to publish + * \return void + */ +static void xmpp_pubsub_publish_device_state(struct ast_xmpp_client *client, const char *device, + const char *device_state) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + iks *request, *state; + char eid_str[20]; + + if (!cfg || !cfg->global || !(request = xmpp_pubsub_build_publish_skeleton(client, device, "device_state"))) { + return; + } + + if (ast_test_flag(&cfg->global->pubsub, XMPP_PUBSUB_AUTOCREATE)) { + if (ast_test_flag(&cfg->global->pubsub, XMPP_XEP0248)) { + xmpp_pubsub_create_node(client, "leaf", device, "device_state"); + } else { + xmpp_pubsub_create_node(client, NULL, device, NULL); + } + } + + ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default); + state = iks_insert(request, "state"); + iks_insert_attrib(state, "xmlns", "http://asterisk.org"); + iks_insert_attrib(state, "eid", eid_str); + iks_insert_cdata(state, device_state, strlen(device_state)); + ast_xmpp_client_send(client, iks_root(request)); + iks_delete(request); +} + +/*! + * \brief Callback function for MWI events + * \param ast_event + * \param data void pointer to ast_client structure + * \return void + */ +static void xmpp_pubsub_mwi_cb(const struct ast_event *ast_event, void *data) +{ + struct ast_xmpp_client *client = data; + const char *mailbox, *context; + char oldmsgs[10], newmsgs[10]; + + if (ast_eid_cmp(&ast_eid_default, ast_event_get_ie_raw(ast_event, AST_EVENT_IE_EID))) { + /* If the event didn't originate from this server, don't send it back out. */ + ast_debug(1, "Returning here\n"); + return; + } + + mailbox = ast_event_get_ie_str(ast_event, AST_EVENT_IE_MAILBOX); + context = ast_event_get_ie_str(ast_event, AST_EVENT_IE_CONTEXT); + snprintf(oldmsgs, sizeof(oldmsgs), "%d", + ast_event_get_ie_uint(ast_event, AST_EVENT_IE_OLDMSGS)); + snprintf(newmsgs, sizeof(newmsgs), "%d", + ast_event_get_ie_uint(ast_event, AST_EVENT_IE_NEWMSGS)); + xmpp_pubsub_publish_mwi(client, mailbox, context, oldmsgs, newmsgs); +} + +/*! + * \brief Callback function for device state events + * \param ast_event + * \param data void pointer to ast_client structure + * \return void + */ +static void xmpp_pubsub_devstate_cb(const struct ast_event *ast_event, void *data) +{ + struct ast_xmpp_client *client = data; + const char *device, *device_state; + + if (ast_eid_cmp(&ast_eid_default, ast_event_get_ie_raw(ast_event, AST_EVENT_IE_EID))) { + /* If the event didn't originate from this server, don't send it back out. */ + ast_debug(1, "Returning here\n"); + return; + } + + device = ast_event_get_ie_str(ast_event, AST_EVENT_IE_DEVICE); + device_state = ast_devstate_str(ast_event_get_ie_uint(ast_event, AST_EVENT_IE_STATE)); + xmpp_pubsub_publish_device_state(client, device, device_state); +} + +/*! + * \brief Subscribe to a PubSub node + * \param client the configured XMPP client we use to connect to a XMPP server + * \param node the name of the node to which to subscribe + * \return void + */ +static void xmpp_pubsub_subscribe(struct ast_xmpp_client *client, const char *node) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + iks *request = xmpp_pubsub_iq_create(client, "set"); + iks *pubsub, *subscribe; + + if (!cfg || !cfg->global || !request) { + ast_log(LOG_ERROR, "Could not create IQ when creating pubsub subscription on client '%s'\n", client->name); + return; + } + + pubsub = iks_insert(request, "pubsub"); + iks_insert_attrib(pubsub, "xmlns", "http://jabber.org/protocol/pubsub"); + subscribe = iks_insert(pubsub, "subscribe"); + iks_insert_attrib(subscribe, "jid", client->jid->partial); + iks_insert_attrib(subscribe, "node", node); + if (ast_test_flag(&cfg->global->pubsub, XMPP_XEP0248)) { + iks *options, *x, *sub_options, *sub_type, *sub_depth; + options = iks_insert(pubsub, "options"); + x = iks_insert(options, "x"); + iks_insert_attrib(x, "xmlns", "jabber:x:data"); + iks_insert_attrib(x, "type", "submit"); + sub_options = iks_insert(x, "field"); + iks_insert_attrib(sub_options, "var", "FORM_TYPE"); + iks_insert_attrib(sub_options, "type", "hidden"); + iks_insert_cdata(iks_insert(sub_options, "value"), + "http://jabber.org/protocol/pubsub#subscribe_options", 51); + sub_type = iks_insert(x, "field"); + iks_insert_attrib(sub_type, "var", "pubsub#subscription_type"); + iks_insert_cdata(iks_insert(sub_type, "value"), "items", 5); + sub_depth = iks_insert(x, "field"); + iks_insert_attrib(sub_type, "var", "pubsub#subscription_depth"); + iks_insert_cdata(iks_insert(sub_depth, "value"), "all", 3); + } + ast_xmpp_client_send(client, request); + iks_delete(request); +} + +/*! + * \brief Callback for handling PubSub events + * \param data void pointer to ast_xmpp_client structure + * \return IKS_FILTER_EAT + */ +static int xmpp_pubsub_handle_event(void *data, ikspak *pak) +{ + char *item_id, *device_state, *context; + int oldmsgs, newmsgs; + iks *item, *item_content; + struct ast_eid pubsub_eid; + struct ast_event *event; + item = iks_find(iks_find(iks_find(pak->x, "event"), "items"), "item"); + if (!item) { + ast_log(LOG_ERROR, "Could not parse incoming PubSub event\n"); + return IKS_FILTER_EAT; + } + item_id = iks_find_attrib(item, "id"); + item_content = iks_child(item); + ast_str_to_eid(&pubsub_eid, iks_find_attrib(item_content, "eid")); + if (!ast_eid_cmp(&ast_eid_default, &pubsub_eid)) { + ast_debug(1, "Returning here, eid of incoming event matches ours!\n"); + return IKS_FILTER_EAT; + } + if (!strcasecmp(iks_name(item_content), "state")) { + device_state = iks_find_cdata(item, "state"); + if (!(event = ast_event_new(AST_EVENT_DEVICE_STATE_CHANGE, + AST_EVENT_IE_DEVICE, AST_EVENT_IE_PLTYPE_STR, item_id, AST_EVENT_IE_STATE, + AST_EVENT_IE_PLTYPE_UINT, ast_devstate_val(device_state), AST_EVENT_IE_EID, + AST_EVENT_IE_PLTYPE_RAW, &pubsub_eid, sizeof(pubsub_eid), + AST_EVENT_IE_END))) { + return IKS_FILTER_EAT; + } + } else if (!strcasecmp(iks_name(item_content), "mailbox")) { + context = strsep(&item_id, "@"); + sscanf(iks_find_cdata(item_content, "OLDMSGS"), "%10d", &oldmsgs); + sscanf(iks_find_cdata(item_content, "NEWMSGS"), "%10d", &newmsgs); + if (!(event = ast_event_new(AST_EVENT_MWI, AST_EVENT_IE_MAILBOX, + AST_EVENT_IE_PLTYPE_STR, item_id, AST_EVENT_IE_CONTEXT, + AST_EVENT_IE_PLTYPE_STR, context, AST_EVENT_IE_OLDMSGS, + AST_EVENT_IE_PLTYPE_UINT, oldmsgs, AST_EVENT_IE_NEWMSGS, + AST_EVENT_IE_PLTYPE_UINT, newmsgs, AST_EVENT_IE_EID, AST_EVENT_IE_PLTYPE_RAW, + &pubsub_eid, sizeof(pubsub_eid), AST_EVENT_IE_END))) { + return IKS_FILTER_EAT; + } + } else { + ast_debug(1, "Don't know how to handle PubSub event of type %s\n", + iks_name(item_content)); + return IKS_FILTER_EAT; + } + ast_event_queue_and_cache(event); + return IKS_FILTER_EAT; +} + +static int xmpp_pubsub_handle_error(void *data, ikspak *pak) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + char *node_name, *error; + int error_num; + iks *orig_request, *orig_pubsub = iks_find(pak->x, "pubsub"); + struct ast_xmpp_client *client = data; + + if (!cfg || !cfg->global) { + ast_log(LOG_ERROR, "No global configuration available\n"); + return IKS_FILTER_EAT; + } + + if (!orig_pubsub) { + ast_log(LOG_ERROR, "Error isn't a PubSub error, why are we here?\n"); + return IKS_FILTER_EAT; + } + + orig_request = iks_child(orig_pubsub); + error = iks_find_attrib(iks_find(pak->x, "error"), "code"); + node_name = iks_find_attrib(orig_request, "node"); + + if (!sscanf(error, "%30d", &error_num)) { + return IKS_FILTER_EAT; + } + + if (error_num > 399 && error_num < 500 && error_num != 404) { + ast_log(LOG_ERROR, + "Error performing operation on PubSub node %s, %s.\n", node_name, error); + return IKS_FILTER_EAT; + } else if (error_num > 499 && error_num < 600) { + ast_log(LOG_ERROR, "PubSub Server error, %s\n", error); + return IKS_FILTER_EAT; + } + + if (!strcasecmp(iks_name(orig_request), "publish")) { + iks *request; + + if (ast_test_flag(&cfg->global->pubsub, XMPP_XEP0248)) { + if (iks_find(iks_find(orig_request, "item"), "state")) { + xmpp_pubsub_create_leaf(client, "device_state", node_name); + } else if (iks_find(iks_find(orig_request, "item"), "mailbox")) { + xmpp_pubsub_create_leaf(client, "message_waiting", node_name); + } + } else { + xmpp_pubsub_create_node(client, NULL, node_name, NULL); + } + + if ((request = xmpp_pubsub_iq_create(client, "set"))) { + iks_insert_node(request, orig_pubsub); + ast_xmpp_client_send(client, request); + iks_delete(request); + } else { + ast_log(LOG_ERROR, "PubSub publish could not create IQ\n"); + } + + return IKS_FILTER_EAT; + } else if (!strcasecmp(iks_name(orig_request), "subscribe")) { + if (ast_test_flag(&cfg->global->pubsub, XMPP_XEP0248)) { + xmpp_pubsub_create_collection(client, node_name); + } else { + xmpp_pubsub_create_node(client, NULL, node_name, NULL); + } + } + + return IKS_FILTER_EAT; +} + +/*! + * \brief Initialize collections for event distribution + * \param client the configured XMPP client we use to connect to a XMPP server + * \return void + */ +static void xmpp_init_event_distribution(struct ast_xmpp_client *client) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name))) { + return; + } + + if (!mwi_sub) { + mwi_sub = ast_event_subscribe(AST_EVENT_MWI, xmpp_pubsub_mwi_cb, "xmpp_pubsub_mwi_subscription", + client, AST_EVENT_IE_END); + } + if (!device_state_sub) { + if (ast_enable_distributed_devstate()) { + return; + } + device_state_sub = ast_event_subscribe(AST_EVENT_DEVICE_STATE_CHANGE, + xmpp_pubsub_devstate_cb, "xmpp_pubsub_devstate_subscription", client, AST_EVENT_IE_END); + ast_event_dump_cache(device_state_sub); + } + + xmpp_pubsub_subscribe(client, "device_state"); + xmpp_pubsub_subscribe(client, "message_waiting"); + iks_filter_add_rule(client->filter, xmpp_pubsub_handle_event, client, IKS_RULE_TYPE, + IKS_PAK_MESSAGE, IKS_RULE_FROM, clientcfg->pubsubnode, IKS_RULE_DONE); + iks_filter_add_rule(client->filter, xmpp_pubsub_handle_error, client, IKS_RULE_TYPE, + IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_ERROR, IKS_RULE_DONE); + +} + +/*! \brief Internal astobj2 callback function which returns the first resource, which is the highest priority one */ +static int xmpp_resource_immediate(void *obj, void *arg, int flags) +{ + return CMP_MATCH | CMP_STOP; +} + +/* + * \internal + * \brief Dial plan function status(). puts the status of watched user + * into a channel variable. + * \param chan ast_channel + * \param data + * \retval 0 success + * \retval -1 error + */ +static int xmpp_status_exec(struct ast_channel *chan, const char *data) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_buddy *buddy; + struct ast_xmpp_resource *resource; + char *s = NULL, status[2]; + int stat = 7; + static int deprecation_warning = 0; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + AST_APP_ARG(variable); + ); + AST_DECLARE_APP_ARGS(jid, + AST_APP_ARG(screenname); + AST_APP_ARG(resource); + ); + + if (deprecation_warning++ % 10 == 0) { + ast_log(LOG_WARNING, "JabberStatus is deprecated. Please use the JABBER_STATUS dialplan function in the future.\n"); + } + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Usage: JabberStatus(,[/],\n"); + return 0; + } + s = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, s); + + if (args.argc != 3) { + ast_log(LOG_ERROR, "JabberStatus() requires 3 arguments.\n"); + return -1; + } + + 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 (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, args.sender))) { + ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + if (!(buddy = ao2_find(clientcfg->client->buddies, jid.screenname, OBJ_KEY))) { + ast_log(LOG_WARNING, "Could not find buddy in list: '%s'\n", jid.screenname); + return -1; + } + + if (ast_strlen_zero(jid.resource) || !(resource = ao2_find(buddy->resources, jid.resource, OBJ_KEY))) { + resource = ao2_callback(buddy->resources, OBJ_NODATA, xmpp_resource_immediate, NULL); + } + + ao2_ref(buddy, -1); + + if (resource) { + stat = resource->status; + ao2_ref(resource, -1); + } else { + ast_log(LOG_NOTICE, "Resource '%s' of buddy '%s' was not found\n", jid.resource, jid.screenname); + } + + snprintf(status, sizeof(status), "%d", stat); + pbx_builtin_setvar_helper(chan, args.variable, status); + + 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) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_buddy *buddy; + struct ast_xmpp_resource *resource; + int stat = 7; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + ); + AST_DECLARE_APP_ARGS(jid, + AST_APP_ARG(screenname); + AST_APP_ARG(resource); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Usage: JABBER_STATUS(,[/])\n"); + return 0; + } + AST_STANDARD_APP_ARGS(args, data); + + if (args.argc != 2) { + ast_log(LOG_ERROR, "JABBER_STATUS requires 2 arguments: sender and jid.\n"); + return -1; + } + + 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 (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, args.sender))) { + ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + if (!(buddy = ao2_find(clientcfg->client->buddies, jid.screenname, OBJ_KEY))) { + ast_log(LOG_WARNING, "Could not find buddy in list: '%s'\n", jid.screenname); + return -1; + } + + if (ast_strlen_zero(jid.resource) || !(resource = ao2_find(buddy->resources, jid.resource, OBJ_KEY))) { + resource = ao2_callback(buddy->resources, OBJ_NODATA, xmpp_resource_immediate, NULL); + } + + ao2_ref(buddy, -1); + + if (resource) { + stat = resource->status; + ao2_ref(resource, -1); + } else { + ast_log(LOG_NOTICE, "Resource %s of buddy %s was not found.\n", jid.resource, jid.screenname); + } + + snprintf(buf, buflen, "%d", stat); + + return 0; +} + +static struct ast_custom_function jabberstatus_function = { + .name = "JABBER_STATUS", + .read = acf_jabberstatus_read, +}; + +/*! + * \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 xmpp_join_exec(struct ast_channel *chan, const char *data) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + char *s, nick[XMPP_MAX_RESJIDLEN]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + AST_APP_ARG(nick); + ); + + if (ast_strlen_zero(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 (strchr(args.jid, '/')) { + ast_log(LOG_ERROR, "Invalid room name : resource must not be appended\n"); + return -1; + } + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, args.sender))) { + ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + if (ast_strlen_zero(args.nick)) { + if (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT)) { + snprintf(nick, sizeof(nick), "asterisk"); + } else { + snprintf(nick, sizeof(nick), "%s", clientcfg->client->jid->user); + } + } else { + snprintf(nick, sizeof(nick), "%s", args.nick); + } + + if (!ast_strlen_zero(args.jid) && strchr(args.jid, '@')) { + ast_xmpp_chatroom_join(clientcfg->client, args.jid, nick); + } else { + ast_log(LOG_ERROR, "Problem with specified jid of '%s'\n", args.jid); + } + + 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 xmpp_leave_exec(struct ast_channel *chan, const char *data) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + char *s, nick[XMPP_MAX_RESJIDLEN]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(jid); + AST_APP_ARG(nick); + ); + + if (ast_strlen_zero(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 (strchr(args.jid, '/')) { + ast_log(LOG_ERROR, "Invalid room name, resource must not be appended\n"); + return -1; + } + + if (ast_strlen_zero(args.jid) || !strchr(args.jid, '@')) { + ast_log(LOG_ERROR, "No jabber ID specified\n"); + return -1; + } + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, args.sender))) { + ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + if (ast_strlen_zero(args.nick)) { + if (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT)) { + snprintf(nick, sizeof(nick), "asterisk"); + } else { + snprintf(nick, sizeof(nick), "%s", clientcfg->client->jid->user); + } + } else { + snprintf(nick, sizeof(nick), "%s", args.nick); + } + + ast_xmpp_chatroom_leave(clientcfg->client, args.jid, nick); + + return 0; +} + +/*! + * \internal + * \brief Dial plan function to send a message. + * \param chan ast_channel + * \param data Data is account,jid,message. + * \retval 0 success + * \retval -1 failure + */ +static int xmpp_send_exec(struct ast_channel *chan, const char *data) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + char *s; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(recipient); + AST_APP_ARG(message); + ); + + if (ast_strlen_zero(data)) { + 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_strlen_zero(args.message) || !strchr(args.recipient, '2')) { + ast_log(LOG_WARNING, "%s requires arguments (account,jid,message)\n", app_ajisend); + return -1; + } + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, args.sender))) { + ast_log(LOG_WARNING, "Could not find sender connection: '%s'\n", args.sender); + return -1; + } + + ast_xmpp_client_send_message(clientcfg->client, args.recipient, args.message); + + 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 xmpp_sendgroup_exec(struct ast_channel *chan, const char *data) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + char *s, nick[XMPP_MAX_RESJIDLEN]; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(sender); + AST_APP_ARG(groupchat); + AST_APP_ARG(message); + AST_APP_ARG(nick); + ); + + if (ast_strlen_zero(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_strlen_zero(args.message) || !strchr(args.groupchat, '@')) { + ast_log(LOG_ERROR, "%s requires arguments (sender,groupchatid,message[,nickname])\n", app_ajisendgroup); + return -1; + } + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, 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 (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT)) { + snprintf(nick, sizeof(nick), "asterisk"); + } else { + snprintf(nick, sizeof(nick), "%s", clientcfg->client->jid->user); + } + } else { + snprintf(nick, sizeof(nick), "%s", args.nick); + } + + ast_xmpp_chatroom_send(clientcfg->client, nick, args.groupchat, args.message); + + return 0; +} + +/*! + * \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) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + char *aux = NULL, *parse = NULL; + int timeout, jidlen, resourcelen, found = 0; + struct timeval start; + long diff = 0; + struct ast_xmpp_message *message; + 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; + } + + parse = ast_strdupa(args.jid); + AST_NONSTANDARD_APP_ARGS(jid, parse, '/'); + if (jid.argc < 1 || jid.argc > 2 || strlen(args.jid) > XMPP_MAX_JIDLEN) { + ast_log(LOG_WARNING, "Invalid JID : %s\n", parse); + 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); + return -1; + } + } + + jidlen = strlen(jid.screenname); + resourcelen = ast_strlen_zero(jid.resource) ? 0 : strlen(jid.resource); + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, args.account))) { + ast_log(LOG_WARNING, "Could not find client %s, exiting\n", args.account); + return -1; + } + + 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", ast_channel_name(chan)); + 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 = 0; + + 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); + if (AST_LIST_EMPTY(&clientcfg->client->messages)) { + 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(&clientcfg->client->messages); + AST_LIST_TRAVERSE_SAFE_BEGIN(&clientcfg->client->messages, message, list) { + if (jid.argc == 1) { + /* no resource provided, compare bare JIDs */ + if (strncasecmp(jid.screenname, message->from, jidlen)) { + continue; + } + } else { + /* resource appended, compare bare JIDs and resources */ + char *resource = strchr(message->from, '/'); + if (!resource || strlen(resource) == 0) { + ast_log(LOG_WARNING, "Remote JID has no resource : %s\n", message->from); + if (strncasecmp(jid.screenname, message->from, jidlen)) { + continue; + } + } else { + resource ++; + if (strncasecmp(jid.screenname, message->from, jidlen) || strncmp(jid.resource, resource, resourcelen)) { + continue; + } + } + } + /* check if the message is not too old */ + if (ast_tvdiff_sec(ast_tvnow(), message->arrived) >= clientcfg->message_timeout) { + ast_debug(3, "Found old message from %s, deleting it\n", message->from); + AST_LIST_REMOVE_CURRENT(list); + xmpp_message_destroy(message); + continue; + } + found = 1; + aux = ast_strdupa(message->message); + AST_LIST_REMOVE_CURRENT(list); + xmpp_message_destroy(message); + break; + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&clientcfg->client->messages); + if (found) { + break; + } + + /* check timeout */ + diff = ast_tvdiff_ms(ast_tvnow(), start); + } + + if (ast_autoservice_stop(chan) < 0) { + ast_log(LOG_WARNING, "Cannot stop autoservice for channel %s\n", ast_channel_name(chan)); + } + + /* 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 + */ +static int delete_old_messages(struct ast_xmpp_client *client, char *from) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + int deleted = 0, isold = 0; + struct ast_xmpp_message *message = NULL; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name))) { + return 0; + } + + AST_LIST_LOCK(&client->messages); + AST_LIST_TRAVERSE_SAFE_BEGIN(&client->messages, message, list) { + if (isold) { + if (!from || !strncasecmp(from, message->from, strlen(from))) { + AST_LIST_REMOVE_CURRENT(list); + xmpp_message_destroy(message); + deleted++; + } + } else if (ast_tvdiff_sec(ast_tvnow(), message->arrived) >= clientcfg->message_timeout) { + isold = 1; + if (!from || !strncasecmp(from, message->from, strlen(from))) { + AST_LIST_REMOVE_CURRENT(list); + xmpp_message_destroy(message); + deleted++; + } + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&client->messages); + + return deleted; +} + +static int xmpp_send_cb(const struct ast_msg *msg, const char *to, const char *from) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + char *sender, *dest; + int res; + + sender = ast_strdupa(from); + strsep(&sender, ":"); + dest = ast_strdupa(to); + strsep(&dest, ":"); + + if (ast_strlen_zero(sender)) { + ast_log(LOG_ERROR, "MESSAGE(from) of '%s' invalid for XMPP\n", from); + return -1; + } + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, sender))) { + ast_log(LOG_WARNING, "Could not finder account to send from as '%s'\n", sender); + return -1; + } + + ast_debug(1, "Sending message to '%s' from '%s'\n", dest, clientcfg->name); + + if ((res = ast_xmpp_client_send_message(clientcfg->client, dest, ast_msg_get_body(msg))) != IKS_OK) { + ast_log(LOG_WARNING, "Failed to send XMPP message (%d).\n", res); + } + + return res == IKS_OK ? 0 : -1; +} + +static const struct ast_msg_tech msg_tech = { + .name = "xmpp", + .msg_send = xmpp_send_cb, +}; + +/*! \brief Internal function which changes the XMPP client state */ +static void xmpp_client_change_state(struct ast_xmpp_client *client, int state) +{ + client->state = state; +} + +/*! \brief Internal function which creates a buddy on a client */ +static struct ast_xmpp_buddy *xmpp_client_create_buddy(struct ao2_container *container, const char *id) +{ + struct ast_xmpp_buddy *buddy; + + if (!(buddy = ao2_alloc(sizeof(*buddy), xmpp_buddy_destructor))) { + return NULL; + } + + if (!(buddy->resources = ao2_container_alloc(RESOURCE_BUCKETS, xmpp_resource_hash, xmpp_resource_cmp))) { + ao2_ref(buddy, -1); + return NULL; + } + + ast_copy_string(buddy->id, id, sizeof(buddy->id)); + + /* Assume we need to subscribe to get their presence until proven otherwise */ + buddy->subscribe = 1; + + ao2_link(container, buddy); + + return buddy; +} + +/*! \brief Helper function which unsubscribes a user and removes them from the roster */ +static int xmpp_client_unsubscribe_user(struct ast_xmpp_client *client, const char *user) +{ + iks *iq, *query = NULL, *item = NULL; + + if (ast_xmpp_client_send(client, iks_make_s10n(IKS_TYPE_UNSUBSCRIBE, user, + "Goodbye. Your status is no longer required.\n"))) { + return -1; + } + + if (!(iq = iks_new("iq")) || !(query = iks_new("query")) || !(item = iks_new("item"))) { + ast_log(LOG_WARNING, "Could not allocate memory for roster removal of '%s' from client '%s'\n", + user, client->name); + goto done; + } + + iks_insert_attrib(iq, "from", client->jid->full); + iks_insert_attrib(iq, "type", "set"); + iks_insert_attrib(query, "xmlns", "jabber:iq:roster"); + iks_insert_node(iq, query); + iks_insert_attrib(item, "jid", user); + iks_insert_attrib(item, "subscription", "remove"); + iks_insert_node(query, item); + + if (ast_xmpp_client_send(client, iq)) { + ast_log(LOG_WARNING, "Could not send roster removal request of '%s' from client '%s'\n", + user, client->name); + } + +done: + iks_delete(item); + iks_delete(query); + iks_delete(iq); + + return 0; +} + +/*! \brief Callback function which subscribes to a user if needed */ +static int xmpp_client_subscribe_user(void *obj, void *arg, int flags) +{ + struct ast_xmpp_buddy *buddy = obj; + struct ast_xmpp_client *client = arg; + + if (!buddy->subscribe) { + return 0; + } + + if (ast_xmpp_client_send(client, iks_make_s10n(IKS_TYPE_SUBSCRIBE, buddy->id, + "Greetings! I am the Asterisk Open Source PBX and I want to subscribe to your presence\n"))) { + ast_log(LOG_WARNING, "Could not send subscription for '%s' on client '%s'\n", + buddy->id, client->name); + } + + buddy->subscribe = 0; + + return 0; +} + +/*! \brief Hook function called when roster is received from server */ +static int xmpp_roster_hook(void *data, ikspak *pak) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_client *client = data; + iks *item; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name))) { + return IKS_FILTER_EAT; + } + + for (item = iks_child(pak->query); item; item = iks_next(item)) { + struct ast_xmpp_buddy *buddy; + + if (iks_strcmp(iks_name(item), "item")) { + continue; + } + + if (!(buddy = ao2_find(client->buddies, iks_find_attrib(item, "jid"), OBJ_KEY))) { + if (ast_test_flag(&clientcfg->flags, XMPP_AUTOPRUNE)) { + /* The buddy has not been specified in the configuration file, we no longer + * want them on our buddy list or to receive their presence. */ + if (xmpp_client_unsubscribe_user(client, iks_find_attrib(item, "jid"))) { + ast_log(LOG_ERROR, "Could not unsubscribe user '%s' on client '%s'\n", + iks_find_attrib(item, "jid"), client->name); + } + continue; + } + + if (!(buddy = xmpp_client_create_buddy(client->buddies, iks_find_attrib(item, "jid")))) { + ast_log(LOG_ERROR, "Could not allocate buddy '%s' on client '%s'\n", iks_find_attrib(item, "jid"), + client->name); + continue; + } + } + + /* Determine if we need to subscribe to their presence or not */ + if (!iks_strcmp(iks_find_attrib(item, "subscription"), "none") || + !iks_strcmp(iks_find_attrib(item, "subscription"), "from")) { + buddy->subscribe = 1; + } else { + buddy->subscribe = 0; + } + + ao2_ref(buddy, -1); + } + + /* If autoregister is enabled we need to go through every buddy that we need to subscribe to and do so */ + if (ast_test_flag(&clientcfg->flags, XMPP_AUTOREGISTER)) { + ao2_callback(client->buddies, OBJ_NODATA | OBJ_MULTIPLE, xmpp_client_subscribe_user, client); + } + + xmpp_client_change_state(client, XMPP_STATE_CONNECTED); + + return IKS_FILTER_EAT; +} + +/*! \brief Internal function which changes the presence status of an XMPP client */ +static void xmpp_client_set_presence(struct ast_xmpp_client *client, const char *to, const char *from, int level, const char *desc) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + iks *presence = NULL, *cnode = NULL, *priority = NULL; + char priorityS[10]; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || + !(presence = iks_make_pres(level, desc)) || !(cnode = iks_new("c")) || !(priority = iks_new("priority"))) { + ast_log(LOG_ERROR, "Unable to allocate stanzas for setting presence status for client '%s'\n", client->name); + goto done; + } + + if (!ast_strlen_zero(to)) { + iks_insert_attrib(presence, "to", to); + } + + if (!ast_strlen_zero(from)) { + iks_insert_attrib(presence, "from", from); + } + + snprintf(priorityS, sizeof(priorityS), "%d", clientcfg->priority); + iks_insert_cdata(priority, priorityS, strlen(priorityS)); + iks_insert_node(presence, priority); + iks_insert_attrib(cnode, "node", "http://www.asterisk.org/xmpp/client/caps"); + iks_insert_attrib(cnode, "ver", "asterisk-xmpp"); + iks_insert_attrib(cnode, "ext", "voice-v1"); + iks_insert_attrib(cnode, "xmlns", "http://jabber.org/protocol/caps"); + iks_insert_node(presence, cnode); + ast_xmpp_client_send(client, presence); + +done: + iks_delete(cnode); + iks_delete(presence); + iks_delete(priority); +} + +/*! \brief Hook function called when client receives a service discovery get message */ +static int xmpp_client_service_discovery_get_hook(void *data, ikspak *pak) +{ + struct ast_xmpp_client *client = data; + iks *iq, *disco = NULL, *ident = NULL, *google = NULL, *jingle = NULL, *ice = NULL, *rtp = NULL, *audio = NULL, *video = NULL, *query = NULL; + + if (!(iq = iks_new("iq")) || !(query = iks_new("query")) || !(ident = iks_new("identity")) || !(disco = iks_new("feature")) || + !(google = iks_new("feature")) || !(jingle = iks_new("feature")) || !(ice = iks_new("feature")) || !(rtp = iks_new("feature")) || + !(audio = iks_new("feature")) || !(video = iks_new("feature"))) { + ast_log(LOG_ERROR, "Could not allocate memory for responding to service discovery request from '%s' on client '%s'\n", + pak->from->full, client->name); + goto end; + } + + iks_insert_attrib(iq, "from", client->jid->full); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_attrib(ident, "category", "client"); + iks_insert_attrib(ident, "type", "pc"); + iks_insert_attrib(ident, "name", "asterisk"); + iks_insert_attrib(disco, "var", "http://jabber.org/protocol/disco#info"); + + iks_insert_attrib(google, "var", "http://www.google.com/xmpp/protocol/voice/v1"); + iks_insert_attrib(jingle, "var", "urn:xmpp:jingle:1"); + iks_insert_attrib(ice, "var", "urn:xmpp:jingle:transports:ice-udp:1"); + iks_insert_attrib(rtp, "var", "urn:xmpp:jingle:apps:rtp:1"); + iks_insert_attrib(audio, "var", "urn:xmpp:jingle:apps:rtp:audio"); + iks_insert_attrib(video, "var", "urn:xmpp:jingle:apps:rtp:video"); + iks_insert_node(iq, query); + iks_insert_node(query, ident); + iks_insert_node(query, google); + iks_insert_node(query, disco); + iks_insert_node(query, jingle); + iks_insert_node(query, ice); + iks_insert_node(query, rtp); + iks_insert_node(query, audio); + iks_insert_node(query, video); + ast_xmpp_client_send(client, iq); + +end: + iks_delete(query); + iks_delete(video); + iks_delete(audio); + iks_delete(rtp); + iks_delete(ice); + iks_delete(jingle); + iks_delete(google); + iks_delete(ident); + iks_delete(disco); + iks_delete(iq); + + return IKS_FILTER_EAT; +} + +/*! \brief Hook function called when client receives a service discovery result message */ +static int xmpp_client_service_discovery_result_hook(void *data, ikspak *pak) +{ + struct ast_xmpp_client *client = data; + struct ast_xmpp_buddy *buddy; + struct ast_xmpp_resource *resource; + + if (!(buddy = ao2_find(client->buddies, pak->from->partial, OBJ_KEY))) { + return IKS_FILTER_EAT; + } + + if (!(resource = ao2_find(buddy->resources, pak->from->resource, OBJ_KEY))) { + ao2_ref(buddy, -1); + return IKS_FILTER_EAT; + } + + ao2_lock(resource); + + if (iks_find_with_attrib(pak->query, "feature", "var", "urn:xmpp:jingle:1")) { + resource->caps.jingle = 1; + } + + ao2_unlock(resource); + + ao2_ref(resource, -1); + ao2_ref(buddy, -1); + + return IKS_FILTER_EAT; +} + +/*! \brief Hook function called when client finishes authenticating with the server */ +static int xmpp_connect_hook(void *data, ikspak *pak) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_client *client = data; + iks *roster; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name))) { + return -1; + } + + client->jid = (iks_find_cdata(pak->query, "jid")) ? iks_id_new(client->stack, iks_find_cdata(pak->query, "jid")) : client->jid; + + if (ast_test_flag(&clientcfg->flags, XMPP_DISTRIBUTE_EVENTS)) { + xmpp_init_event_distribution(client); + } + + if (!(roster = iks_make_iq(IKS_TYPE_GET, IKS_NS_ROSTER))) { + ast_log(LOG_ERROR, "Unable to allocate memory for roster request for client '%s'\n", client->name); + return -1; + } + + iks_filter_add_rule(client->filter, xmpp_client_service_discovery_get_hook, client, IKS_RULE_SUBTYPE, IKS_TYPE_GET, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE); + iks_filter_add_rule(client->filter, xmpp_client_service_discovery_result_hook, client, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE); + + iks_insert_attrib(roster, "id", "roster"); + ast_xmpp_client_send(client, roster); + + iks_filter_remove_hook(client->filter, xmpp_connect_hook); + iks_filter_add_rule(client->filter, xmpp_roster_hook, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, "roster", IKS_RULE_DONE); + + xmpp_client_set_presence(client, NULL, client->jid->full, clientcfg->status, clientcfg->statusmsg); + xmpp_client_change_state(client, XMPP_STATE_ROSTER); + + return IKS_FILTER_EAT; +} + +/*! \brief Logging hook function */ +static void xmpp_log_hook(void *data, const char *xmpp, size_t size, int incoming) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_client *client = data; + + if (!ast_strlen_zero(xmpp)) { + manager_event(EVENT_FLAG_USER, "JabberEvent", "Account: %s\r\nPacket: %s\r\n", client->name, xmpp); + } + + if (!debug && (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || !ast_test_flag(&clientcfg->flags, XMPP_DEBUG))) { + return; + } + + if (!incoming) { + if (strlen(xmpp) == 1) { + if (option_debug > 2 && xmpp[0] == ' ') { + ast_verbose("\n<--- XMPP keep alive from '%s' --->\n", client->name); + } + } else { + ast_verbose("\n<--- XMPP sent to '%s' --->\n%s\n<------------->\n", client->name, xmpp); + } + } else { + ast_verbose("\n<--- XMPP received from '%s' --->\n%s\n<------------->\n", client->name, xmpp); + } +} + +/*! \brief Internal function which sends a raw message */ +static int xmpp_client_send_raw_message(struct ast_xmpp_client *client, const char *message) +{ + int ret; +#ifdef HAVE_OPENSSL + int len = strlen(message); + + if (xmpp_is_secure(client)) { + ret = SSL_write(client->ssl_session, message, len); + if (ret) { + /* Log the message here, because iksemel's logHook is + unaccessible */ + xmpp_log_hook(client, message, len, 0); + return IKS_OK; + } + } +#endif + /* If needed, data will be sent unencrypted, and logHook will + be called inside iks_send_raw */ + ret = iks_send_raw(client->parser, message); + if (ret != IKS_OK) { + return ret; + } + + return IKS_OK; +} + +/*! \brief Helper function which sends an XMPP stream header to the server */ +static int xmpp_send_stream_header(struct ast_xmpp_client *client, const struct ast_xmpp_client_config *cfg, const char *to) +{ + char *namespace = ast_test_flag(&cfg->flags, XMPP_COMPONENT) ? "jabber:component:accept" : "jabber:client"; + char msg[91 + strlen(namespace) + 6 + strlen(to) + 16 + 1]; + + snprintf(msg, sizeof(msg), "" + "", namespace, to); + + return xmpp_client_send_raw_message(client, msg); +} + +int ast_xmpp_client_send(struct ast_xmpp_client *client, iks *stanza) +{ + return xmpp_client_send_raw_message(client, iks_string(iks_stack(stanza), stanza)); +} + +/*! \brief Internal function called when we need to request TLS support */ +static int xmpp_client_request_tls(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ + /* If the client connection is already secure we can jump straight to authenticating */ + if (xmpp_is_secure(client)) { + xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATE); + return 0; + } + +#ifndef HAVE_OPENSSL + ast_log(LOG_ERROR, "TLS connection for client '%s' cannot be established. OpenSSL is not available.\n", client->name); + return -1; +#else + if (iks_send_raw(client->parser, "") == IKS_NET_TLSFAIL) { + ast_log(LOG_ERROR, "TLS connection for client '%s' cannot be started.\n", client->name); + return -1; + } + + client->stream_flags |= TRY_SECURE; + + xmpp_client_change_state(client, XMPP_STATE_REQUESTED_TLS); + + return 0; +#endif +} + +/*! \brief Internal function called when we receive a response to our TLS initiation request */ +static int xmpp_client_requested_tls(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ +#ifdef HAVE_OPENSSL + int sock; +#endif + + if (!strcmp(iks_name(node), "success")) { + /* TLS is up and working, we can move on to authenticating now */ + xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATE); + return 0; + } else if (!strcmp(iks_name(node), "failure")) { + /* TLS negotiation was a failure, close it on down! */ + return -1; + } else if (strcmp(iks_name(node), "proceed")) { + /* Ignore any other responses */ + return 0; + } + +#ifndef HAVE_OPENSSL + ast_log(LOG_ERROR, "Somehow we managed to try to start TLS negotiation on client '%s' without OpenSSL support, disconnecting\n", client->name); + return -1; +#else + client->ssl_method = SSLv3_method(); + if (!(client->ssl_context = SSL_CTX_new((SSL_METHOD *) client->ssl_method))) { + goto failure; + } + + if (!(client->ssl_session = SSL_new(client->ssl_context))) { + goto failure; + } + + sock = iks_fd(client->parser); + if (!SSL_set_fd(client->ssl_session, sock)) { + goto failure; + } + + if (!SSL_connect(client->ssl_session)) { + goto failure; + } + + client->stream_flags &= (~TRY_SECURE); + client->stream_flags |= SECURE; + + if (xmpp_send_stream_header(client, cfg, client->jid->server) != IKS_OK) { + ast_log(LOG_ERROR, "TLS connection for client '%s' could not be established, failed to send stream header after negotiation\n", + client->name); + return -1; + } + + ast_debug(1, "TLS connection for client '%s' started with server\n", client->name); + + xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATE); + + return 0; + +failure: + ast_log(LOG_ERROR, "TLS connection for client '%s' cannot be established. OpenSSL initialization failed.\n", client->name); + return -1; +#endif +} + +/*! \brief Internal function called when we need to authenticate using non-SASL */ +static int xmpp_client_authenticate_digest(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ + iks *iq = NULL, *query = NULL; + char buf[41], sidpass[100]; + + if (!(iq = iks_new("iq")) || !(query = iks_insert(iq, "query"))) { + ast_log(LOG_ERROR, "Stanzas could not be allocated for authentication on client '%s'\n", client->name); + iks_delete(iq); + return -1; + } + + iks_insert_attrib(iq, "type", "set"); + iks_insert_cdata(iks_insert(query, "username"), client->jid->user, 0); + iks_insert_cdata(iks_insert(query, "resource"), client->jid->resource, 0); + + snprintf(sidpass, sizeof(sidpass), "%s%s", iks_find_attrib(node, "id"), cfg->password); + ast_sha1_hash(buf, sidpass); + iks_insert_cdata(iks_insert(query, "digest"), buf, 0); + + ast_xmpp_client_lock(client); + iks_filter_add_rule(client->filter, xmpp_connect_hook, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, client->mid, IKS_RULE_DONE); + iks_insert_attrib(iq, "id", client->mid); + ast_xmpp_increment_mid(client->mid); + ast_xmpp_client_unlock(client); + + iks_insert_attrib(iq, "to", client->jid->server); + + ast_xmpp_client_send(client, iq); + + iks_delete(iq); + + xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATING); + + return 0; +} + +/*! \brief Internal function called when we need to authenticate using SASL */ +static int xmpp_client_authenticate_sasl(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ + int features, len = strlen(cfg->user) + strlen(cfg->password) + 3; + iks *auth; + char combined[len]; + char base64[(len + 2) * 4 / 3]; + + if (strcmp(iks_name(node), "stream:features")) { + /* Ignore anything beside stream features */ + return 0; + } + + features = iks_stream_features(node); + + if ((features & IKS_STREAM_SASL_MD5) && !xmpp_is_secure(client)) { + if (iks_start_sasl(client->parser, IKS_SASL_DIGEST_MD5, (char*)cfg->user, (char*)cfg->password) != IKS_OK) { + ast_log(LOG_ERROR, "Tried to authenticate client '%s' using SASL DIGEST-MD5 but could not\n", client->name); + return -1; + } + + xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATING); + return 0; + } + + /* Our only other available option is plain so if they don't support it, bail out now */ + if (!(features & IKS_STREAM_SASL_PLAIN)) { + ast_log(LOG_ERROR, "Tried to authenticate client '%s' using SASL PLAIN but server does not support it\n", client->name); + return -1; + } + + if (!(auth = iks_new("auth"))) { + ast_log(LOG_ERROR, "Could not allocate memory for SASL PLAIN authentication for client '%s'\n", client->name); + return -1; + } + + iks_insert_attrib(auth, "xmlns", IKS_NS_XMPP_SASL); + iks_insert_attrib(auth, "mechanism", "PLAIN"); + + snprintf(combined, sizeof(combined), "%c%s%c%s", 0, cfg->user, 0, cfg->password); + ast_base64encode(base64, (const unsigned char *) combined, len - 1, (len + 2) * 4 / 3); + iks_insert_cdata(auth, base64, 0); + + ast_xmpp_client_send(client, auth); + + iks_delete(auth); + + xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATING); + + return 0; +} + +/*! \brief Internal function called when we need to authenticate */ +static int xmpp_client_authenticate(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ + return ast_test_flag(&cfg->flags, XMPP_USESASL) ? xmpp_client_authenticate_sasl(client, cfg, type, node) : xmpp_client_authenticate_digest(client, cfg, type, node); +} + +/*! \brief Internal function called when we are authenticating */ +static int xmpp_client_authenticating(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ + int features; + + if (!strcmp(iks_name(node), "success")) { + /* Authentication was a success, yay! */ + xmpp_send_stream_header(client, cfg, client->jid->server); + + return 0; + } else if (!strcmp(iks_name(node), "failure")) { + /* Authentication was a bust, disconnect and reconnect later */ + return -1; + } else if (strcmp(iks_name(node), "stream:features")) { + /* Ignore any other responses */ + return 0; + } + + features = iks_stream_features(node); + + if (features & IKS_STREAM_BIND) { + iks *auth; + + if (!(auth = iks_make_resource_bind(client->jid))) { + ast_log(LOG_ERROR, "Failed to allocate memory for stream bind on client '%s'\n", client->name); + return -1; + } + + ast_xmpp_client_lock(client); + iks_insert_attrib(auth, "id", client->mid); + ast_xmpp_increment_mid(client->mid); + ast_xmpp_client_unlock(client); + ast_xmpp_client_send(client, auth); + + iks_delete(auth); + + iks_filter_add_rule(client->filter, xmpp_connect_hook, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_DONE); + } + + if (features & IKS_STREAM_SESSION) { + iks *auth; + + if (!(auth = iks_make_session())) { + ast_log(LOG_ERROR, "Failed to allocate memory for stream session on client '%s'\n", client->name); + return -1; + } + + iks_insert_attrib(auth, "id", "auth"); + ast_xmpp_client_lock(client); + ast_xmpp_increment_mid(client->mid); + ast_xmpp_client_unlock(client); + ast_xmpp_client_send(client, auth); + + iks_delete(auth); + + iks_filter_add_rule(client->filter, xmpp_connect_hook, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, "auth", IKS_RULE_DONE); + } + + return 0; +} + +/*! \brief Internal function called when we should authenticate as a component */ +static int xmpp_component_authenticate(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ + char secret[160], shasum[320], message[344]; + ikspak *pak = iks_packet(node); + + snprintf(secret, sizeof(secret), "%s%s", pak->id, cfg->password); + ast_sha1_hash(shasum, secret); + snprintf(message, sizeof(message), "%s", shasum); + + if (xmpp_client_send_raw_message(client, message) != IKS_OK) { + ast_log(LOG_ERROR, "Unable to send handshake for component '%s'\n", client->name); + return -1; + } + + xmpp_client_change_state(client, XMPP_STATE_AUTHENTICATING); + + return 0; +} + +/*! \brief Hook function called when component receives a service discovery get message */ +static int xmpp_component_service_discovery_get_hook(void *data, ikspak *pak) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_client *client = data; + iks *iq = NULL, *query = NULL, *identity = NULL, *disco = NULL, *reg = NULL, *commands = NULL, *gateway = NULL; + iks *version = NULL, *vcard = NULL, *search = NULL, *item = NULL; + char *node; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || + !(iq = iks_new("iq")) || !(query = iks_new("query")) || !(identity = iks_new("identity")) || !(disco = iks_new("feature")) || + !(reg = iks_new("feature")) || !(commands = iks_new("feature")) || !(gateway = iks_new("feature")) || !(version = iks_new("feature")) || + !(vcard = iks_new("feature")) || !(search = iks_new("search")) || !(item = iks_new("item"))) { + ast_log(LOG_ERROR, "Failed to allocate stanzas for service discovery get response to '%s' on component '%s'\n", + pak->from->partial, client->name); + goto done; + } + + iks_insert_attrib(iq, "from", clientcfg->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + + if (!(node = iks_find_attrib(pak->query, "node"))) { + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_attrib(identity, "category", "gateway"); + iks_insert_attrib(identity, "type", "pstn"); + iks_insert_attrib(identity, "name", "Asterisk The Open Source PBX"); + iks_insert_attrib(disco, "var", "http://jabber.org/protocol/disco"); + iks_insert_attrib(reg, "var", "jabber:iq:register"); + iks_insert_attrib(commands, "var", "http://jabber.org/protocol/commands"); + iks_insert_attrib(gateway, "var", "jabber:iq:gateway"); + iks_insert_attrib(version, "var", "jabber:iq:version"); + iks_insert_attrib(vcard, "var", "vcard-temp"); + iks_insert_attrib(search, "var", "jabber:iq:search"); + + iks_insert_node(iq, query); + iks_insert_node(query, identity); + iks_insert_node(query, disco); + iks_insert_node(query, reg); + iks_insert_node(query, commands); + iks_insert_node(query, gateway); + iks_insert_node(query, version); + iks_insert_node(query, vcard); + iks_insert_node(query, search); + } else if (!strcasecmp(node, "http://jabber.org/protocol/commands")) { + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items"); + iks_insert_attrib(query, "node", "http://jabber.org/protocol/commands"); + iks_insert_attrib(item, "node", "confirmaccount"); + iks_insert_attrib(item, "name", "Confirm account"); + iks_insert_attrib(item, "jid", clientcfg->user); + + iks_insert_node(iq, query); + iks_insert_node(query, item); + } else if (!strcasecmp(node, "confirmaccount")) { + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_attrib(commands, "var", "http://jabber.org/protocol/commands"); + + iks_insert_node(iq, query); + iks_insert_node(query, commands); + } else { + ast_debug(3, "Unsupported service discovery info request received with node '%s' on component '%s'\n", + node, client->name); + goto done; + } + + if (ast_xmpp_client_send(client, iq)) { + ast_log(LOG_WARNING, "Could not send response to service discovery request on component '%s'\n", + client->name); + } + +done: + iks_delete(search); + iks_delete(vcard); + iks_delete(version); + iks_delete(gateway); + iks_delete(commands); + iks_delete(reg); + iks_delete(disco); + iks_delete(identity); + iks_delete(query); + iks_delete(iq); + + return IKS_FILTER_EAT; +} + +/*! \brief Hook function called when the component is queried about registration */ +static int xmpp_component_register_get_hook(void *data, ikspak *pak) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_client *client = data; + iks *iq = NULL, *query = NULL, *error = NULL, *notacceptable = NULL, *instructions = NULL; + struct ast_xmpp_buddy *buddy; + char *node; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || + !(iq = iks_new("iq")) || !(query = iks_new("query")) || !(error = iks_new("error")) || !(notacceptable = iks_new("not-acceptable")) || + !(instructions = iks_new("instructions"))) { + ast_log(LOG_ERROR, "Failed to allocate stanzas for register get response to '%s' on component '%s'\n", + pak->from->partial, client->name); + goto done; + } + + iks_insert_attrib(iq, "from", clientcfg->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "jabber:iq:register"); + iks_insert_node(iq, query); + + if (!(buddy = ao2_find(client->buddies, pak->from->partial, OBJ_KEY))) { + iks_insert_attrib(error, "code", "406"); + iks_insert_attrib(error, "type", "modify"); + iks_insert_attrib(notacceptable, "xmlns", "urn:ietf:params:xml:ns:xmpp-stanzas"); + + iks_insert_node(iq, error); + iks_insert_node(error, notacceptable); + + ast_log(LOG_ERROR, "Received register attempt from '%s' but buddy is not configured on component '%s'\n", + pak->from->partial, client->name); + } else if (!(node = iks_find_attrib(pak->query, "node"))) { + iks_insert_cdata(instructions, "Welcome to Asterisk - the Open Source PBX.\n", 0); + iks_insert_node(query, instructions); + ao2_ref(buddy, -1); + } else { + ast_log(LOG_WARNING, "Received register get to component '%s' using unsupported node '%s' from '%s'\n", + client->name, node, pak->from->partial); + ao2_ref(buddy, -1); + goto done; + } + + if (ast_xmpp_client_send(client, iq)) { + ast_log(LOG_WARNING, "Could not send response to '%s' for received register get on component '%s'\n", + pak->from->partial, client->name); + } + +done: + iks_delete(instructions); + iks_delete(notacceptable); + iks_delete(error); + iks_delete(query); + iks_delete(iq); + + return IKS_FILTER_EAT; +} + +/*! \brief Hook function called when someone registers to the component */ +static int xmpp_component_register_set_hook(void *data, ikspak *pak) +{ + struct ast_xmpp_client *client = data; + iks *iq, *presence = NULL, *x = NULL; + + if (!(iq = iks_new("iq")) || !(presence = iks_new("presence")) || !(x = iks_new("x"))) { + ast_log(LOG_ERROR, "Failed to allocate stanzas for register set response to '%s' on component '%s'\n", + pak->from->partial, client->name); + goto done; + } + + iks_insert_attrib(iq, "from", client->jid->full); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + + if (ast_xmpp_client_send(client, iq)) { + ast_log(LOG_WARNING, "Could not send response to '%s' for received register set on component '%s'\n", + pak->from->partial, client->name); + goto done; + } + + iks_insert_attrib(presence, "from", client->jid->full); + iks_insert_attrib(presence, "to", pak->from->partial); + ast_xmpp_client_lock(client); + iks_insert_attrib(presence, "id", client->mid); + ast_xmpp_increment_mid(client->mid); + ast_xmpp_client_unlock(client); + iks_insert_attrib(presence, "type", "subscribe"); + iks_insert_attrib(x, "xmlns", "vcard-temp:x:update"); + + iks_insert_node(presence, x); + + if (ast_xmpp_client_send(client, presence)) { + ast_log(LOG_WARNING, "Could not send subscription to '%s' on component '%s'\n", + pak->from->partial, client->name); + } + +done: + iks_delete(x); + iks_delete(presence); + iks_delete(iq); + + return IKS_FILTER_EAT; +} + +/*! \brief Hook function called when we receive a service discovery items request */ +static int xmpp_component_service_discovery_items_hook(void *data, ikspak *pak) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_client *client = data; + iks *iq = NULL, *query = NULL, *item = NULL, *feature = NULL; + char *node; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name)) || + !(iq = iks_new("iq")) || !(query = iks_new("query")) || !(item = iks_new("item")) || !(feature = iks_new("feature"))) { + ast_log(LOG_ERROR, "Failed to allocate stanzas for service discovery items response to '%s' on component '%s'\n", + pak->from->partial, client->name); + goto done; + } + + iks_insert_attrib(iq, "from", clientcfg->user); + iks_insert_attrib(iq, "to", pak->from->full); + iks_insert_attrib(iq, "id", pak->id); + iks_insert_attrib(iq, "type", "result"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items"); + iks_insert_node(iq, query); + + if (!(node = iks_find_attrib(pak->query, "node"))) { + iks_insert_attrib(item, "node", "http://jabber.org/protocol/commands"); + iks_insert_attrib(item, "name", "Asterisk Commands"); + iks_insert_attrib(item, "jid", clientcfg->user); + + iks_insert_node(query, item); + } else if (!strcasecmp(node, "http://jabber.org/protocol/commands")) { + iks_insert_attrib(query, "node", "http://jabber.org/protocol/commands"); + } else { + ast_log(LOG_WARNING, "Received service discovery items request to component '%s' using unsupported node '%s' from '%s'\n", + client->name, node, pak->from->partial); + goto done; + } + + if (ast_xmpp_client_send(client, iq)) { + ast_log(LOG_WARNING, "Could not send response to service discovery items request from '%s' on component '%s'\n", + pak->from->partial, client->name); + } + +done: + iks_delete(feature); + iks_delete(item); + iks_delete(query); + iks_delete(iq); + + return IKS_FILTER_EAT; +} + +/*! \brief Internal function called when we authenticated as a component */ +static int xmpp_component_authenticating(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node) +{ + if (strcmp(iks_name(node), "handshake")) { + ast_log(LOG_ERROR, "Failed to authenticate component '%s'\n", client->name); + return -1; + } + + iks_filter_add_rule(client->filter, xmpp_component_service_discovery_items_hook, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#items", IKS_RULE_DONE); + + iks_filter_add_rule(client->filter, xmpp_component_service_discovery_get_hook, client, IKS_RULE_SUBTYPE, IKS_TYPE_GET, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE); + + /* This uses the client service discovery result hook on purpose, as the code is common between both */ + iks_filter_add_rule(client->filter, xmpp_client_service_discovery_result_hook, client, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE); + + iks_filter_add_rule(client->filter, xmpp_component_register_get_hook, client, IKS_RULE_SUBTYPE, IKS_TYPE_GET, IKS_RULE_NS, "jabber:iq:register", IKS_RULE_DONE); + iks_filter_add_rule(client->filter, xmpp_component_register_set_hook, client, IKS_RULE_SUBTYPE, IKS_TYPE_SET, IKS_RULE_NS, "jabber:iq:register", IKS_RULE_DONE); + + xmpp_client_change_state(client, XMPP_STATE_CONNECTED); + + return 0; +} + +/*! \brief Internal function called when a message is received */ +static int xmpp_pak_message(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, iks *node, ikspak *pak) +{ + struct ast_xmpp_message *message; + int deleted = 0; + + ast_debug(3, "XMPP client '%s' received a message\n", client->name); + + if (!(message = ast_calloc(1, sizeof(*message)))) { + return -1; + } + + message->arrived = ast_tvnow(); + + if (iks_find_cdata(pak->x, "body")) { + message->message = ast_strdup(iks_find_cdata(pak->x, "body")); + } + + ast_copy_string(message->id, S_OR(pak->id, ""), sizeof(message->id)); + message->from = !ast_strlen_zero(pak->from->full) ? ast_strdup(pak->from->full) : NULL; + + if (ast_test_flag(&cfg->flags, XMPP_SEND_TO_DIALPLAN)) { + struct ast_msg *msg; + + if ((msg = ast_msg_alloc())) { + int res; + + ast_xmpp_client_lock(client); + + res = ast_msg_set_to(msg, "xmpp:%s", cfg->user); + res |= ast_msg_set_from(msg, "xmpp:%s", message->from); + res |= ast_msg_set_body(msg, "%s", message->message); + res |= ast_msg_set_context(msg, "%s", cfg->context); + + ast_xmpp_client_unlock(client); + + if (res) { + ast_msg_destroy(msg); + } else { + ast_msg_queue(msg); + } + } + } + + /* 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, message, list); + AST_LIST_UNLOCK(&client->messages); + + /* wake up threads waiting for messages */ + ast_mutex_lock(&messagelock); + ast_cond_broadcast(&message_received_condition); + ast_mutex_unlock(&messagelock); + + return 0; +} + +/*! \brief Helper function which sends a discovery information request to a user */ +static int xmpp_client_send_disco_info_request(struct ast_xmpp_client *client, const char *to, const char *from) +{ + iks *iq, *query; + int res; + + if (!(iq = iks_new("iq")) || !(query = iks_new("query"))) { + iks_delete(iq); + return -1; + } + + iks_insert_attrib(iq, "type", "get"); + iks_insert_attrib(iq, "to", to); + iks_insert_attrib(iq, "from", from); + ast_xmpp_client_lock(client); + iks_insert_attrib(iq, "id", client->mid); + ast_xmpp_increment_mid(client->mid); + ast_xmpp_client_unlock(client); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info"); + iks_insert_node(iq, query); + + res = ast_xmpp_client_send(client, iq); + + iks_delete(query); + iks_delete(iq); + + return res; +} + +/*! \brief Internal function called when a presence message is received */ +static int xmpp_pak_presence(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, iks *node, ikspak *pak) +{ + struct ast_xmpp_buddy *buddy; + struct ast_xmpp_resource *resource; + char *type = iks_find_attrib(pak->x, "type"); + int status = pak->show ? pak->show : STATUS_DISAPPEAR; + + /* If no resource is available this is a general buddy presence update, which we will ignore */ + if (!pak->from->resource) { + return 0; + } + + if (!(buddy = ao2_find(client->buddies, pak->from->partial, OBJ_KEY))) { + ast_log(LOG_WARNING, "Received presence information about '%s' despite not having them in roster on client '%s'\n", + pak->from->partial, client->name); + return 0; + } + + /* If this is a component presence probe request answer immediately with our presence status */ + if (ast_test_flag(&cfg->flags, XMPP_COMPONENT) && !ast_strlen_zero(type) && !strcasecmp(type, "probe")) { + xmpp_client_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), cfg->status, cfg->statusmsg); + } + + ao2_lock(buddy->resources); + + if (!(resource = ao2_find(buddy->resources, pak->from->resource, OBJ_KEY | OBJ_NOLOCK))) { + /* Only create the new resource if it is not going away - in reality this should not happen */ + if (status != STATUS_DISAPPEAR) { + if (!(resource = ao2_alloc(sizeof(*resource), xmpp_resource_destructor))) { + ast_log(LOG_ERROR, "Could not allocate resource object for resource '%s' of buddy '%s' on client '%s'\n", + pak->from->resource, buddy->id, client->name); + ao2_unlock(buddy->resources); + ao2_ref(buddy, -1); + return 0; + } + + ast_copy_string(resource->resource, pak->from->resource, sizeof(resource->resource)); + } + } else { + /* We unlink the resource in case the priority changes or in case they are going away */ + ao2_unlink_flags(buddy->resources, resource, OBJ_NOLOCK); + } + + /* Only update the resource and add it back in if it is not going away */ + if (resource && (status != STATUS_DISAPPEAR)) { + char *node, *ver; + + /* Try to get the XMPP spec node, and fall back to Google if not found */ + if (!(node = iks_find_attrib(iks_find(pak->x, "c"), "node"))) { + node = iks_find_attrib(iks_find(pak->x, "caps:c"), "node"); + } + + if (!(ver = iks_find_attrib(iks_find(pak->x, "c"), "ver"))) { + ver = iks_find_attrib(iks_find(pak->x, "caps:c"), "ver"); + } + + if (resource->description) { + ast_free(resource->description); + } + + if ((node && strcmp(resource->caps.node, node)) || (ver && strcmp(resource->caps.version, ver))) { + ast_copy_string(resource->caps.node, node, sizeof(resource->caps.node)); + ast_copy_string(resource->caps.version, ver, sizeof(resource->caps.version)); + + /* Google Talk places the capabilities information directly in presence, so see if it is there */ + if (iks_find_with_attrib(pak->x, "c", "node", "http://www.google.com/xmpp/client/caps") || + iks_find_with_attrib(pak->x, "caps:c", "node", "http://www.google.com/xmpp/client/caps") || + iks_find_with_attrib(pak->x, "c", "node", "http://www.android.com/gtalk/client/caps") || + iks_find_with_attrib(pak->x, "caps:c", "node", "http://www.android.com/gtalk/client/caps")) { + resource->caps.google = 1; + } + + /* To discover if the buddy supports Jingle we need to query, so do so */ + if (xmpp_client_send_disco_info_request(client, pak->from->full, client->jid->full)) { + ast_log(LOG_WARNING, "Could not send discovery information request to resource '%s' of buddy '%s' on client '%s', capabilities may be incomplete\n", resource->resource, buddy->id, client->name); + } + } + + resource->status = status; + resource->description = ast_strdup(iks_find_cdata(pak->x, "status")); + resource->priority = atoi((iks_find_cdata(pak->x, "priority")) ? iks_find_cdata(pak->x, "priority") : "0"); + + ao2_link_flags(buddy->resources, resource, OBJ_NOLOCK); + + manager_event(EVENT_FLAG_USER, "JabberStatus", + "Account: %s\r\nJID: %s\r\nResource: %s\r\nStatus: %d\r\nPriority: %d" + "\r\nDescription: %s\r\n", + client->name, pak->from->partial, resource->resource, resource->status, + resource->priority, S_OR(resource->description, "")); + + ao2_ref(resource, -1); + } else { + /* This will get hit by presence coming in for an unknown resource, and also when a resource goes away */ + if (resource) { + ao2_ref(resource, -1); + } + + manager_event(EVENT_FLAG_USER, "JabberStatus", + "Account: %s\r\nJID: %s\r\nStatus: %d\r\n", + client->name, pak->from->partial, pak->show ? pak->show : IKS_SHOW_UNAVAILABLE); + } + + ao2_unlock(buddy->resources); + + ao2_ref(buddy, -1); + + return 0; +} + +/*! \brief Internal function called when a subscription message is received */ +static int xmpp_pak_s10n(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg,iks *node, ikspak *pak) +{ + struct ast_xmpp_buddy *buddy; + + switch (pak->subtype) { + case IKS_TYPE_SUBSCRIBE: + if (ast_test_flag(&cfg->flags, XMPP_AUTOREGISTER)) { + iks *presence, *status = NULL; + + if ((presence = iks_new("presence")) && (status = iks_new("status"))) { + iks_insert_attrib(presence, "type", "subscribed"); + iks_insert_attrib(presence, "to", pak->from->full); + iks_insert_attrib(presence, "from", client->jid->full); + + if (pak->id) { + iks_insert_attrib(presence, "id", pak->id); + } + + iks_insert_cdata(status, "Asterisk has approved your subscription", 0); + iks_insert_node(presence, status); + + if (ast_xmpp_client_send(client, presence)) { + ast_log(LOG_ERROR, "Could not send subscription acceptance to '%s' from client '%s'\n", + pak->from->partial, client->name); + } + } else { + ast_log(LOG_ERROR, "Could not allocate presence stanzas for accepting subscription from '%s' to client '%s'\n", + pak->from->partial, client->name); + } + + iks_delete(status); + iks_delete(presence); + } + + if (ast_test_flag(&cfg->flags, XMPP_COMPONENT)) { + xmpp_client_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), cfg->status, cfg->statusmsg); + } + /* This purposely flows through so we have the subscriber amongst our buddies */ + case IKS_TYPE_SUBSCRIBED: + ao2_lock(client->buddies); + + if (!(buddy = ao2_find(client->buddies, pak->from->partial, OBJ_KEY | OBJ_NOLOCK))) { + buddy = xmpp_client_create_buddy(client->buddies, pak->from->partial); + } + + if (!buddy) { + ast_log(LOG_WARNING, "Could not find or create buddy '%s' on client '%s'\n", + pak->from->partial, client->name); + } else { + ao2_ref(buddy, -1); + } + + ao2_unlock(client->buddies); + + break; + default: + break; + } + + return 0; +} + +/*! \brief Action hook for when things occur */ +static int xmpp_action_hook(void *data, int type, iks *node) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + struct ast_xmpp_client *client = data; + ikspak *pak; + int i; + + if (!node) { + ast_log(LOG_ERROR, "xmpp_action_hook was called without a packet\n"); + return IKS_HOOK; + } + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name))) { + return IKS_HOOK; + } + + /* If the client is disconnecting ignore everything */ + if (client->state == XMPP_STATE_DISCONNECTING) { + return IKS_HOOK; + } + + pak = iks_packet(node); + + /* work around iksemel's impossibility to recognize node names + * containing a colon. Set the namespace of the corresponding + * node accordingly. */ + if (iks_has_children(node) && strchr(iks_name(iks_child(node)), ':')) { + char *node_ns = NULL; + char attr[XMPP_MAX_ATTRLEN]; + char *node_name = iks_name(iks_child(node)); + char *aux = strchr(node_name, ':') + 1; + snprintf(attr, strlen("xmlns:") + (strlen(node_name) - strlen(aux)), "xmlns:%s", node_name); + node_ns = iks_find_attrib(iks_child(node), attr); + if (node_ns) { + pak->ns = node_ns; + pak->query = iks_child(node); + } + } + + /* Process through any state handlers */ + for (i = 0; i < ARRAY_LEN(xmpp_state_handlers); i++) { + if ((xmpp_state_handlers[i].state == client->state) && (xmpp_state_handlers[i].component == (ast_test_flag(&clientcfg->flags, XMPP_COMPONENT) ? 1 : 0))) { + if (xmpp_state_handlers[i].handler(client, clientcfg, type, node)) { + /* If the handler wants us to stop now, do so */ + return IKS_HOOK; + } + break; + } + } + + /* Process through any PAK handlers */ + for (i = 0; i < ARRAY_LEN(xmpp_pak_handlers); i++) { + if (xmpp_pak_handlers[i].type == pak->type) { + if (xmpp_pak_handlers[i].handler(client, clientcfg, node, pak)) { + /* If the handler wants us to stop now, do so */ + return IKS_HOOK; + } + break; + } + } + + /* Send the packet through the filter in case any filters want to process it */ + iks_filter_packet(client->filter, pak); + + iks_delete(node); + + return IKS_OK; +} + +int ast_xmpp_client_disconnect(struct ast_xmpp_client *client) +{ + if (client->thread != AST_PTHREADT_NULL) { + client->state = XMPP_STATE_DISCONNECTING; + pthread_join(client->thread, NULL); + client->thread = AST_PTHREADT_NULL; + } + +#ifdef HAVE_OPENSSL + if (client->stream_flags & SECURE) { + SSL_shutdown(client->ssl_session); + SSL_CTX_free(client->ssl_context); + SSL_free(client->ssl_session); + } +#endif + + if (client->parser) { + iks_disconnect(client->parser); + } + + /* Disconnecting the parser and going back to a disconnected state means any hooks should no longer be present */ + if (client->filter) { + iks_filter_delete(client->filter); + client->filter = NULL; + } + + client->state = XMPP_STATE_DISCONNECTED; + + return 0; +} + +/*! \brief Internal function used to reconnect an XMPP client to its server */ +static int xmpp_client_reconnect(struct ast_xmpp_client *client) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + int res = IKS_NET_NOCONN; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, client->name))) { + return -1; + } + +#ifdef HAVE_OPENSSL + client->stream_flags = 0; +#endif + + client->state = XMPP_STATE_DISCONNECTED; + client->timeout = 50; + iks_parser_reset(client->parser); + + if (!client->filter && !(client->filter = iks_filter_new())) { + ast_log(LOG_ERROR, "Could not create IKS filter for client connection '%s'\n", client->name); + return -1; + } + + /* If it's a component connect to user otherwise connect to server */ + res = iks_connect_via(client->parser, S_OR(clientcfg->server, client->jid->server), clientcfg->port, + ast_test_flag(&clientcfg->flags, XMPP_COMPONENT) ? clientcfg->user : client->jid->server); + + if (res == IKS_NET_NOCONN) { + ast_log(LOG_ERROR, "No XMPP connection available when trying to connect client '%s'\n", client->name); + return -1; + } else if (res == IKS_NET_NODNS) { + ast_log(LOG_ERROR, "No DNS available for XMPP connection when trying to connect client '%s'\n", client->name); + return -1; + } + + /* Depending on the configuration of the client we eiher jump to requesting TLS, or authenticating */ + xmpp_client_change_state(client, (ast_test_flag(&clientcfg->flags, XMPP_USETLS) ? XMPP_STATE_REQUEST_TLS : XMPP_STATE_AUTHENTICATE)); + + return 0; +} + +/*! \brief Internal function which polls on an XMPP client and receives data */ +static int xmpp_io_recv(struct ast_xmpp_client *client, char *buffer, size_t buf_len, int timeout) +{ + struct pollfd pfd = { .events = POLLIN }; + int len, res; + +#ifdef HAVE_OPENSSL + if (xmpp_is_secure(client)) { + pfd.fd = SSL_get_fd(client->ssl_session); + if (pfd.fd < 0) { + return -1; + } + } else +#endif /* HAVE_OPENSSL */ + pfd.fd = iks_fd(client->parser); + + res = ast_poll(&pfd, 1, timeout > 0 ? timeout * 1000 : -1); + if (res > 0) { +#ifdef HAVE_OPENSSL + if (xmpp_is_secure(client)) { + len = SSL_read(client->ssl_session, buffer, buf_len); + } else +#endif /* HAVE_OPENSSL */ + len = recv(pfd.fd, buffer, buf_len, 0); + + if (len > 0) { + return len; + } else if (len <= 0) { + return -1; + } + } + return res; +} + +/*! \brief Internal function which receives data from the XMPP client connection */ +static int xmpp_client_receive(struct ast_xmpp_client *client, unsigned int timeout) +{ + int len, ret, pos = 0, newbufpos = 0; + char buf[NET_IO_BUF_SIZE - 1] = ""; + char newbuf[NET_IO_BUF_SIZE - 1] = ""; + unsigned char c; + + while (1) { + len = xmpp_io_recv(client, buf, NET_IO_BUF_SIZE - 2, timeout); + if (len < 0) return IKS_NET_RWERR; + if (len == 0) return IKS_NET_EXPIRED; + buf[len] = '\0'; + + /* our iksemel parser won't work as expected if we feed + it with XML packets that contain multiple whitespace + characters between tags */ + while (pos < len) { + c = buf[pos]; + /* if we stumble on the ending tag character, + we skip any whitespace that follows it*/ + if (c == '>') { + while (isspace(buf[pos+1])) { + pos++; + } + } + newbuf[newbufpos] = c; + newbufpos++; + pos++; + } + pos = 0; + newbufpos = 0; + + /* Log the message here, because iksemel's logHook is + unaccessible */ + xmpp_log_hook(client, buf, len, 1); + + /* let iksemel deal with the string length, + and reset our buffer */ + ret = iks_parse(client->parser, newbuf, 0, 0); + memset(newbuf, 0, sizeof(newbuf)); + + switch (ret) { + case IKS_NOMEM: + ast_log(LOG_WARNING, "Parsing failure: Out of memory.\n"); + break; + case IKS_BADXML: + ast_log(LOG_WARNING, "Parsing failure: Invalid XML.\n"); + break; + case IKS_HOOK: + ast_log(LOG_WARNING, "Parsing failure: Hook returned an error.\n"); + break; + } + if (ret != IKS_OK) { + return ret; + } + ast_debug(3, "XML parsing successful\n"); + } + return IKS_OK; +} + +/*! \brief XMPP client connection thread */ +static void *xmpp_client_thread(void *data) +{ + struct ast_xmpp_client *client = data; + int res = IKS_NET_RWERR; + + do { + if (client->state == XMPP_STATE_DISCONNECTING) { + break; + } + + if (res == IKS_NET_RWERR || client->timeout == 0) { + ast_debug(3, "Connecting client '%s'\n", client->name); + if ((res = xmpp_client_reconnect(client)) != IKS_OK) { + sleep(4); + } + continue; + } + + res = xmpp_client_receive(client, 1); + + /* Decrease timeout if no data received, and delete + * old messages globally */ + if (res == IKS_NET_EXPIRED) { + client->timeout--; + } + + if (res == IKS_HOOK) { + ast_debug(2, "JABBER: Got hook event.\n"); + } else if (res == IKS_NET_TLSFAIL) { + ast_log(LOG_ERROR, "JABBER: Failure in TLS.\n"); + } else if (!client->timeout && client->state == XMPP_STATE_CONNECTED) { + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + + if (cfg && cfg->clients && (clientcfg = xmpp_config_find(cfg->clients, client->name))) { + res = ast_test_flag(&clientcfg->flags, XMPP_KEEPALIVE) ? xmpp_client_send_raw_message(client, " ") : IKS_OK; + } else { + res = IKS_OK; + } + + if (res == IKS_OK) { + client->timeout = 50; + } else { + ast_log(LOG_WARNING, "JABBER: Network Timeout\n"); + } + } else if (res == IKS_NET_RWERR) { + ast_log(LOG_WARNING, "JABBER: socket read error\n"); + } + + } while (1); + + return NULL; +} + +static int xmpp_client_config_merge_buddies(void *obj, void *arg, int flags) +{ + struct ast_xmpp_buddy *buddy1 = obj, *buddy2; + struct ao2_container *buddies = arg; + + /* If the buddy does not already exist link it into the client buddies container */ + if (!(buddy2 = ao2_find(buddies, buddy1->id, OBJ_KEY))) { + ao2_link(buddies, buddy1); + } else { + ao2_ref(buddy2, -1); + } + + /* All buddies are unlinked from the configuration buddies container, always */ + return 1; +} + +static int xmpp_client_config_post_apply(void *obj, void *arg, int flags) +{ + struct ast_xmpp_client_config *cfg = obj; + + /* Merge buddies as need be */ + ao2_callback(cfg->buddies, OBJ_MULTIPLE | OBJ_UNLINK, xmpp_client_config_merge_buddies, cfg->client->buddies); + + if (cfg->client->reconnect) { + /* Disconnect the existing session since our role is changing, or we are starting up */ + ast_xmpp_client_disconnect(cfg->client); + + if (!(cfg->client->parser = iks_stream_new(ast_test_flag(&cfg->flags, XMPP_COMPONENT) ? "jabber:component:accept" : "jabber:client", cfg->client, + xmpp_action_hook))) { + ast_log(LOG_ERROR, "Iksemel stream could not be created for client '%s' - client not active\n", cfg->name); + return -1; + } + + iks_set_log_hook(cfg->client->parser, xmpp_log_hook); + + /* Create a JID based on the given user, if no resource is given use the default */ + if (!strchr(cfg->user, '/') && !ast_test_flag(&cfg->flags, XMPP_COMPONENT)) { + char resource[strlen(cfg->user) + strlen("/asterisk-xmpp") + 1]; + + snprintf(resource, sizeof(resource), "%s/asterisk-xmpp", cfg->user); + cfg->client->jid = iks_id_new(cfg->client->stack, resource); + } else { + cfg->client->jid = iks_id_new(cfg->client->stack, cfg->user); + } + + if (!cfg->client->jid) { + ast_log(LOG_ERROR, "Jabber identity could not be created for client '%s' - client not active\n", cfg->name); + return -1; + } + + ast_pthread_create_background(&cfg->client->thread, NULL, xmpp_client_thread, cfg->client); + + cfg->client->reconnect = 0; + } else if (cfg->client->state == XMPP_STATE_CONNECTED) { + /* If this client is connected update their presence status since it may have changed */ + xmpp_client_set_presence(cfg->client, NULL, cfg->client->jid->full, cfg->status, cfg->statusmsg); + + /* Subscribe to the status of any newly added buddies */ + if (ast_test_flag(&cfg->flags, XMPP_AUTOREGISTER)) { + ao2_callback(cfg->client->buddies, OBJ_NODATA | OBJ_MULTIPLE, xmpp_client_subscribe_user, cfg->client); + } + } + + return 0; +} + +/*! + * \internal + * \brief Send a Jabber Message via call from the Manager + * \param s mansession Manager session + * \param m message Message to send + * \return 0 + */ +static int manager_jabber_send(struct mansession *s, const struct message *m) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + const char *id = astman_get_header(m, "ActionID"); + const char *jabber = astman_get_header(m, "Jabber"); + const char *screenname = astman_get_header(m, "ScreenName"); + const char *message = astman_get_header(m, "Message"); + + if (ast_strlen_zero(jabber)) { + astman_send_error(s, m, "No transport specified"); + return 0; + } + if (ast_strlen_zero(screenname)) { + astman_send_error(s, m, "No ScreenName specified"); + return 0; + } + if (ast_strlen_zero(message)) { + astman_send_error(s, m, "No Message specified"); + return 0; + } + + astman_send_ack(s, m, "Attempting to send Jabber Message"); + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, jabber))) { + astman_send_error(s, m, "Could not find Sender"); + return 0; + } + + if (strchr(screenname, '@') && !ast_xmpp_client_send_message(clientcfg->client, screenname, message)) { + astman_append(s, "Response: Success\r\n"); + } else { + astman_append(s, "Response: Error\r\n"); + } + + if (!ast_strlen_zero(id)) { + astman_append(s, "ActionID: %s\r\n", id); + } + + astman_append(s, "\r\n"); + + return 0; +} + +/*! + * \brief Build the a node request + * \param client the configured XMPP client we use to connect to a XMPP server + * \param collection name of the collection for request + * \return iks* + */ +static iks* xmpp_pubsub_build_node_request(struct ast_xmpp_client *client, const char *collection) +{ + iks *request = xmpp_pubsub_iq_create(client, "get"), *query; + + if (!request) { + return NULL; + } + + query = iks_insert(request, "query"); + iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#items"); + + if (collection) { + iks_insert_attrib(query, "node", collection); + } + + return request; +} + +/*! + * \brief Receive pubsub item lists + * \param data pointer to ast_xmpp_client structure + * \param pak response from pubsub diso#items query + * \return IKS_FILTER_EAT + */ +static int xmpp_pubsub_receive_node_list(void *data, ikspak* pak) +{ + struct ast_xmpp_client *client = data; + iks *item = NULL; + + if (iks_has_children(pak->query)) { + item = iks_first_tag(pak->query); + ast_verbose("Connection %s: %s\nNode name: %s\n", client->name, client->jid->partial, + iks_find_attrib(item, "node")); + while ((item = iks_next_tag(item))) { + ast_verbose("Node name: %s\n", iks_find_attrib(item, "node")); + } + } + + if (item) { + iks_delete(item); + } + + + return IKS_FILTER_EAT; +} + +/*! +* \brief Request item list from pubsub +* \param client the configured XMPP client we use to connect to a XMPP server +* \param collection name of the collection for request +* \return void +*/ +static void xmpp_pubsub_request_nodes(struct ast_xmpp_client *client, const char *collection) +{ + iks *request = xmpp_pubsub_build_node_request(client, collection); + + if (!request) { + ast_log(LOG_ERROR, "Could not request pubsub nodes on client '%s' - IQ could not be created\n", client->name); + return; + } + + iks_filter_add_rule(client->filter, xmpp_pubsub_receive_node_list, client, IKS_RULE_TYPE, + IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, client->mid, + IKS_RULE_DONE); + ast_xmpp_client_send(client, request); + iks_delete(request); + +} + +/* + * \brief Method to expose PubSub node list via CLI. + * \param e pointer to ast_cli_entry structure + * \param cmd + * \param a pointer to ast_cli_args structure + * \return char * + */ +static char *xmpp_cli_list_pubsub_nodes(struct ast_cli_entry *e, int cmd, struct + ast_cli_args *a) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + const char *name = NULL, *collection = NULL; + + switch (cmd) { + case CLI_INIT: + e->command = "xmpp list nodes"; + e->usage = + "Usage: xmpp list nodes [collection]\n" + " Lists the user's nodes on the respective connection\n" + " ([connection] as configured in xmpp.conf.)\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc > 5 || a->argc < 4) { + return CLI_SHOWUSAGE; + } else if (a->argc == 4 || a->argc == 5) { + name = a->argv[3]; + } + + if (a->argc == 5) { + collection = a->argv[4]; + } + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, name))) { + ast_cli(a->fd, "Unable to find client '%s'!\n", name); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Listing pubsub nodes.\n"); + + xmpp_pubsub_request_nodes(clientcfg->client, collection); + + return CLI_SUCCESS; +} + +/*! + * \brief Delete pubsub item lists + * \param data pointer to ast_xmpp_client structure + * \param pak response from pubsub diso#items query + * \return IKS_FILTER_EAT + */ +static int xmpp_pubsub_delete_node_list(void *data, ikspak* pak) +{ + struct ast_xmpp_client *client = data; + iks *item = NULL; + + if (iks_has_children(pak->query)) { + item = iks_first_tag(pak->query); + ast_log(LOG_WARNING, "Connection: %s Node name: %s\n", client->jid->partial, + iks_find_attrib(item, "node")); + while ((item = iks_next_tag(item))) { + xmpp_pubsub_delete_node(client, iks_find_attrib(item, "node")); + } + } + + if (item) { + iks_delete(item); + } + + return IKS_FILTER_EAT; +} + +static void xmpp_pubsub_purge_nodes(struct ast_xmpp_client *client, const char* collection_name) +{ + iks *request = xmpp_pubsub_build_node_request(client, collection_name); + ast_xmpp_client_send(client, request); + iks_filter_add_rule(client->filter, xmpp_pubsub_delete_node_list, client, IKS_RULE_TYPE, + IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, client->mid, + IKS_RULE_DONE); + ast_xmpp_client_send(client, request); + iks_delete(request); +} + +/*! + * \brief Method to purge PubSub nodes via CLI. + * \param e pointer to ast_cli_entry structure + * \param cmd + * \param a pointer to ast_cli_args structure + * \return char * + */ +static char *xmpp_cli_purge_pubsub_nodes(struct ast_cli_entry *e, int cmd, struct + ast_cli_args *a) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + const char *name; + + switch (cmd) { + case CLI_INIT: + e->command = "xmpp purge nodes"; + e->usage = + "Usage: xmpp purge nodes \n" + " Purges nodes on PubSub server\n" + " as configured in xmpp.conf.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + name = a->argv[3]; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, name))) { + ast_cli(a->fd, "Unable to find client '%s'!\n", name); + return CLI_FAILURE; + } + + if (ast_test_flag(&cfg->global->pubsub, XMPP_XEP0248)) { + xmpp_pubsub_purge_nodes(clientcfg->client, a->argv[4]); + } else { + xmpp_pubsub_delete_node(clientcfg->client, a->argv[4]); + } + + return CLI_SUCCESS; +} + +/*! + * \brief Method to expose PubSub node deletion via CLI. + * \param e pointer to ast_cli_entry structure + * \param cmd + * \param a pointer to ast_cli_args structure + * \return char * + */ +static char *xmpp_cli_delete_pubsub_node(struct ast_cli_entry *e, int cmd, struct + ast_cli_args *a) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + const char *name; + + switch (cmd) { + case CLI_INIT: + e->command = "xmpp delete node"; + e->usage = + "Usage: xmpp delete node \n" + " Deletes a node on PubSub server\n" + " as configured in xmpp.conf.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + name = a->argv[3]; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, name))) { + ast_cli(a->fd, "Unable to find client '%s'!\n", name); + return CLI_FAILURE; + } + + xmpp_pubsub_delete_node(clientcfg->client, a->argv[4]); + + return CLI_SUCCESS; +} + +/*! + * \brief Method to expose PubSub collection node creation via CLI. + * \return char *. + */ +static char *xmpp_cli_create_collection(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + const char *name, *collection_name; + + switch (cmd) { + case CLI_INIT: + e->command = "xmpp create collection"; + e->usage = + "Usage: xmpp create collection \n" + " Creates a PubSub collection node using the account\n" + " as configured in xmpp.conf.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 5) { + return CLI_SHOWUSAGE; + } + name = a->argv[3]; + collection_name = a->argv[4]; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, name))) { + ast_cli(a->fd, "Unable to find client '%s'!\n", name); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Creating test PubSub node collection.\n"); + + xmpp_pubsub_create_collection(clientcfg->client, collection_name); + + return CLI_SUCCESS; +} + +/*! + * \brief Method to expose PubSub leaf node creation via CLI. + * \return char *. + */ +static char *xmpp_cli_create_leafnode(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + RAII_VAR(struct ast_xmpp_client_config *, clientcfg, NULL, ao2_cleanup); + const char *name, *collection_name, *leaf_name; + + switch (cmd) { + case CLI_INIT: + e->command = "xmpp create leaf"; + e->usage = + "Usage: xmpp create leaf \n" + " Creates a PubSub leaf node using the account\n" + " as configured in xmpp.conf.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 6) { + return CLI_SHOWUSAGE; + } + name = a->argv[3]; + collection_name = a->argv[4]; + leaf_name = a->argv[5]; + + if (!cfg || !cfg->clients || !(clientcfg = xmpp_config_find(cfg->clients, name))) { + ast_cli(a->fd, "Unable to find client '%s'!\n", name); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Creating test PubSub node collection.\n"); + + xmpp_pubsub_create_leaf(clientcfg->client, collection_name, leaf_name); + + return CLI_SUCCESS; +} + +/*! + * \internal + * \brief Turn on/off console debugging. + * \return CLI_SUCCESS. + */ +static char *xmpp_do_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + switch (cmd) { + case CLI_INIT: + e->command = "xmpp set debug {on|off}"; + e->usage = + "Usage: xmpp set debug {on|off}\n" + " Enables/disables dumping of XMPP/Jabber packets for debugging purposes.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != e->args) { + return CLI_SHOWUSAGE; + } + + if (!strncasecmp(a->argv[e->args - 1], "on", 2)) { + debug = 1; + ast_cli(a->fd, "XMPP Debugging Enabled.\n"); + return CLI_SUCCESS; + } else if (!strncasecmp(a->argv[e->args - 1], "off", 3)) { + debug = 0; + ast_cli(a->fd, "XMPP Debugging Disabled.\n"); + return CLI_SUCCESS; + } + return CLI_SHOWUSAGE; /* defaults to invalid */ +} + +/*! + * \internal + * \brief Show client status. + * \return CLI_SUCCESS. + */ +static char *xmpp_show_clients(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + struct ao2_iterator i; + struct ast_xmpp_client_config *clientcfg; + + switch (cmd) { + case CLI_INIT: + e->command = "xmpp show connections"; + e->usage = + "Usage: xmpp show connections\n" + " Shows state of client and component connections\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (!cfg || !cfg->clients) { + return NULL; + } + + ast_cli(a->fd, "Jabber Users and their status:\n"); + + i = ao2_iterator_init(cfg->clients, 0); + while ((clientcfg = ao2_iterator_next(&i))) { + char *state; + + switch (clientcfg->client->state) { + case XMPP_STATE_DISCONNECTING: + state = "Disconnecting"; + break; + case XMPP_STATE_DISCONNECTED: + state = "Disconnected"; + break; + case XMPP_STATE_CONNECTING: + state = "Connecting"; + break; + case XMPP_STATE_REQUEST_TLS: + state = "Waiting to request TLS"; + break; + case XMPP_STATE_REQUESTED_TLS: + state = "Requested TLS"; + break; + case XMPP_STATE_AUTHENTICATE: + state = "Waiting to authenticate"; + break; + case XMPP_STATE_AUTHENTICATING: + state = "Authenticating"; + break; + case XMPP_STATE_ROSTER: + state = "Retrieving roster"; + break; + case XMPP_STATE_CONNECTED: + state = "Connected"; + break; + default: + state = "Unknown"; + } + + ast_cli(a->fd, " [%s] %s - %s\n", clientcfg->name, clientcfg->user, state); + + ao2_ref(clientcfg, -1); + } + ao2_iterator_destroy(&i); + + ast_cli(a->fd, "----\n"); + ast_cli(a->fd, " Number of clients: %d\n", ao2_container_count(cfg->clients)); + + return CLI_SUCCESS; +} + +/*! + * \internal + * \brief Show buddy lists + * \return CLI_SUCCESS. + */ +static char *xmpp_show_buddies(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + RAII_VAR(struct xmpp_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup); + struct ao2_iterator i; + struct ast_xmpp_client_config *clientcfg; + + switch (cmd) { + case CLI_INIT: + e->command = "xmpp show buddies"; + e->usage = + "Usage: xmpp show buddies\n" + " Shows buddy lists of our clients\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (!cfg || !cfg->clients) { + return NULL; + } + + ast_cli(a->fd, "XMPP buddy lists\n"); + + i = ao2_iterator_init(cfg->clients, 0); + while ((clientcfg = ao2_iterator_next(&i))) { + struct ao2_iterator bud; + struct ast_xmpp_buddy *buddy; + + ast_cli(a->fd, "Client: %s\n", clientcfg->name); + + bud = ao2_iterator_init(clientcfg->client->buddies, 0); + while ((buddy = ao2_iterator_next(&bud))) { + struct ao2_iterator res; + struct ast_xmpp_resource *resource; + + ast_cli(a->fd, "\tBuddy:\t%s\n", buddy->id); + + res = ao2_iterator_init(buddy->resources, 0); + while ((resource = ao2_iterator_next(&res))) { + ast_cli(a->fd, "\t\tResource: %s\n", resource->resource); + ast_cli(a->fd, "\t\t\tnode: %s\n", resource->caps.node); + ast_cli(a->fd, "\t\t\tversion: %s\n", resource->caps.version); + ast_cli(a->fd, "\t\t\tGoogle Talk capable: %s\n", resource->caps.google ? "yes" : "no"); + ast_cli(a->fd, "\t\t\tJingle capable: %s\n", resource->caps.jingle ? "yes" : "no"); + + ao2_ref(resource, -1); + } + ao2_iterator_destroy(&res); + + ao2_ref(buddy, -1); + } + ao2_iterator_destroy(&bud); + + ao2_ref(clientcfg, -1); + } + ao2_iterator_destroy(&i); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry xmpp_cli[] = { + AST_CLI_DEFINE(xmpp_do_set_debug, "Enable/Disable Jabber debug"), + AST_CLI_DEFINE(xmpp_show_clients, "Show state of clients and components"), + AST_CLI_DEFINE(xmpp_show_buddies, "Show buddy lists of our clients"), + AST_CLI_DEFINE(xmpp_cli_create_collection, "Creates a PubSub node collection."), + AST_CLI_DEFINE(xmpp_cli_list_pubsub_nodes, "Lists PubSub nodes"), + AST_CLI_DEFINE(xmpp_cli_create_leafnode, "Creates a PubSub leaf node"), + AST_CLI_DEFINE(xmpp_cli_delete_pubsub_node, "Deletes a PubSub node"), + AST_CLI_DEFINE(xmpp_cli_purge_pubsub_nodes, "Purges PubSub nodes"), +}; + +static int unload_module(void) +{ + ast_msg_tech_unregister(&msg_tech); + ast_cli_unregister_multiple(xmpp_cli, ARRAY_LEN(xmpp_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); + if (mwi_sub) { + ast_event_unsubscribe(mwi_sub); + } + if (device_state_sub) { + ast_event_unsubscribe(device_state_sub); + } + ast_custom_function_unregister(&jabberreceive_function); + + ast_cond_destroy(&message_received_condition); + ast_mutex_destroy(&messagelock); + + return 0; +} + +static int global_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_xmpp_global_config *global = obj; + + if (!strcasecmp(var->name, "debug")) { + debug = ast_true(var->value); + } else if (!strcasecmp(var->name, "autoprune")) { + ast_set2_flag(&global->general, ast_true(var->value), XMPP_AUTOPRUNE); + } else if (!strcasecmp(var->name, "autoregister")) { + ast_set2_flag(&global->general, ast_true(var->value), XMPP_AUTOREGISTER); + } else if (!strcasecmp(var->name, "auth_policy")) { + ast_set2_flag(&global->general, !strcasecmp(var->value, "accept") ? 1 : 0, XMPP_AUTOACCEPT); + } else if (!strcasecmp(var->name, "collection_nodes")) { + ast_set2_flag(&global->pubsub, ast_true(var->value), XMPP_XEP0248); + } else if (!strcasecmp(var->name, "pubsub_autocreate")) { + ast_set2_flag(&global->pubsub, ast_true(var->value), XMPP_PUBSUB_AUTOCREATE); + } else { + return -1; + } + + return 0; +} + +static int client_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_xmpp_client_config *cfg = obj; + + if (!strcasecmp(var->name, "debug")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_DEBUG); + } else if (!strcasecmp(var->name, "type")) { + ast_set2_flag(&cfg->flags, !strcasecmp(var->value, "component") ? 1 : 0, XMPP_COMPONENT); + } else if (!strcasecmp(var->name, "distribute_events")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_DISTRIBUTE_EVENTS); + } else if (!strcasecmp(var->name, "usetls")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_USETLS); + } else if (!strcasecmp(var->name, "usesasl")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_USESASL); + } else if (!strcasecmp(var->name, "forceoldssl")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_FORCESSL); + } else if (!strcasecmp(var->name, "keepalive")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_KEEPALIVE); + } else if (!strcasecmp(var->name, "autoprune")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_AUTOPRUNE); + } else if (!strcasecmp(var->name, "autoregister")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_AUTOREGISTER); + } else if (!strcasecmp(var->name, "auth_policy")) { + ast_set2_flag(&cfg->flags, !strcasecmp(var->value, "accept") ? 1 : 0, XMPP_AUTOACCEPT); + } else if (!strcasecmp(var->name, "sendtodialplan")) { + ast_set2_flag(&cfg->flags, ast_true(var->value), XMPP_SEND_TO_DIALPLAN); + } else { + return -1; + } + + return 0; +} + +static int client_status_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_xmpp_client_config *cfg = obj; + + if (!strcasecmp(var->value, "unavailable")) { + cfg->status = IKS_SHOW_UNAVAILABLE; + } else if (!strcasecmp(var->value, "available") || !strcasecmp(var->value, "online")) { + cfg->status = IKS_SHOW_AVAILABLE; + } else if (!strcasecmp(var->value, "chat") || !strcasecmp(var->value, "chatty")) { + cfg->status = IKS_SHOW_CHAT; + } else if (!strcasecmp(var->value, "away")) { + cfg->status = IKS_SHOW_AWAY; + } else if (!strcasecmp(var->value, "xa") || !strcasecmp(var->value, "xaway")) { + cfg->status = IKS_SHOW_XA; + } else if (!strcasecmp(var->value, "dnd")) { + cfg->status = IKS_SHOW_DND; + } else if (!strcasecmp(var->value, "invisible")) { +#ifdef IKS_SHOW_INVISIBLE + cfg->status = IKS_SHOW_INVISIBLE; +#else + cfg->status = IKS_SHOW_DND; +#endif + } else { + return -1; + } + + return 0; +} + +static int client_buddy_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct ast_xmpp_client_config *cfg = obj; + struct ast_xmpp_buddy *buddy; + + if ((buddy = ao2_find(cfg->buddies, var->value, OBJ_KEY))) { + ao2_ref(buddy, -1); + return -1; + } + + if (!(buddy = xmpp_client_create_buddy(cfg->buddies, var->value))) { + return -1; + } + + ao2_ref(buddy, -1); + + return 0; +} + +static int load_module(void) +{ + if (aco_info_init(&cfg_info)) { + return AST_MODULE_LOAD_DECLINE; + } + + aco_option_register_custom(&cfg_info, "debug", ACO_EXACT, global_options, "no", global_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "autoprune", ACO_EXACT, global_options, "no", global_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "autoregister", ACO_EXACT, global_options, "yes", global_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "collection_nodes", ACO_EXACT, global_options, "no", global_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "pubsub_autocreate", ACO_EXACT, global_options, "no", global_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "auth_policy", ACO_EXACT, global_options, "accept", global_bitfield_handler, 0); + + aco_option_register(&cfg_info, "username", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, user)); + aco_option_register(&cfg_info, "secret", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, password)); + aco_option_register(&cfg_info, "serverhost", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, server)); + aco_option_register(&cfg_info, "statusmessage", ACO_EXACT, client_options, "Online and Available", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, statusmsg)); + aco_option_register(&cfg_info, "pubsub_node", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, pubsubnode)); + aco_option_register(&cfg_info, "context", ACO_EXACT, client_options, "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, context)); + aco_option_register(&cfg_info, "priority", ACO_EXACT, client_options, "1", OPT_UINT_T, 0, FLDSET(struct ast_xmpp_client_config, priority)); + aco_option_register(&cfg_info, "port", ACO_EXACT, client_options, "5222", OPT_UINT_T, 0, FLDSET(struct ast_xmpp_client_config, port)); + aco_option_register(&cfg_info, "timeout", ACO_EXACT, client_options, "5", OPT_UINT_T, 0, FLDSET(struct ast_xmpp_client_config, message_timeout)); + + aco_option_register_custom(&cfg_info, "debug", ACO_EXACT, client_options, "no", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "type", ACO_EXACT, client_options, "client", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "distribute_events", ACO_EXACT, client_options, "no", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "usetls", ACO_EXACT, client_options, "yes", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "usesasl", ACO_EXACT, client_options, "yes", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "forceoldssl", ACO_EXACT, client_options, "no", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "keepalive", ACO_EXACT, client_options, "yes", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "autoprune", ACO_EXACT, client_options, "no", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "autoregister", ACO_EXACT, client_options, "yes", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "auth_policy", ACO_EXACT, client_options, "accept", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "sendtodialplan", ACO_EXACT, client_options, "no", client_bitfield_handler, 0); + aco_option_register_custom(&cfg_info, "status", ACO_EXACT, client_options, "available", client_status_handler, 0); + aco_option_register_custom(&cfg_info, "buddy", ACO_EXACT, client_options, NULL, client_buddy_handler, 0); + + if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) { + return AST_MODULE_LOAD_DECLINE; + } + + ast_manager_register_xml("JabberSend", EVENT_FLAG_SYSTEM, manager_jabber_send); + + ast_register_application_xml(app_ajisend, xmpp_send_exec); + ast_register_application_xml(app_ajisendgroup, xmpp_sendgroup_exec); + ast_register_application_xml(app_ajistatus, xmpp_status_exec); + ast_register_application_xml(app_ajijoin, xmpp_join_exec); + ast_register_application_xml(app_ajileave, xmpp_leave_exec); + + ast_cli_register_multiple(xmpp_cli, ARRAY_LEN(xmpp_cli)); + ast_custom_function_register(&jabberstatus_function); + ast_custom_function_register(&jabberreceive_function); + ast_msg_tech_register(&msg_tech); + + ast_mutex_init(&messagelock); + ast_cond_init(&message_received_condition, NULL); + + return AST_MODULE_LOAD_SUCCESS; +} + +static int reload(void) +{ + if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) { + return AST_MODULE_LOAD_DECLINE; + } + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Asterisk XMPP Interface", + .load = load_module, + .unload = unload_module, + .reload = reload, + .load_pri = AST_MODPRI_CHANNEL_DEPEND, + );