aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortilghman <tilghman@f38db490-d61c-443f-a65b-d21fe96a405b>2010-06-15 17:06:23 +0000
committertilghman <tilghman@f38db490-d61c-443f-a65b-d21fe96a405b>2010-06-15 17:06:23 +0000
commit1e5fadf04d0a542f347bafee1a401a84684f9cec (patch)
tree0ee2cba4b60b7843bf03b5cea55baa3ffd4d4a73
parent3285486390c9652b274d41fb62d57541965a62e4 (diff)
Add distributed devicestate via the XMPP protocol.
(closes issue #15757) Reported by: Marquis Patches: distributed_devstate-XMPP.txt uploaded by lmadsen (license 10) Tested by: Marquis, lmadsen, marcelloceschia Review: https://reviewboard.asterisk.org/r/351/ git-svn-id: http://svn.digium.com/svn/asterisk/trunk@270519 f38db490-d61c-443f-a65b-d21fe96a405b
-rw-r--r--CHANGES2
-rw-r--r--configs/jabber.conf.sample64
-rw-r--r--doc/distributed_devstate-XMPP.txt433
-rw-r--r--include/asterisk/jabber.h18
-rw-r--r--res/res_jabber.c1400
5 files changed, 1632 insertions, 285 deletions
diff --git a/CHANGES b/CHANGES
index 66e023a1f..5e69680c8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -496,6 +496,8 @@ Miscellaneous
initialization order.
* The Realtime dialplan switch now caches entries for 1 second. This provides a
significant increase in performance (about 3X) for installations using this switchtype.
+ * Distributed devicestate now supports the use of the XMPP protocol, in addition to
+ AIS. For more information, please see doc/distributed_devstate-XMPP.txt
CLI Changes
-----------
diff --git a/configs/jabber.conf.sample b/configs/jabber.conf.sample
index 5cbd0c0c7..d02a7b301 100644
--- a/configs/jabber.conf.sample
+++ b/configs/jabber.conf.sample
@@ -1,29 +1,41 @@
[general]
-;debug=yes ;;Turn on debugging 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.
-;auth_policy=accept ;;Auto accept users' subscription requests (default).
- ;;Set to deny for auto denial.
+;debug=yes ;;Turn on debugging 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] ;;label
-;type=client ;;Client or Component connection
-;serverhost=astjab.org ;;Route to server for example,
- ;; talk.google.com
-;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.
-;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.
+;[asterisk] ;;label
+;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.
diff --git a/doc/distributed_devstate-XMPP.txt b/doc/distributed_devstate-XMPP.txt
new file mode 100644
index 000000000..1a8c143b4
--- /dev/null
+++ b/doc/distributed_devstate-XMPP.txt
@@ -0,0 +1,433 @@
+===============================================================================
+===
+=== XMPP PubSub Distributed Device State
+===
+=== Copyright (C) 2010, Digium, Inc.
+=== Leif Madsen <lmadsen@digium.com>
+===
+===============================================================================
+
+-------------------------------------------------------------------------------
+--- INTRODUCTION
+-------------------------------------------------------------------------------
+
+This document describes installing and utilizing XMPP PubSub events to
+distribute device state and message waiting indication (MWI) events between
+servers. The difference between this method and OpenAIS (see
+distributed_devstate.txt) is that OpenAIS can only be used in low latency
+networks; meaning only on the LAN, and not across the internet.
+
+If you plan on distributing device state or MWI across the internet, then you
+will require the use of XMPP PubSub events.
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Tigase Installation
+-------------------------------------------------------------------------------
+
+-- Description --
+
+Currently the only server supported for XMPP PubSub events is the Tigase open
+source XMPP/Jabber environment. This is the server that the various Asterisk
+servers will connect to in order to distribute the events. The Tigase server can
+even be clustered in order to provide high availability for your device state;
+however, that is beyond the scope of this document.
+
+For more information about Tigase, visit their web site:
+
+ http://www.tigase.org
+
+-- Download --
+
+To download the Tigase environment, get the latest version at:
+
+ http://www.tigase.org/en/filebrowser/tigase-server
+
+-- Install --
+
+The Tigase server requires a working Java environment, including both a JRE
+(Java Runtime Environment) and a JDK (Java Development Kit), currently at least
+version 1.6.
+
+For more information about how to install Tigase, see the web site:
+
+ http://www.tigase.org/en/content/quick-start
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Tigase Configuration
+-------------------------------------------------------------------------------
+
+While installing Tigase, be sure you enable the PubSub module. Without it, the
+PubSub events won't be accepted by the server, and your device state will not be
+distributed.
+
+There are a couple of things you need to configure in Tigase before you start it
+in order for Asterisk to connect. The first thing we need to do is generate the
+self-signed certificate. To do this we use the keytool application. More
+information can be found here:
+
+ http://www.tigase.org/en/content/server-certificate
+
+-- Generating the keystore file --
+
+Generally, we need to run the following commands to generate a new keystore
+file.
+
+# cd /opt/Tigase-4.3.1-b1858/certs
+
+Be sure to change the 'yourdomain' to your domain.
+
+# keytool -genkey -alias yourdomain -keystore rsa-keystore \
+ -keyalg RSA -sigalg MD5withRSA
+
+The keytool application will then ask you for a password. Use the password
+'keystore' as this is the default password that Tigase will use to load the
+keystore file.
+
+You then need to specify your domain as the first value to be entered in the
+security certificate.
+
+What is your first and last name?
+ [Unknown]: asterisk.mydomain.tld
+What is the name of your organizational unit?
+ [Unknown]:
+What is the name of your organization?
+ [Unknown]:
+What is the name of your City or Locality?
+ [Unknown]:
+What is the name of your State or Province?
+ [Unknown]:
+What is the two-letter country code for this unit?
+ [Unknown]:
+Is CN=asterisk.mydomain.tld, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
+ [no]: yes
+
+You will then be asked for another password, in which case you must just press
+enter for the same password as Tigase will not work without them being the same.
+
+Enter key password for <mykey>
+ (RETURN if same as keystore password):
+
+
+-- Configuring init.properties --
+
+The next step is to configure the init.properties file which is used by Tigase
+to generate the tigase.xml file. Whenever you change the init.properties file
+because sure to remove the current tigase.xml file so that it will be
+regenerated at start up.
+
+# cd /opt/Tigase-4.3.1-b1858/etc
+
+Then edit the init.properties file and add the following:
+
+config-type=--gen-config-def
+--admins=admin@asterisk.mydomain.tld
+--virt-hosts=asterisk.mydomain.tld
+--debug=server
+--user-db=derby
+--user-db-uri=jdbc:derby:/opt/Tigase-4.3.1-b1858
+--comp-name-1=pubsub
+--comp-class-1=tigase.pubsub.PubSubComponent
+
+Be sure to change the domain in the --admin and --virt-hosts options. The most
+important lines are --comp-name-1 and --comp-class-1 which tell Tigase to load
+the PubSub module.
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Running Tigase
+-------------------------------------------------------------------------------
+
+You can then start the Tigase server with the tigase.sh script.
+
+# cd /opt/Tigase-4.3.1-b1858
+# ./scripts/tigase.sh start etc/tigase.conf
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Adding Buddies to Tigase
+-------------------------------------------------------------------------------
+
+At this time, Asterisk is not able to automatically register your peers for you,
+so you'll need to use an external application to do the initial registration.
+
+Pidgin is an excellent multi-protocol instant messenger application which
+supports XMPP. It runs on Linux, Windows, and OSX, and is open source. You can
+get Pidgin from http://www.pidgin.im
+
+Then add the two buddies we'll use in Asterisk with Pidgin by connecting to
+the Tigase server. For more information about how to register new buddies, see
+the Pidgin documentation.
+
+Once the initial registration is done and loaded into Tigase, you no longer need
+to worry about using Pidgin. Asterisk will then be able to load the peers into
+memory at start up.
+
+The example peers we've used in the following documentation for our two nodes
+are:
+
+server1@asterisk.mydomain.tld/astvoip1
+server2@asterisk.mydomain.tld/astvoip2
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Installing Asterisk
+-------------------------------------------------------------------------------
+
+Install Asterisk as usual. However, you'll need to make sure you have the
+res_jabber module compiled, which requires the iksemel development library.
+Additionally, be sure you have the OpenSSL development library installed so you
+can connect securly to the Tigase server.
+
+Make sure you check menuselect that res_jabber is selected so that it will
+compile.
+
+# cd asterisk-source
+# ./configure
+
+# make menuselect
+ ---> Resource Modules
+
+If you don't have jabber.conf in your existing configuration, because sure to
+copy the sample configuration file there.
+
+# cd configs
+# cp jabber.conf.sample /etc/asterisk/jabber.conf
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Configuring Asterisk
+-------------------------------------------------------------------------------
+
+We then need to configure our servers to communicate with the Tigase server. We
+need to modify the jabber.conf file on the servers. The configurations below are
+for a 2 server setup, but could be expanded for additional servers easily.
+
+The key note here is to note that the pubsub_node option needs to start with
+pubsub, so for example, pubsub.asterisk.mydomain.tld. Without the 'pubsub' your
+Asterisk system will not be able to distribute events.
+
+Additionally, you will need to specify each of the servers you need to connec to
+using the 'buddy' option.
+
+
+-- Asterisk Server 1 --
+
+[general]
+debug=no ;;Turn on debugging 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'.
+
+[asterisk]
+type=client
+serverhost=asterisk.mydomain.tld
+pubsub_node=pubsub.asterisk.mydomain.tld
+username=server1@asterisk.mydomain.tld/astvoip1
+secret=welcome
+distribute_events=yes
+status=available
+usetls=no
+usesasl=yes
+buddy=server2@asterisk.mydomain.tld/astvoip2
+
+
+-- Asterisk Server 2 --
+
+[general]
+debug=yes ;;Turn on debugging 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'.
+
+[asterisk]
+type=client
+serverhost=asterisk.mydomain.tld
+pubsub_node=pubsub.asterisk.mydomain.tld
+username=server2@asterisk.mydomain.tld/astvoip2
+secret=welcome
+distribute_events=yes
+status=available
+usetls=no
+usesasl=yes
+buddy=server1@asterisk.mydomain.tld/astvoip1
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Basic Testing of Asterisk with XMPP PubSub
+-------------------------------------------------------------------------------
+
+Once you have Asterisk installed with XMPP PubSub, it is time to test it out.
+
+We need to start up our first server and make sure we get connected to the XMPP
+server. We can verify this with an Asterisk console command to determine if
+we're connected.
+
+On Asterisk 1 we can run 'jabber show connected' to verify we're connected to
+the XMPP server.
+
+*CLI> jabber show connected
+Jabber Users and their status:
+ User: server1@asterisk.mydomain.tld/astvoip1 - Connected
+----
+ Number of users: 1
+
+The command above has given us output which verifies we've connected our first
+server.
+
+We can then check the state of our buddies with the 'jabber show buddies' CLI
+command.
+
+*CLI> jabber show buddies
+Jabber buddy lists
+Client: server1@asterisk.mydomain.tld/astvoip1
+ Buddy: server2@asterisk.mydomain.tld
+ Resource: None
+ Buddy: server2@asterisk.mydomain.tld/astvoip2
+ Resource: None
+
+The output above tells us we're not connected to any buddies, and thus we're not
+distributing state to anyone (or getting it from anyone). That makes sense since
+we haven't yet started our other server.
+
+Now, let's start the other server and verify the servers are able to establish
+a connection between each other.
+
+On Asterisk 2, again we run the 'jabber show connected' command to make sure
+we've connected successfully to the XMPP server.
+
+*CLI> jabber show connected
+Jabber Users and their status:
+ User: server2@asterisk.mydomain.tld/astvoip2 - Connected
+----
+ Number of users: 1
+
+And now we can check the status of our buddies.
+
+*CLI> jabber show buddies
+Jabber buddy lists
+Client: server2@scooter/astvoip2
+ Buddy: server1@asterisk.mydomain.tld
+ Resource: astvoip1
+ node: http://www.asterisk.org/xmpp/client/caps
+ version: asterisk-xmpp
+ Jingle capable: yes
+ Status: 1
+ Priority: 0
+ Buddy: server1@asterisk.mydomain.tld/astvoip1
+ Resource: None
+
+Excellent! So we're connected to the buddy on Asterisk 1, and we could run the
+same command on Asterisk 1 to verify the buddy on Asterisk 2 is seen.
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Testing Distributed Device State
+-------------------------------------------------------------------------------
+
+The easiest way to test distributed device state is to use the DEVICE_STATE()
+diaplan function. For example, you could have the following piece of dialplan
+on every server:
+
+[devstate_test]
+
+exten => 1234,hint,Custom:mystate
+
+exten => set_inuse,1,Set(DEVICE_STATE(Custom:mystate)=INUSE)
+exten => set_not_inuse,1,Set(DEVICE_STATE(Custom:mystate)=NOT_INUSE)
+
+exten => check,1,NoOp(Custom:mystate is ${DEVICE_STATE(Custom:mystate)})
+
+
+Now, you can test that the cluster-wide state of "Custom:mystate" is what
+you would expect after going to the CLI of each server and adjusting the state.
+
+server1*CLI> console dial set_inuse@devstate_test
+ ...
+
+server2*CLI> console dial check@devstate_test
+ -- Executing [check@devstate_test:1] NoOp("OSS/dsp", "Custom:mystate is INUSE") in new stack
+
+Various combinations of setting and checking the state on different servers can
+be used to verify that it works as expected. Also, you can see the status of
+the hint on each server, as well, to see how extension state would reflect the
+state change with distributed device state:
+
+server2*CLI> core show hints
+ -= Registered Asterisk Dial Plan Hints =-
+ 1234@devstate_test : Custom:mystate State:InUse Watchers 0
+
+
+One other helpful thing here during testing and debugging is to enable debug
+logging. To do so, enable debug on the console in /etc/asterisk/logger.conf.
+Also, enable debug at the Asterisk CLI.
+
+*CLI> core set debug 1
+
+When you have this debug enabled, you will see output during the processing of
+every device state change. The important thing to look for is where the known
+state of the device for each server is added together to determine the overall
+state.
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Notes On Large Installations
+-------------------------------------------------------------------------------
+
+On larger installations where you want a fully meshed network of buddies (i.e.
+all servers have all the buddies of the remote servers), you may want some
+method of distributing those buddies automatically so you don't need to modify
+all servers (N+1) every time you add a new server to the cluster.
+
+The problem there is that you're confined by what's allowed in XEP-0060, and
+unfortunately that means modifying affiliations by individual JID (as opposed to
+the various subscription access models, which are more flexible).
+
+See here for details:
+http://xmpp.org/extensions/xep-0060.html#owner-affiliations
+
+One method for making this slightly easier is to utilize the #exec functionality
+in configuration files, and dynamically generate the buddies via script that
+pulls the information from a database, or to #include a file which is
+automatically generated on all the servers when you add a new node to the
+cluster.
+
+Unfortunately this still requires a reload of res_jabber.so on all the servers,
+but this could also be solved through the use of the Asterisk Manager Interface
+(AMI).
+
+So while this is not the ideal situation, it is programmatically solvable with
+existing technologies and features found in Asterisk today.
+
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+--- Questions, Comments, and Bug Reports
+-------------------------------------------------------------------------------
+
+Please utilize the Asterisk issue tracker for all bug reports at
+https://issues.asterisk.org
diff --git a/include/asterisk/jabber.h b/include/asterisk/jabber.h
index a3ee0b2fa..5f5dd14f3 100644
--- a/include/asterisk/jabber.h
+++ b/include/asterisk/jabber.h
@@ -1,7 +1,7 @@
/*
* Asterisk -- An open source telephony toolkit.
*
- * Copyright (C) 1999 - 2005, Digium, Inc.
+ * Copyright (C) 1999 - 2010, Digium, Inc.
*
* Matt O'Gorman <mogorman@digium.com>
*
@@ -85,13 +85,19 @@ enum aji_state {
enum {
AJI_AUTOPRUNE = (1 << 0),
AJI_AUTOREGISTER = (1 << 1),
- AJI_AUTOACCEPT = (1 << 2)
+ AJI_AUTOACCEPT = (1 << 2),
+};
+
+enum {
+ AJI_XEP0248 = (1 << 0),
+ AJI_PUBSUB = (1 << 1),
+ AJI_PUBSUB_AUTOCREATE = (1 << 2),
};
enum aji_btype {
- AJI_USER=0,
- AJI_TRANS=1,
- AJI_UTRANS=2
+ AJI_USER = 0,
+ AJI_TRANS = 1,
+ AJI_UTRANS = 2,
};
struct aji_version {
@@ -145,6 +151,7 @@ struct aji_client {
char password[160];
char user[AJI_MAX_JIDLEN];
char serverhost[AJI_MAX_RESJIDLEN];
+ char pubsub_node[AJI_MAX_RESJIDLEN];
char statusmessage[256];
char name_space[256];
char sid[10]; /* Session ID */
@@ -170,6 +177,7 @@ struct aji_client {
int timeout;
int message_timeout;
int authorized;
+ int distribute_events;
struct ast_flags flags;
int component; /* 0 client, 1 component */
struct aji_buddy_container buddies;
diff --git a/res/res_jabber.c b/res/res_jabber.c
index 611028887..68c7c8f12 100644
--- a/res/res_jabber.c
+++ b/res/res_jabber.c
@@ -1,7 +1,7 @@
/*
* Asterisk -- An open source telephony toolkit.
*
- * Copyright (C) 1999 - 2006, Digium, Inc.
+ * Copyright (C) 1999 - 2010, Digium, Inc.
*
* Matt O'Gorman <mogorman@digium.com>
*
@@ -58,6 +58,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/astobj.h"
#include "asterisk/astdb.h"
#include "asterisk/manager.h"
+#include "asterisk/event.h"
+#include "asterisk/devicestate.h"
/*** DOCUMENTATION
<application name="JabberSend" language="en_US">
@@ -277,8 +279,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</manager>
***/
-/*! \todo This should really be renamed to xmpp.conf. For backwards compatibility, we
- need to read both files */
+/*!\todo This should really be renamed to xmpp.conf. For backwards compatibility, we
+ * need to read both files */
#define JABBER_CONFIG "jabber.conf"
/*-- Forward declarations */
@@ -325,7 +327,45 @@ static int aji_ditems_handler(void *data, ikspak *pak);
static int aji_register_query_handler(void *data, ikspak *pak);
static int aji_register_approve_handler(void *data, ikspak *pak);
static int aji_reconnect(struct aji_client *client);
+static char *aji_cli_create_collection(struct ast_cli_entry *e, int cmd,
+ struct ast_cli_args *a);
+static char *aji_cli_list_pubsub_nodes(struct ast_cli_entry *e, int cmd,
+ struct ast_cli_args *a);
+static char *aji_cli_delete_pubsub_node(struct ast_cli_entry *e, int cmd, struct
+ ast_cli_args *a);
+static char *aji_cli_purge_pubsub_nodes(struct ast_cli_entry *e, int cmd, struct
+ ast_cli_args *a);
static iks *jabber_make_auth(iksid * id, const char *pass, const char *sid);
+static int aji_receive_node_list(void *data, ikspak* pak);
+static void aji_init_event_distribution(struct aji_client *client);
+static iks* aji_create_pubsub_node(struct aji_client *client, const char *node_type,
+ const char *name, const char *collection_name);
+static iks* aji_build_node_config(iks *pubsub, const char *node_type,
+ const char *collection_name);
+static void aji_create_pubsub_collection(struct aji_client *client,
+ const char *collection_name);
+static void aji_create_pubsub_leaf(struct aji_client *client, const char *collection_name,
+ const char *leaf_name);
+static char *aji_cli_create_leafnode(struct ast_cli_entry *e, int cmd,
+ struct ast_cli_args *a);
+static void aji_create_affiliations(struct aji_client *client, const char *node);
+static iks* aji_pubsub_iq_create(struct aji_client *client, const char *type);
+static void aji_publish_device_state(struct aji_client *client, const char * device,
+ const char *device_state);
+static int aji_handle_pubsub_error(void *data, ikspak *pak);
+static int aji_handle_pubsub_event(void *data, ikspak *pak);
+static void aji_pubsub_subscribe(struct aji_client *client, const char *node);
+static void aji_delete_pubsub_node(struct aji_client *client, const char *node_name);
+static iks* aji_build_node_request(struct aji_client *client, const char *collection);
+static int aji_delete_node_list(void *data, ikspak* pak);
+static void aji_pubsub_purge_nodes(struct aji_client *client,
+ const char* collection_name);
+static void aji_publish_mwi(struct aji_client *client, const char *mailbox,
+ const char *context, const char *oldmsgs, const char *newmsgs);
+static void aji_devstate_cb(const struct ast_event *ast_event, void *data);
+static void aji_mwi_cb(const struct ast_event *ast_event, void *data);
+static iks* aji_build_publish_skeleton(struct aji_client *client, const char *node,
+ const char *event_type);
/* No transports in this version */
/*
static int aji_create_transport(char *label, struct aji_client *client);
@@ -339,6 +379,11 @@ static struct ast_cli_entry aji_cli[] = {
AST_CLI_DEFINE(aji_show_clients, "Show state of clients and components"),
AST_CLI_DEFINE(aji_show_buddies, "Show buddy lists of our clients"),
AST_CLI_DEFINE(aji_test, "Shows roster, but is generally used for mog's debugging."),
+ AST_CLI_DEFINE(aji_cli_create_collection, "Creates a PubSub node collection."),
+ AST_CLI_DEFINE(aji_cli_list_pubsub_nodes, "Lists PubSub nodes"),
+ AST_CLI_DEFINE(aji_cli_create_leafnode, "Creates a PubSub leaf node"),
+ AST_CLI_DEFINE(aji_cli_delete_pubsub_node, "Deletes a PubSub node"),
+ AST_CLI_DEFINE(aji_cli_purge_pubsub_nodes, "Purges PubSub nodes"),
};
static char *app_ajisend = "JabberSend";
@@ -349,12 +394,16 @@ static char *app_ajileave = "JabberLeave";
static struct aji_client_container clients;
static struct aji_capabilities *capabilities = NULL;
+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;
/*! \brief Global flags, initialized to default values */
static struct ast_flags globalflags = { AJI_AUTOREGISTER | AJI_AUTOACCEPT };
+/*! \brief PubSub flags, initialized to default values */
+static struct ast_flags pubsubflags = { AJI_AUTOREGISTER };
/*!
* \internal
* \brief Deletes the aji_client data structure.
@@ -430,17 +479,20 @@ static struct aji_version *aji_find_version(char *node, char *version, ikspak *p
list = capabilities;
- if (!node)
+ if (!node) {
node = pak->from->full;
- if (!version)
+ }
+ if (!version) {
version = "none supplied.";
- while(list) {
+ }
+ while (list) {
if (!strcasecmp(list->node, node)) {
res = list->versions;
while(res) {
- if (!strcasecmp(res->version, version))
- return res;
- res = res->next;
+ if (!strcasecmp(res->version, version)) {
+ return res;
+ }
+ res = res->next;
}
/* Specified version not found. Let's add it to
this node in our capabilities list */
@@ -489,14 +541,15 @@ static struct aji_version *aji_find_version(char *node, char *version, ikspak *p
* \internal
* \brief Find the aji_resource we want
* \param buddy aji_buddy A buddy
- * \param name
+ * \param name
* \return aji_resource object
*/
static struct aji_resource *aji_find_resource(struct aji_buddy *buddy, char *name)
{
struct aji_resource *res = NULL;
- if (!buddy || !name)
+ if (!buddy || !name) {
return res;
+ }
res = buddy->resources;
while (res) {
if (!strcasecmp(res->resource, name)) {
@@ -515,8 +568,9 @@ static struct aji_resource *aji_find_resource(struct aji_buddy *buddy, char *nam
*/
static int gtalk_yuck(iks *node)
{
- if (iks_find_with_attrib(node, "c", "node", "http://www.google.com/xmpp/client/caps"))
+ if (iks_find_with_attrib(node, "c", "node", "http://www.google.com/xmpp/client/caps")) {
return 1;
+ }
return 0;
}
@@ -577,8 +631,9 @@ static int aji_status_exec(struct ast_channel *chan, const char *data)
AST_APP_ARG(resource);
);
- if (deprecation_warning++ % 10 == 0)
+ if (deprecation_warning++ % 10 == 0) {
ast_log(LOG_WARNING, "JabberStatus is deprecated. Please use the JABBER_STATUS dialplan function in the future.\n");
+ }
if (!data) {
ast_log(LOG_ERROR, "Usage: JabberStatus(<sender>,<jid>[/<resource>],<varname>\n");
@@ -608,12 +663,14 @@ static int aji_status_exec(struct ast_channel *chan, const char *data)
return -1;
}
r = aji_find_resource(buddy, jid.resource);
- if (!r && buddy->resources)
+ if (!r && buddy->resources) {
r = buddy->resources;
- if (!r)
+ }
+ if (!r) {
ast_log(LOG_NOTICE, "Resource '%s' of buddy '%s' was not found\n", jid.resource, jid.screenname);
- else
+ } else {
stat = r->status;
+ }
snprintf(status, sizeof(status), "%d", stat);
pbx_builtin_setvar_helper(chan, args.variable, status);
return 0;
@@ -670,12 +727,14 @@ static int acf_jabberstatus_read(struct ast_channel *chan, const char *name, cha
return -1;
}
r = aji_find_resource(buddy, jid.resource);
- if (!r && buddy->resources)
+ if (!r && buddy->resources) {
r = buddy->resources;
- if (!r)
+ }
+ if (!r) {
ast_log(LOG_NOTICE, "Resource %s of buddy %s was not found.\n", jid.resource, jid.screenname);
- else
+ } else {
stat = r->status;
+ }
snprintf(buf, buflen, "%d", stat);
return 0;
}
@@ -723,7 +782,7 @@ static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, ch
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);
+ ast_log(LOG_WARNING, "%s requires arguments (account,jid[,timeout])\n", name);
return -1;
}
@@ -771,7 +830,7 @@ static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, ch
struct timeval wait;
int res;
- wait = ast_tvadd(start, ast_tv(timeout, 0));
+ wait = ast_tvadd(start, ast_tv(timeout, 0));
ts.tv_sec = wait.tv_sec;
ts.tv_nsec = wait.tv_usec * 1000;
@@ -782,7 +841,7 @@ static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, ch
if (res == ETIMEDOUT) {
ast_debug(3, "No message received from %s in %d seconds\n", args.jid, timeout);
break;
- };
+ }
AST_LIST_LOCK(&client->messages);
AST_LIST_TRAVERSE_SAFE_BEGIN(&client->messages, tmp, list) {
@@ -940,23 +999,23 @@ static int aji_join_exec(struct ast_channel *chan, const char *data)
ast_log(LOG_ERROR, "%s requires arguments (sender,jid[,nickname])\n", app_ajijoin);
return -1;
}
-
+
if (!(client = ast_aji_get_client(args.sender))) {
ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender);
return -1;
}
-
+
if (strchr(args.jid, '/')) {
ast_log(LOG_ERROR, "Invalid room name : resource must not be appended\n");
ASTOBJ_UNREF(client, aji_client_destroy);
return -1;
- }
-
+ }
+
if (!ast_strlen_zero(args.nick)) {
snprintf(nick, AJI_MAX_RESJIDLEN, "%s", args.nick);
} else {
if (client->component) {
- sprintf(nick, "asterisk");
+ sprintf(nick, "asterisk");
} else {
snprintf(nick, AJI_MAX_RESJIDLEN, "%s", client->jid->user);
}
@@ -967,11 +1026,11 @@ static int aji_join_exec(struct ast_channel *chan, const char *data)
} else {
ast_log(LOG_ERROR, "Problem with specified jid of '%s'\n", args.jid);
}
-
+
ASTOBJ_UNREF(client, aji_client_destroy);
return 0;
}
-
+
/*!
* \brief Application to leave a chat room
* \param chan ast_channel
@@ -989,29 +1048,29 @@ static int aji_leave_exec(struct ast_channel *chan, const char *data)
AST_APP_ARG(jid);
AST_APP_ARG(nick);
);
-
+
if (!data) {
ast_log(LOG_ERROR, "%s requires arguments (sender,jid[,nickname])\n", app_ajileave);
return -1;
}
s = ast_strdupa(data);
-
+
AST_STANDARD_APP_ARGS(args, s);
if (args.argc < 2 || args.argc > 3) {
ast_log(LOG_ERROR, "%s requires arguments (sender,jid[,nickname])\n", app_ajileave);
return -1;
}
-
+
if (!(client = ast_aji_get_client(args.sender))) {
ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender);
return -1;
}
-
+
if (strchr(args.jid, '/')) {
ast_log(LOG_ERROR, "Invalid room name, resource must not be appended\n");
ASTOBJ_UNREF(client, aji_client_destroy);
return -1;
- }
+ }
if (!ast_strlen_zero(args.nick)) {
snprintf(nick, AJI_MAX_RESJIDLEN, "%s", args.nick);
} else {
@@ -1021,10 +1080,10 @@ static int aji_leave_exec(struct ast_channel *chan, const char *data)
snprintf(nick, AJI_MAX_RESJIDLEN, "%s", client->jid->user);
}
}
-
+
if (!ast_strlen_zero(args.jid) && strchr(args.jid, '@')) {
ast_aji_leave_chat(client, args.jid, nick);
- }
+ }
ASTOBJ_UNREF(client, aji_client_destroy);
return 0;
}
@@ -1088,24 +1147,24 @@ static int aji_sendgroup_exec(struct ast_channel *chan, const char *data)
AST_APP_ARG(message);
AST_APP_ARG(nick);
);
-
+
if (!data) {
ast_log(LOG_ERROR, "%s requires arguments (sender,groupchatid,message[,nickname])\n", app_ajisendgroup);
return -1;
}
s = ast_strdupa(data);
-
+
AST_STANDARD_APP_ARGS(args, s);
if (args.argc < 3 || args.argc > 4) {
ast_log(LOG_ERROR, "%s requires arguments (sender,groupchatid,message[,nickname])\n", app_ajisendgroup);
return -1;
}
-
+
if (!(client = ast_aji_get_client(args.sender))) {
ast_log(LOG_ERROR, "Could not find sender connection: '%s'\n", args.sender);
return -1;
}
-
+
if (ast_strlen_zero(args.nick) || args.argc == 3) {
if (client->component) {
sprintf(nick, "asterisk");
@@ -1115,11 +1174,11 @@ static int aji_sendgroup_exec(struct ast_channel *chan, const char *data)
} else {
snprintf(nick, AJI_MAX_RESJIDLEN, "%s", args.nick);
}
-
+
if (strchr(args.groupchat, '@') && !ast_strlen_zero(args.message)) {
res = ast_aji_send_groupchat(client, nick, args.groupchat, args.message);
}
-
+
ASTOBJ_UNREF(client, aji_client_destroy);
if (res != IKS_OK) {
return -1;
@@ -1154,64 +1213,64 @@ static int aji_start_tls(struct aji_client *client)
int ret;
/* This is sent not encrypted */
- ret = iks_send_raw(client->p, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
- if (ret)
+ if ((ret = iks_send_raw(client->p, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"))) {
return ret;
+ }
client->stream_flags |= TRY_SECURE;
return IKS_OK;
}
-/*!
+/*!
* \internal
* \brief TLS handshake, OpenSSL initialization
* \param client the configured XMPP client we use to connect to a XMPP server
- * \return IKS_OK on success, IKS_NET_TLSFAIL on failure
+ * \return IKS_OK on success, IKS_NET_TLSFAIL on failure
*/
static int aji_tls_handshake(struct aji_client *client)
{
int ret;
int sock;
-
- ast_debug(1, "Starting TLS handshake\n");
+
+ ast_debug(1, "Starting TLS handshake\n");
/* Choose an SSL/TLS protocol version, create SSL_CTX */
client->ssl_method = SSLv3_method();
- client->ssl_context = SSL_CTX_new(client->ssl_method);
- if (!client->ssl_context)
+ if (!(client->ssl_context = SSL_CTX_new(client->ssl_method))) {
return IKS_NET_TLSFAIL;
+ }
/* Create new SSL session */
- client->ssl_session = SSL_new(client->ssl_context);
- if (!client->ssl_session)
+ if (!(client->ssl_session = SSL_new(client->ssl_context))) {
return IKS_NET_TLSFAIL;
+ }
/* Enforce TLS on our XMPP connection */
sock = iks_fd(client->p);
- ret = SSL_set_fd(client->ssl_session, sock);
- if (!ret)
+ if (!(ret = SSL_set_fd(client->ssl_session, sock))) {
return IKS_NET_TLSFAIL;
+ }
/* Perform SSL handshake */
- ret = SSL_connect(client->ssl_session);
- if (!ret)
+ if (!(ret = SSL_connect(client->ssl_session))) {
return IKS_NET_TLSFAIL;
+ }
client->stream_flags &= (~TRY_SECURE);
client->stream_flags |= SECURE;
/* Sent over the established TLS connection */
- ret = aji_send_header(client, client->jid->server);
- if (ret != IKS_OK)
+ if ((ret = aji_send_header(client, client->jid->server)) != IKS_OK) {
return IKS_NET_TLSFAIL;
+ }
- ast_debug(1, "TLS started with server\n");
+ ast_debug(1, "TLS started with server\n");
return IKS_OK;
}
#endif /* HAVE_OPENSSL */
-/*!
+/*!
* \internal
* \brief Secured or unsecured IO socket receiving function
* \param client the configured XMPP client we use to connect to a XMPP server
@@ -1232,11 +1291,12 @@ static int aji_io_recv(struct aji_client *client, char *buffer, size_t buf_len,
#ifdef HAVE_OPENSSL
if (aji_is_secure(client)) {
sock = SSL_get_fd(client->ssl_session);
- if (sock < 0)
- return -1;
+ if (sock < 0) {
+ return -1;
+ }
} else
#endif /* HAVE_OPENSSL */
- sock = iks_fd(client->p);
+ sock = iks_fd(client->p);
memset(&tv, 0, sizeof(struct timeval));
FD_ZERO(&fds);
@@ -1265,7 +1325,7 @@ static int aji_io_recv(struct aji_client *client, char *buffer, size_t buf_len,
return res;
}
-/*!
+/*!
* \internal
* \brief Tries to receive data from the Jabber server
* \param client the configured XMPP client we use to connect to a XMPP server
@@ -1296,7 +1356,7 @@ static int aji_recv (struct aji_client *client, int timeout)
buf[len] = '\0';
/* our iksemel parser won't work as expected if we feed
- it with XML packets that contain multiple whitespace
+ it with XML packets that contain multiple whitespace
characters between tags */
while (pos < len) {
c = buf[pos];
@@ -1314,11 +1374,11 @@ static int aji_recv (struct aji_client *client, int timeout)
pos = 0;
newbufpos = 0;
- /* Log the message here, because iksemel's logHook is
+ /* Log the message here, because iksemel's logHook is
unaccessible */
aji_log_hook(client, buf, len, 1);
- /* let iksemel deal with the string length,
+ /* let iksemel deal with the string length,
and reset our buffer */
ret = iks_parse(client->p, newbuf, 0, 0);
memset(newbuf, 0, sizeof(newbuf));
@@ -1337,12 +1397,12 @@ static int aji_recv (struct aji_client *client, int timeout)
if (ret != IKS_OK) {
return ret;
}
- ast_debug(3, "XML parsing successful\n");
+ ast_debug(3, "XML parsing successful\n");
}
return IKS_OK;
}
-/*!
+/*!
* \internal
* \brief Sends XMPP header to the server
* \param client the configured XMPP client we use to connect to a XMPP server
@@ -1369,7 +1429,7 @@ static int aji_send_header(struct aji_client *client, const char *to)
return IKS_OK;
}
-/*!
+/*!
* \brief Wraps raw sending
* \param client the configured XMPP client we use to connect to a XMPP server
* \param x the XMPP packet to send
@@ -1380,12 +1440,12 @@ int ast_aji_send(struct aji_client *client, iks *x)
return aji_send_raw(client, iks_string(iks_stack(x), x));
}
-/*!
+/*!
* \internal
* \brief Sends an XML string over an XMPP connection
* \param client the configured XMPP client we use to connect to a XMPP server
* \param xmlstr the XML string to send
- * The XML data is sent whether the connection is secured or not. In the
+ * The XML data is sent whether the connection is secured or not. In the
* latter case, we just call iks_send_raw().
* \return IKS_OK on success, any other value on failure
*/
@@ -1398,18 +1458,19 @@ static int aji_send_raw(struct aji_client *client, const char *xmlstr)
if (aji_is_secure(client)) {
ret = SSL_write(client->ssl_session, xmlstr, len);
if (ret) {
- /* Log the message here, because iksemel's logHook is
+ /* Log the message here, because iksemel's logHook is
unaccessible */
aji_log_hook(client, xmlstr, len, 0);
return IKS_OK;
}
}
#endif
- /* If needed, data will be sent unencrypted, and logHook will
+ /* If needed, data will be sent unencrypted, and logHook will
be called inside iks_send_raw */
ret = iks_send_raw(client->p, xmlstr);
- if (ret != IKS_OK)
- return ret;
+ if (ret != IKS_OK) {
+ return ret;
+ }
return IKS_OK;
}
@@ -1426,19 +1487,21 @@ static void aji_log_hook(void *data, const char *xmpp, size_t size, int is_incom
{
struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
- if (!ast_strlen_zero(xmpp))
+ if (!ast_strlen_zero(xmpp)) {
manager_event(EVENT_FLAG_USER, "JabberEvent", "Account: %s\r\nPacket: %s\r\n", client->name, xmpp);
+ }
if (client->debug) {
- if (is_incoming)
+ if (is_incoming) {
ast_verbose("\nJABBER: %s INCOMING: %s\n", client->name, xmpp);
- else {
+ } else {
if (strlen(xmpp) == 1) {
if (option_debug > 2 && xmpp[0] == ' ') {
ast_verbose("\nJABBER: Keep alive packet\n");
}
- } else
+ } else {
ast_verbose("\nJABBER: %s OUTGOING: %s\n", client->name, xmpp);
+ }
}
}
@@ -1551,8 +1614,9 @@ static int aji_act_hook(void *data, int type, iks *node)
ast_aji_increment_mid(client->mid);
ast_aji_send(client, auth);
iks_delete(auth);
- } else
+ } else {
ast_log(LOG_ERROR, "Out of memory.\n");
+ }
}
break;
@@ -1567,8 +1631,9 @@ static int aji_act_hook(void *data, int type, iks *node)
if (!strcmp("stream:features", iks_name(node))) {
features = iks_stream_features(node);
if (client->usesasl) {
- if (client->usetls && !aji_is_secure(client))
+ if (client->usetls && !aji_is_secure(client)) {
break;
+ }
if (client->authorized) {
if (features & IKS_STREAM_BIND) {
iks_filter_add_rule(client->f, aji_client_connect, client, IKS_RULE_TYPE, IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_DONE);
@@ -1617,16 +1682,16 @@ static int aji_act_hook(void *data, int type, iks *node)
aji_send_header(client, client->jid->server);
}
break;
- case IKS_NODE_ERROR:
- ast_log(LOG_ERROR, "JABBER: Node Error\n");
- ASTOBJ_UNREF(client, aji_client_destroy);
- return IKS_HOOK;
- break;
- case IKS_NODE_STOP:
- ast_log(LOG_WARNING, "JABBER: Disconnected\n");
- ASTOBJ_UNREF(client, aji_client_destroy);
- return IKS_HOOK;
- break;
+ case IKS_NODE_ERROR:
+ ast_log(LOG_ERROR, "JABBER: Node Error\n");
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+ break;
+ case IKS_NODE_STOP:
+ ast_log(LOG_WARNING, "JABBER: Disconnected\n");
+ ASTOBJ_UNREF(client, aji_client_destroy);
+ return IKS_HOOK;
+ break;
}
} else if (client->state != AJI_CONNECTED && client->component) {
switch (type) {
@@ -1690,7 +1755,7 @@ static int aji_act_hook(void *data, int type, iks *node)
ast_debug(1, "JABBER: I don't know anything about paktype '%d'\n", pak->type);
break;
}
-
+
iks_filter_packet(client->f, pak);
if (node)
@@ -1729,17 +1794,16 @@ static int aji_register_approve_handler(void *data, ikspak *pak)
iks_insert_attrib(presence, "type", "subscribe");
iks_insert_attrib(x, "xmlns", "vcard-temp:x:update");
iks_insert_node(presence, x);
- ast_aji_send(client, presence);
+ ast_aji_send(client, presence);
}
} else {
ast_log(LOG_ERROR, "Out of memory.\n");
}
-
iks_delete(iq);
iks_delete(presence);
iks_delete(x);
-
+
ASTOBJ_UNREF(client, aji_client_destroy);
return IKS_FILTER_EAT;
}
@@ -1753,7 +1817,7 @@ static int aji_register_approve_handler(void *data, ikspak *pak)
static int aji_register_query_handler(void *data, ikspak *pak)
{
struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
- struct aji_buddy *buddy = NULL;
+ struct aji_buddy *buddy = NULL;
char *node = NULL;
iks *iq = NULL, *query = NULL;
@@ -1787,7 +1851,7 @@ static int aji_register_query_handler(void *data, ikspak *pak)
iks_delete(error);
iks_delete(notacceptable);
- } else if (!(node = iks_find_attrib(pak->query, "node"))) {
+ } else if (!(node = iks_find_attrib(pak->query, "node"))) {
iks *instructions = NULL;
char *explain = "Welcome to Asterisk - the Open Source PBX.\n";
iq = iks_new("iq");
@@ -1819,7 +1883,7 @@ static int aji_register_query_handler(void *data, ikspak *pak)
* \internal
* \brief Handles stuff
* \param data void
- * \param pak ikspak
+ * \param pak ikspak
* \return IKS_FILTER_EAT.
*/
static int aji_ditems_handler(void *data, ikspak *pak)
@@ -1934,8 +1998,9 @@ static int aji_client_info_handler(void *data, ikspak *pak)
}
if (iks_find_with_attrib(pak->query, "feature", "var", "http://www.google.com/xmpp/protocol/voice/v1")) {
resource->cap->jingle = 1;
- } else
+ } else {
resource->cap->jingle = 0;
+ }
} else if (pak->subtype == IKS_TYPE_GET) {
iks *iq, *disco, *ident, *google, *query;
iq = iks_new("iq");
@@ -1959,8 +2024,9 @@ static int aji_client_info_handler(void *data, ikspak *pak)
iks_insert_node(query, google);
iks_insert_node(query, disco);
ast_aji_send(client, iq);
- } else
+ } else {
ast_log(LOG_ERROR, "Out of Memory.\n");
+ }
iks_delete(iq);
iks_delete(query);
@@ -1995,14 +2061,15 @@ static int aji_dinfo_handler(void *data, ikspak *pak)
}
if (pak->subtype == IKS_TYPE_RESULT) {
if (!resource) {
- ast_log(LOG_NOTICE,"JABBER: Received client info from %s when not requested.\n", pak->from->full);
+ ast_log(LOG_NOTICE, "JABBER: Received client info from %s when not requested.\n", pak->from->full);
ASTOBJ_UNREF(client, aji_client_destroy);
return IKS_FILTER_EAT;
}
if (iks_find_with_attrib(pak->query, "feature", "var", "http://www.google.com/xmpp/protocol/voice/v1")) {
resource->cap->jingle = 1;
- } else
+ } else {
resource->cap->jingle = 0;
+ }
} else if (pak->subtype == IKS_TYPE_GET && !(node = iks_find_attrib(pak->query, "node"))) {
iks *iq, *query, *identity, *disco, *reg, *commands, *gateway, *version, *vcard, *search;
@@ -2016,7 +2083,6 @@ static int aji_dinfo_handler(void *data, ikspak *pak)
version = iks_new("feature");
vcard = iks_new("feature");
search = iks_new("feature");
-
if (iq && query && identity && disco && reg && commands && gateway && version && vcard && search && client) {
iks_insert_attrib(iq, "from", client->user);
iks_insert_attrib(iq, "to", pak->from->full);
@@ -2058,7 +2124,6 @@ static int aji_dinfo_handler(void *data, ikspak *pak)
iks_delete(version);
iks_delete(vcard);
iks_delete(search);
-
} else if (pak->subtype == IKS_TYPE_GET && !strcasecmp(node, "http://jabber.org/protocol/commands")) {
iks *iq, *query, *confirm;
iq = iks_new("iq");
@@ -2212,27 +2277,31 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak)
status = (pak->show) ? pak->show : 6;
priority = atoi((iks_find_cdata(pak->x, "priority")) ? iks_find_cdata(pak->x, "priority") : "0");
tmp = buddy->resources;
- descrip = ast_strdup(iks_find_cdata(pak->x,"status"));
+ descrip = ast_strdup(iks_find_cdata(pak->x, "status"));
while (tmp && pak->from->resource) {
if (!strcasecmp(tmp->resource, pak->from->resource)) {
tmp->status = status;
- if (tmp->description) ast_free(tmp->description);
+ if (tmp->description) {
+ ast_free(tmp->description);
+ }
tmp->description = descrip;
found = tmp;
if (status == 6) { /* Sign off Destroy resource */
if (last && found->next) {
last->next = found->next;
} else if (!last) {
- if (found->next)
+ if (found->next) {
buddy->resources = found->next;
- else
+ } else {
buddy->resources = NULL;
+ }
} else if (!found->next) {
- if (last)
+ if (last) {
last->next = NULL;
- else
+ } else {
buddy->resources = NULL;
+ }
}
ast_free(found);
found = NULL;
@@ -2241,33 +2310,38 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak)
/* resource list is sorted by descending priority */
if (tmp->priority != priority) {
found->priority = priority;
- if (!last && !found->next)
+ if (!last && !found->next) {
/* resource was found to be unique,
leave loop */
break;
+ }
/* search for resource in our list
and take it out for the moment */
- if (last)
+ if (last) {
last->next = found->next;
- else
+ } else {
buddy->resources = found->next;
+ }
last = NULL;
tmp = buddy->resources;
- if (!buddy->resources)
+ if (!buddy->resources) {
buddy->resources = found;
+ }
/* priority processing */
while (tmp) {
- /* insert resource back according to
+ /* insert resource back according to
its priority value */
if (found->priority > tmp->priority) {
- if (last)
+ if (last) {
/* insert within list */
last->next = found;
+ }
found->next = tmp;
- if (!last)
+ if (!last) {
/* insert on top */
buddy->resources = found;
+ }
break;
}
if (!tmp->next) {
@@ -2303,11 +2377,13 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak)
tmp = buddy->resources;
while (tmp) {
if (found->priority > tmp->priority) {
- if (last)
+ if (last) {
last->next = found;
+ }
found->next = tmp;
- if (!last)
+ if (!last) {
buddy->resources = found;
+ }
break;
}
if (!tmp->next) {
@@ -2317,10 +2393,11 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak)
last = tmp;
tmp = tmp->next;
}
- if (!tmp)
+ if (!tmp) {
buddy->resources = found;
+ }
}
-
+
ASTOBJ_UNLOCK(buddy);
ASTOBJ_UNREF(buddy, aji_buddy_destroy);
@@ -2336,28 +2413,27 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak)
/* retrieve capabilites of the new resource */
if (status != 6 && found && !found->cap) {
found->cap = aji_find_version(node, ver, pak);
- if (gtalk_yuck(pak->x)) /* gtalk should do discover */
+ if (gtalk_yuck(pak->x)) { /* gtalk should do discover */
found->cap->jingle = 1;
- if (found->cap->jingle && option_debug > 4) {
- ast_debug(1,"Special case for google till they support discover.\n");
}
- else {
+ if (found->cap->jingle && option_debug > 4) {
+ ast_debug(1, "Special case for google till they support discover.\n");
+ } else {
iks *iq, *query;
iq = iks_new("iq");
query = iks_new("query");
- if (query && iq) {
+ if (query && iq) {
iks_insert_attrib(iq, "type", "get");
iks_insert_attrib(iq, "to", pak->from->full);
- iks_insert_attrib(iq,"from", client->jid->full);
+ iks_insert_attrib(iq, "from", client->jid->full);
iks_insert_attrib(iq, "id", client->mid);
ast_aji_increment_mid(client->mid);
iks_insert_attrib(query, "xmlns", "http://jabber.org/protocol/disco#info");
iks_insert_node(iq, query);
ast_aji_send(client, iq);
-
- } else
+ } else {
ast_log(LOG_ERROR, "Out of memory.\n");
-
+ }
iks_delete(query);
iks_delete(iq);
}
@@ -2395,17 +2471,17 @@ static void aji_handle_presence(struct aji_client *client, ikspak *pak)
ast_debug(3, "JABBER: Kinky! how did that happen %i\n", pak->show);
}
- if (found) {
- manager_event(EVENT_FLAG_USER, "JabberStatus",
+ if (found) {
+ 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, found->resource, found->status,
found->priority, found->description);
- } else {
- manager_event(EVENT_FLAG_USER, "JabberStatus",
+ } else {
+ 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);
- }
+ client->name, pak->from->partial, pak->show ? pak->show : IKS_SHOW_UNAVAILABLE);
+ }
}
/*!
@@ -2420,7 +2496,7 @@ static void aji_handle_subscribe(struct aji_client *client, ikspak *pak)
iks *presence = NULL, *status = NULL;
struct aji_buddy* buddy = NULL;
- switch (pak->subtype) {
+ switch (pak->subtype) {
case IKS_TYPE_SUBSCRIBE:
if (ast_test_flag(&client->flags, AJI_AUTOACCEPT)) {
presence = iks_new("presence");
@@ -2434,8 +2510,9 @@ static void aji_handle_subscribe(struct aji_client *client, ikspak *pak)
iks_insert_cdata(status, "Asterisk has approved subscription", 0);
iks_insert_node(presence, status);
ast_aji_send(client, presence);
- } else
+ } else {
ast_log(LOG_ERROR, "Unable to allocate nodes\n");
+ }
iks_delete(presence);
iks_delete(status);
@@ -2501,12 +2578,12 @@ static int aji_send_raw_chat(struct aji_client *client, int groupchat, const cha
} else {
snprintf(from, AJI_MAX_JIDLEN, "%s", client->jid->full);
}
-
+
if (client->state != AJI_CONNECTED) {
ast_log(LOG_WARNING, "JABBER: Not connected can't send\n");
return -1;
- }
-
+ }
+
message_packet = iks_make_msg(groupchat ? IKS_TYPE_GROUPCHAT : IKS_TYPE_CHAT, address, message);
if (!message_packet) {
ast_log(LOG_ERROR, "Out of memory.\n");
@@ -2515,7 +2592,7 @@ static int aji_send_raw_chat(struct aji_client *client, int groupchat, const cha
iks_insert_attrib(message_packet, "from", from);
res = ast_aji_send(client, message_packet);
iks_delete(message_packet);
-
+
return res;
}
@@ -2539,8 +2616,9 @@ int ast_aji_create_chat(struct aji_client *client, char *room, char *server, cha
iks_insert_attrib(iq, "id", client->mid);
ast_aji_increment_mid(client->mid);
ast_aji_send(client, iq);
- } else
+ } else {
ast_log(LOG_ERROR, "Out of memory.\n");
+ }
iks_delete(iq);
@@ -2573,7 +2651,7 @@ int ast_aji_leave_chat(struct aji_client *client, char *room, char *nick)
/*!
* \brief invite to a chatroom.
* \param client the configured XMPP client we use to connect to a XMPP server
- * \param user
+ * \param user
* \param room
* \param message
* \return res.
@@ -2596,13 +2674,14 @@ int ast_aji_invite_chat(struct aji_client *client, char *user, char *room, char
iks_insert_node(invite, body);
iks_insert_node(invite, namespace);
res = ast_aji_send(client, invite);
- } else
+ } else {
ast_log(LOG_ERROR, "Out of memory.\n");
+ }
iks_delete(body);
iks_delete(namespace);
iks_delete(invite);
-
+
return res;
}
@@ -2617,7 +2696,7 @@ static void *aji_recv_loop(void *data)
struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
int res = IKS_HOOK;
- while(res != IKS_OK) {
+ while (res != IKS_OK) {
ast_debug(3, "JABBER: Connecting.\n");
res = aji_reconnect(client);
sleep(4);
@@ -2625,7 +2704,7 @@ static void *aji_recv_loop(void *data)
do {
if (res == IKS_NET_RWERR || client->timeout == 0) {
- while(res != IKS_OK) {
+ while (res != IKS_OK) {
ast_debug(3, "JABBER: reconnecting.\n");
res = aji_reconnect(client);
sleep(4);
@@ -2633,7 +2712,7 @@ static void *aji_recv_loop(void *data)
}
res = aji_recv(client, 1);
-
+
if (client->state == AJI_DISCONNECTING) {
ast_debug(2, "Ending our Jabber client's thread due to a disconnect\n");
pthread_exit(NULL);
@@ -2803,14 +2882,14 @@ static void aji_pruneregister(struct aji_client *client)
res = ast_aji_send(client, iks_make_s10n(IKS_TYPE_UNSUBSCRIBED, iterator->name,
"GoodBye. You are no longer in the Asterisk config file so I am removing"
" your access to my presence.\n"));
- iks_insert_attrib(removeiq, "from", client->jid->full);
- iks_insert_attrib(removeiq, "type", "set");
+ iks_insert_attrib(removeiq, "from", client->jid->full);
+ iks_insert_attrib(removeiq, "type", "set");
iks_insert_attrib(removequery, "xmlns", "jabber:iq:roster");
iks_insert_attrib(removeitem, "jid", iterator->name);
iks_insert_attrib(removeitem, "subscription", "remove");
res = ast_aji_send(client, removeiq);
} else if (ast_test_flag(&iterator->flags, AJI_AUTOREGISTER)) {
- res = ast_aji_send(client, iks_make_s10n(IKS_TYPE_SUBSCRIBE, iterator->name,
+ res = ast_aji_send(client, iks_make_s10n(IKS_TYPE_SUBSCRIBE, iterator->name,
"Greetings! I am the Asterisk Open Source PBX and I want to subscribe to your presence\n"));
ast_clear_flag(&iterator->flags, AJI_AUTOREGISTER);
}
@@ -2822,7 +2901,7 @@ static void aji_pruneregister(struct aji_client *client)
iks_delete(removequery);
iks_delete(removeitem);
iks_delete(send);
-
+
ASTOBJ_CONTAINER_PRUNE_MARKED(&client->buddies, aji_buddy_destroy);
}
@@ -2839,7 +2918,7 @@ static int aji_filter_roster(void *data, ikspak *pak)
int flag = 0;
iks *x = NULL;
struct aji_buddy *buddy;
-
+
client->state = AJI_CONNECTED;
ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
ASTOBJ_RDLOCK(iterator);
@@ -2854,10 +2933,11 @@ static int aji_filter_roster(void *data, ikspak *pak)
}
x = iks_next(x);
}
- if (!flag)
+ if (!flag) {
ast_copy_flags(&iterator->flags, &client->flags, AJI_AUTOREGISTER);
+ }
iks_delete(x);
-
+
ASTOBJ_UNLOCK(iterator);
});
@@ -2877,7 +2957,7 @@ static int aji_filter_roster(void *data, ikspak *pak)
x = iks_next(x);
continue;
}
-
+
buddy = ast_calloc(1, sizeof(*buddy));
if (!buddy) {
ast_log(LOG_WARNING, "Out of memory\n");
@@ -2921,13 +3001,16 @@ static int aji_reconnect(struct aji_client *client)
{
int res = 0;
- if (client->state)
+ if (client->state) {
client->state = AJI_DISCONNECTED;
- client->timeout=50;
- if (client->p)
+ }
+ client->timeout = 50;
+ if (client->p) {
iks_parser_reset(client->p);
- if (client->authorized)
+ }
+ if (client->authorized) {
client->authorized = 0;
+ }
res = aji_initialize(client);
@@ -2952,7 +3035,7 @@ static int aji_get_roster(struct aji_client *client)
}
iks_delete(roster);
-
+
return 1;
}
@@ -2974,11 +3057,16 @@ static int aji_client_connect(void *data, ikspak *pak)
client->state = AJI_CONNECTING;
client->jid = (iks_find_cdata(pak->query, "jid")) ? iks_id_new(client->stack, iks_find_cdata(pak->query, "jid")) : client->jid;
iks_filter_remove_hook(client->f, aji_client_connect);
- if (!client->component) /*client*/
+ if (!client->component) { /*client*/
aji_get_roster(client);
+ if (client->distribute_events) {
+ aji_init_event_distribution(client);
+ }
+ }
}
- } else
+ } else {
ast_log(LOG_ERROR, "Out of memory.\n");
+ }
ASTOBJ_UNREF(client, aji_client_destroy);
return res;
@@ -2994,7 +3082,7 @@ static int aji_initialize(struct aji_client *client)
{
int connected = IKS_NET_NOCONN;
-#ifdef HAVE_OPENSSL
+#ifdef HAVE_OPENSSL
/* reset stream flags */
client->stream_flags = 0;
#endif
@@ -3004,8 +3092,9 @@ static int aji_initialize(struct aji_client *client)
if (connected == IKS_NET_NOCONN) {
ast_log(LOG_ERROR, "JABBER ERROR: No Connection\n");
return IKS_HOOK;
- } else if (connected == IKS_NET_NODNS) {
- ast_log(LOG_ERROR, "JABBER ERROR: No DNS %s for client to %s\n", client->name, S_OR(client->serverhost, client->jid->server));
+ } else if (connected == IKS_NET_NODNS) {
+ ast_log(LOG_ERROR, "JABBER ERROR: No DNS %s for client to %s\n", client->name,
+ S_OR(client->serverhost, client->jid->server));
return IKS_HOOK;
}
@@ -3037,6 +3126,771 @@ int ast_aji_disconnect(struct aji_client *client)
}
/*!
+ * \brief Callback function for MWI events
+ * \param ast_event
+ * \param data void pointer to ast_client structure
+ * \return void
+ */
+static void aji_mwi_cb(const struct ast_event *ast_event, void *data)
+{
+ const char *mailbox;
+ const char *context;
+ char oldmsgs[10];
+ char 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_log(LOG_DEBUG, "Returning here\n");
+ return;
+ }
+
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ 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));
+ aji_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 aji_devstate_cb(const struct ast_event *ast_event, void *data)
+{
+ const char *device;
+ const char *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_log(LOG_DEBUG, "Returning here\n");
+ return;
+ }
+
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ 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));
+ aji_publish_device_state(client, device, device_state);
+}
+
+/*!
+ * \brief Initialize collections for event distribution
+ * \param client the configured XMPP client we use to connect to a XMPP server
+ * \return void
+ */
+static void aji_init_event_distribution(struct aji_client *client)
+{
+ if (!mwi_sub) {
+ mwi_sub = ast_event_subscribe(AST_EVENT_MWI, aji_mwi_cb, "aji_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,
+ aji_devstate_cb, "aji_devstate_subscription", client, AST_EVENT_IE_END);
+ ast_event_dump_cache(device_state_sub);
+ }
+
+ aji_pubsub_subscribe(client, "device_state");
+ aji_pubsub_subscribe(client, "message_waiting");
+ iks_filter_add_rule(client->f, aji_handle_pubsub_event, client, IKS_RULE_TYPE,
+ IKS_PAK_MESSAGE, IKS_RULE_FROM, client->pubsub_node, IKS_RULE_DONE);
+ iks_filter_add_rule(client->f, aji_handle_pubsub_error, client, IKS_RULE_TYPE,
+ IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_ERROR, IKS_RULE_DONE);
+
+}
+
+/*!
+ * \brief Callback for handling PubSub events
+ * \param data void pointer to aji_client structure
+ * \return IKS_FILTER_EAT
+ */
+static int aji_handle_pubsub_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_log(LOG_DEBUG, "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_log(LOG_DEBUG, "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;
+}
+
+/*!
+ * \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 aji_create_affiliations(struct aji_client *client, const char *node)
+{
+ int res = 0;
+ iks *modify_affiliates = aji_pubsub_iq_create(client, "set");
+ iks *pubsub, *affiliations, *affiliate;
+ 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);
+ ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
+ ASTOBJ_RDLOCK(iterator);
+ affiliate = iks_insert(affiliations, "affiliation");
+ iks_insert_attrib(affiliate, "jid", iterator->name);
+ iks_insert_attrib(affiliate, "affiliation", "owner");
+ ASTOBJ_UNLOCK(iterator);
+ });
+ res = ast_aji_send(client, modify_affiliates);
+ iks_delete(modify_affiliates);
+}
+
+/*!
+ * \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 aji_pubsub_subscribe(struct aji_client *client, const char *node)
+{
+ iks *request = aji_pubsub_iq_create(client, "set");
+ iks *pubsub, *subscribe;
+
+ 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(&globalflags, AJI_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_aji_send(client, request);
+ iks_delete(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* aji_build_publish_skeleton(struct aji_client *client, const char *node,
+ const char *event_type)
+{
+ iks *request = aji_pubsub_iq_create(client, "set");
+ iks *pubsub, *publish, *item;
+ pubsub = iks_insert(request, "pubsub");
+ iks_insert_attrib(pubsub, "xmlns", "http://jabber.org/protocol/pubsub");
+ publish = iks_insert(pubsub, "publish");
+ if (ast_test_flag(&globalflags, AJI_XEP0248)) {
+ iks_insert_attrib(publish, "node", node);
+ } else {
+ iks_insert_attrib(publish, "node", event_type);
+ }
+ item = iks_insert(publish, "item");
+ iks_insert_attrib(item, "id", node);
+ return item;
+
+}
+
+/*!
+ * \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 aji_publish_device_state(struct aji_client *client, const char *device,
+ const char *device_state)
+{
+ iks *request = aji_build_publish_skeleton(client, device, "device_state");
+ iks *state;
+ char eid_str[20];
+ if (ast_test_flag(&pubsubflags, AJI_PUBSUB_AUTOCREATE)) {
+ if (ast_test_flag(&pubsubflags, AJI_XEP0248)) {
+ aji_create_pubsub_node(client, "leaf", device, "device_state");
+ } else {
+ aji_create_pubsub_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_aji_send(client, iks_root(request));
+ iks_delete(request);
+}
+
+/*!
+ * \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 aji_publish_mwi(struct aji_client *client, const char *mailbox,
+ const char *context, const char *oldmsgs, const char *newmsgs)
+{
+ char full_mailbox[AST_MAX_EXTENSION+AST_MAX_CONTEXT];
+ char eid_str[20];
+ iks *mailbox_node;
+ snprintf(full_mailbox, sizeof(full_mailbox), "%s@%s", mailbox, context);
+ iks *request = aji_build_publish_skeleton(client, full_mailbox, "message_waiting");
+ 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_aji_send(client, iks_root(request));
+ iks_delete(request);
+}
+
+/*!
+ * \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* aji_pubsub_iq_create(struct aji_client *client, const char *type)
+{
+ iks *request = iks_new("iq");
+
+ iks_insert_attrib(request, "to", client->pubsub_node);
+ iks_insert_attrib(request, "from", client->jid->full);
+ iks_insert_attrib(request, "type", type);
+ ast_aji_increment_mid(client->mid);
+ iks_insert_attrib(request, "id", client->mid);
+ return request;
+}
+
+static int aji_handle_pubsub_error(void *data, ikspak *pak)
+{
+ char *node_name;
+ char *error;
+ int error_num;
+ iks *orig_request;
+ iks *orig_pubsub = iks_find(pak->x, "pubsub");
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ 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")) {
+ if (ast_test_flag(&pubsubflags, AJI_XEP0248)) {
+ if (iks_find(iks_find(orig_request, "item"), "state")) {
+ aji_create_pubsub_leaf(client, "device_state", node_name);
+ } else if (iks_find(iks_find(orig_request, "item"), "mailbox")) {
+ aji_create_pubsub_leaf(client, "message_waiting", node_name);
+ }
+ } else {
+ aji_create_pubsub_node(client, NULL, node_name, NULL);
+ }
+ iks *request = aji_pubsub_iq_create(client, "set");
+ iks_insert_node(request, orig_pubsub);
+ ast_aji_send(client, request);
+ iks_delete(request);
+ return IKS_FILTER_EAT;
+ } else if (!strcasecmp(iks_name(orig_request), "subscribe")) {
+ if (ast_test_flag(&pubsubflags, AJI_XEP0248)) {
+ aji_create_pubsub_collection(client, node_name);
+ } else {
+ aji_create_pubsub_node(client, NULL, node_name, NULL);
+ }
+ }
+
+ 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 aji_request_pubsub_nodes(struct aji_client *client, const char *collection)
+{
+ int res = 0;
+ iks *request = aji_build_node_request(client, collection);
+
+ iks_filter_add_rule(client->f, aji_receive_node_list, client, IKS_RULE_TYPE,
+ IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, client->mid,
+ IKS_RULE_DONE);
+ res = ast_aji_send(client, request);
+ iks_delete(request);
+
+}
+
+/*!
+ * \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* aji_build_node_request(struct aji_client *client, const char *collection)
+{
+ iks *request = aji_pubsub_iq_create(client, "get");
+ iks *query;
+ 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 aji_client structure
+ * \param pak response from pubsub diso#items query
+ * \return IKS_FILTER_EAT
+ */
+static int aji_receive_node_list(void *data, ikspak* pak)
+{
+
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ iks *item;
+ if (iks_has_children(pak->query)) {
+ item = iks_first_tag(pak->query);
+ ast_verbose("Connection: %s\nNode name: %s\n", 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"));
+ }
+ }
+ iks_delete(item);
+ return IKS_FILTER_EAT;
+}
+
+
+/*!
+ * \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 *aji_cli_list_pubsub_nodes(struct ast_cli_entry *e, int cmd, struct
+ast_cli_args *a)
+{
+ struct aji_client *client;
+ const char *name = "asterisk";
+ const char *collection = NULL;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "jabber list nodes";
+ e->usage =
+ "Usage: jabber list nodes [name]\n"
+ " Lists nodes on PubSub server\n"
+ " as configured in jabber.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 (!(client = ASTOBJ_CONTAINER_FIND(&clients, name))) {
+ ast_cli(a->fd, "Unable to find client '%s'!\n", name);
+ return CLI_FAILURE;
+ }
+
+ ast_cli(a->fd, "Listing pubsub nodes.\n");
+ aji_request_pubsub_nodes(client, collection);
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \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 *aji_cli_purge_pubsub_nodes(struct ast_cli_entry *e, int cmd, struct
+ ast_cli_args *a)
+{
+ struct aji_client *client;
+ const char *name = "asterisk";
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "jabber purge nodes";
+ e->usage =
+ "Usage: jabber purge nodes [name]\n"
+ " Purges nodes on PubSub server\n"
+ " as configured in jabber.conf.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc > 5) {
+ return CLI_SHOWUSAGE;
+ } else if (a->argc == 5) {
+ name = a->argv[3];
+ }
+ if (!(client = ASTOBJ_CONTAINER_FIND(&clients, name))) {
+ ast_cli(a->fd, "Unable to find client '%s'!\n", name);
+ return CLI_FAILURE;
+ }
+ if (ast_test_flag(&pubsubflags, AJI_XEP0248)) {
+ aji_pubsub_purge_nodes(client, a->argv[4]);
+ } else {
+ aji_delete_pubsub_node(client, a->argv[4]);
+ }
+ return CLI_SUCCESS;
+}
+
+static void aji_pubsub_purge_nodes(struct aji_client *client, const char* collection_name)
+{
+ int res = 0;
+ iks *request = aji_build_node_request(client, collection_name);
+ ast_aji_send(client, request);
+ iks_filter_add_rule(client->f, aji_delete_node_list, client, IKS_RULE_TYPE,
+ IKS_PAK_IQ, IKS_RULE_SUBTYPE, IKS_TYPE_RESULT, IKS_RULE_ID, client->mid,
+ IKS_RULE_DONE);
+ res = ast_aji_send(client, request);
+ iks_delete(request);
+}
+
+/*!
+ * \brief Delete pubsub item lists
+ * \param data pointer to aji_client structure
+ * \param pak response from pubsub diso#items query
+ * \return IKS_FILTER_EAT
+ */
+static int aji_delete_node_list(void *data, ikspak* pak)
+{
+
+ struct aji_client *client = ASTOBJ_REF((struct aji_client *) data);
+ iks *item;
+ 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))) {
+ aji_delete_pubsub_node(client, iks_find_attrib(item, "node"));
+ }
+ }
+ iks_delete(item);
+ return IKS_FILTER_EAT;
+}
+
+
+/*!
+ * \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 *aji_cli_delete_pubsub_node(struct ast_cli_entry *e, int cmd, struct
+ ast_cli_args *a)
+{
+ struct aji_client *client;
+ const char *name = "asterisk";
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "jabber delete node";
+ e->usage =
+ "Usage: jabber delete node [name]\n"
+ " Deletes a node on PubSub server\n"
+ " as configured in jabber.conf.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc > 5) {
+ return CLI_SHOWUSAGE;
+ } else if (a->argc == 5) {
+ name = a->argv[3];
+ }
+ if (!(client = ASTOBJ_CONTAINER_FIND(&clients, name))) {
+ ast_cli(a->fd, "Unable to find client '%s'!\n", name);
+ return CLI_FAILURE;
+ }
+ aji_delete_pubsub_node(client, a->argv[4]);
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \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 aji_delete_pubsub_node(struct aji_client *client, const char *node_name)
+{
+ iks *request = aji_pubsub_iq_create(client, "set");
+ iks *pubsub, *delete;
+ 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_aji_send(client, 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 aji_create_pubsub_collection(struct aji_client *client, const char
+*collection_name)
+{
+ aji_create_pubsub_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 aji_create_pubsub_leaf(struct aji_client *client, const char *collection_name,
+const char *leaf_name)
+{
+ aji_create_pubsub_node(client, "leaf", leaf_name, collection_name);
+}
+
+/*!
+ * \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 iks*
+ */
+static iks* aji_create_pubsub_node(struct aji_client *client, const char *node_type, const
+ char *name, const char *collection_name)
+{
+ int res = 0;
+ iks *node = aji_pubsub_iq_create(client, "set");
+ iks *pubsub, *create, *configure;
+ 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);
+ configure = aji_build_node_config(pubsub, node_type, collection_name);
+ res = ast_aji_send(client, node);
+ aji_create_affiliations(client, name);
+ iks_delete(node);
+ return 0;
+}
+
+
+
+static iks* aji_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 Method to expose PubSub collection node creation via CLI.
+ * \return char *.
+ */
+static char *aji_cli_create_collection(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct aji_client *client;
+ const char *name = "asterisk";
+ const char *collection_name = "test_collection";
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "jabber create collection";
+ e->usage =
+ "Usage: jabber create collection [name] [node name]\n"
+ " Creates a PubSub collection node using the account\n"
+ " as configured in jabber.conf.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc > 5) {
+ return CLI_SHOWUSAGE;
+ } else if (a->argc == 5) {
+ name = a->argv[3];
+ collection_name = a->argv[4];
+ }
+
+ if (!(client = ASTOBJ_CONTAINER_FIND(&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");
+ aji_create_pubsub_collection(client, collection_name);
+ return CLI_SUCCESS;
+}
+
+/*!
+ * \brief Method to expose PubSub leaf node creation via CLI.
+ * \return char *.
+ */
+static char *aji_cli_create_leafnode(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct aji_client *client;
+ const char *name = "asterisk";
+ const char *collection_name = "test_collection";
+ const char *leaf_name = "test_leaf";
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "jabber create leaf";
+ e->usage =
+ "Usage: jabber create leaf [name] [collection_name] [node name]\n"
+ " Creates a PubSub leaf node using the account\n"
+ " as configured in jabber.conf.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc > 6) {
+ return CLI_SHOWUSAGE;
+ } else if (a->argc == 6) {
+ name = a->argv[3];
+ collection_name = a->argv[4];
+ leaf_name = a->argv[5];
+ }
+
+ if (!(client = ASTOBJ_CONTAINER_FIND(&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");
+ aji_create_pubsub_leaf(client, collection_name, leaf_name);
+ return CLI_SUCCESS;
+}
+
+
+
+/*!
* \internal
* \brief set presence of client.
* \param client the configured XMPP client we use to connect to a XMPP server
@@ -3055,10 +3909,12 @@ static void aji_set_presence(struct aji_client *client, char *to, char *from, in
char priorityS[10];
if (presence && cnode && client && priority) {
- if (to)
+ if (to) {
iks_insert_attrib(presence, "to", to);
- if (from)
+ }
+ if (from) {
iks_insert_attrib(presence, "from", from);
+ }
snprintf(priorityS, sizeof(priorityS), "%d", client->priority);
iks_insert_cdata(priority, priorityS, strlen(priorityS));
iks_insert_node(presence, priority);
@@ -3068,8 +3924,9 @@ static void aji_set_presence(struct aji_client *client, char *to, char *from, in
iks_insert_attrib(cnode, "xmlns", "http://jabber.org/protocol/caps");
iks_insert_node(presence, cnode);
res = ast_aji_send(client, presence);
- } else
+ } else {
ast_log(LOG_ERROR, "Out of memory.\n");
+ }
iks_delete(cnode);
iks_delete(presence);
@@ -3092,10 +3949,10 @@ static int aji_set_group_presence(struct aji_client *client, char *room, int lev
iks *presence = NULL, *x = NULL;
char from[AJI_MAX_JIDLEN];
char roomid[AJI_MAX_JIDLEN];
-
+
presence = iks_make_pres(level, NULL);
x = iks_new("x");
-
+
if (client->component) {
snprintf(from, AJI_MAX_JIDLEN, "%s@%s/%s", nick, client->jid->full, nick);
snprintf(roomid, AJI_MAX_JIDLEN, "%s/%s", room, nick);
@@ -3103,19 +3960,19 @@ static int aji_set_group_presence(struct aji_client *client, char *room, int lev
snprintf(from, AJI_MAX_JIDLEN, "%s", client->jid->full);
snprintf(roomid, AJI_MAX_JIDLEN, "%s/%s", room, nick ? nick : client->jid->user);
}
-
+
if (!presence || !x || !client) {
ast_log(LOG_ERROR, "Out of memory.\n");
res = -1;
goto safeout;
- } else {
+ } else {
iks_insert_attrib(presence, "to", roomid);
iks_insert_attrib(presence, "from", from);
iks_insert_attrib(x, "xmlns", MUC_NS);
iks_insert_node(presence, x);
res = ast_aji_send(client, presence);
}
-
+
safeout:
iks_delete(presence);
iks_delete(x);
@@ -3140,12 +3997,13 @@ static char *aji_do_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
return NULL;
}
- if (a->argc != e->args)
+ if (a->argc != e->args) {
return CLI_SHOWUSAGE;
+ }
if (!strncasecmp(a->argv[e->args - 1], "on", 2)) {
ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
- ASTOBJ_RDLOCK(iterator);
+ ASTOBJ_RDLOCK(iterator);
iterator->debug = 1;
ASTOBJ_UNLOCK(iterator);
});
@@ -3153,7 +4011,7 @@ static char *aji_do_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_a
return CLI_SUCCESS;
} else if (!strncasecmp(a->argv[e->args - 1], "off", 3)) {
ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
- ASTOBJ_RDLOCK(iterator);
+ ASTOBJ_RDLOCK(iterator);
iterator->debug = 0;
ASTOBJ_UNLOCK(iterator);
});
@@ -3195,7 +4053,7 @@ static char *aji_show_clients(struct ast_cli_entry *e, int cmd, struct ast_cli_a
{
char *status;
int count = 0;
-
+
switch (cmd) {
case CLI_INIT:
e->command = "jabber show connected";
@@ -3255,22 +4113,22 @@ static char *aji_show_buddies(struct ast_cli_entry *e, int cmd, struct ast_cli_a
ast_cli(a->fd, "Jabber buddy lists\n");
ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
- ast_cli(a->fd,"Client: %s\n", iterator->user);
+ ast_cli(a->fd, "Client: %s\n", iterator->user);
client = iterator;
ASTOBJ_CONTAINER_TRAVERSE(&client->buddies, 1, {
ASTOBJ_RDLOCK(iterator);
- ast_cli(a->fd,"\tBuddy:\t%s\n", iterator->name);
+ ast_cli(a->fd, "\tBuddy:\t%s\n", iterator->name);
if (!iterator->resources)
- ast_cli(a->fd,"\t\tResource: None\n");
+ ast_cli(a->fd, "\t\tResource: None\n");
for (resource = iterator->resources; resource; resource = resource->next) {
- ast_cli(a->fd,"\t\tResource: %s\n", resource->resource);
+ ast_cli(a->fd, "\t\tResource: %s\n", resource->resource);
if (resource->cap) {
- ast_cli(a->fd,"\t\t\tnode: %s\n", resource->cap->parent->node);
- ast_cli(a->fd,"\t\t\tversion: %s\n", resource->cap->version);
- ast_cli(a->fd,"\t\t\tJingle capable: %s\n", resource->cap->jingle ? "yes" : "no");
+ ast_cli(a->fd, "\t\t\tnode: %s\n", resource->cap->parent->node);
+ ast_cli(a->fd, "\t\t\tversion: %s\n", resource->cap->version);
+ ast_cli(a->fd, "\t\t\tJingle capable: %s\n", resource->cap->jingle ? "yes" : "no");
}
- ast_cli(a->fd,"\t\tStatus: %d\n", resource->status);
- ast_cli(a->fd,"\t\tPriority: %d\n", resource->priority);
+ ast_cli(a->fd, "\t\tStatus: %d\n", resource->status);
+ ast_cli(a->fd, "\t\tPriority: %d\n", resource->priority);
}
ASTOBJ_UNLOCK(iterator);
});
@@ -3303,10 +4161,11 @@ static char *aji_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
return NULL;
}
- if (a->argc > 3)
+ if (a->argc > 3) {
return CLI_SHOWUSAGE;
- else if (a->argc == 3)
+ } else if (a->argc == 3) {
name = a->argv[2];
+ }
if (!(client = ASTOBJ_CONTAINER_FIND(&clients, name))) {
ast_cli(a->fd, "Unable to find client '%s'!\n", name);
@@ -3327,7 +4186,7 @@ static char *aji_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
}
ast_verbose(" Priority: %d\n", resource->priority);
ast_verbose(" Status: %d\n", resource->status);
- ast_verbose(" Message: %s\n", S_OR(resource->description,""));
+ ast_verbose(" Message: %s\n", S_OR(resource->description, ""));
}
ASTOBJ_UNLOCK(iterator);
});
@@ -3347,7 +4206,7 @@ static char *aji_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
* \brief creates aji_client structure.
* \param label
* \param var ast_variable
- * \param debug
+ * \param debug
* \return 0.
*/
static int aji_create_client(char *label, struct ast_variable *var, int debug)
@@ -3356,7 +4215,7 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
struct aji_client *client = NULL;
int flag = 0;
- client = ASTOBJ_CONTAINER_FIND(&clients,label);
+ client = ASTOBJ_CONTAINER_FIND(&clients, label);
if (!client) {
flag = 1;
client = ast_calloc(1, sizeof(*client));
@@ -3385,6 +4244,7 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
client->keepalive = 1;
client->timeout = 50;
client->message_timeout = 5;
+ client->distribute_events = 0;
AST_LIST_HEAD_INIT(&client->messages);
client->component = 0;
ast_copy_string(client->statusmessage, "Online and Available", sizeof(client->statusmessage));
@@ -3396,79 +4256,91 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
client->state = AJI_DISCONNECTED;
}
while (var) {
- if (!strcasecmp(var->name, "username"))
+ if (!strcasecmp(var->name, "username")) {
ast_copy_string(client->user, var->value, sizeof(client->user));
- else if (!strcasecmp(var->name, "serverhost"))
+ } else if (!strcasecmp(var->name, "serverhost")) {
ast_copy_string(client->serverhost, var->value, sizeof(client->serverhost));
- else if (!strcasecmp(var->name, "secret"))
+ } else if (!strcasecmp(var->name, "secret")) {
ast_copy_string(client->password, var->value, sizeof(client->password));
- else if (!strcasecmp(var->name, "statusmessage"))
+ } else if (!strcasecmp(var->name, "statusmessage")) {
ast_copy_string(client->statusmessage, var->value, sizeof(client->statusmessage));
- else if (!strcasecmp(var->name, "port"))
+ } else if (!strcasecmp(var->name, "port")) {
client->port = atoi(var->value);
- else if (!strcasecmp(var->name, "timeout"))
+ } else if (!strcasecmp(var->name, "timeout")) {
client->message_timeout = atoi(var->value);
- else if (!strcasecmp(var->name, "debug"))
+ } else if (!strcasecmp(var->name, "debug")) {
client->debug = (ast_false(var->value)) ? 0 : 1;
- else if (!strcasecmp(var->name, "type")) {
- if (!strcasecmp(var->value, "component"))
+ } else if (!strcasecmp(var->name, "type")) {
+ if (!strcasecmp(var->value, "component")) {
client->component = 1;
+ if (client->distribute_events) {
+ ast_log(LOG_ERROR, "Client cannot be configured to be both a component and to distribute events! Event distribution will be disabled.\n");
+ client->distribute_events = 0;
+ }
+ }
+ } else if (!strcasecmp(var->name, "distribute_events")) {
+ if (ast_true(var->value)) {
+ if (client->component) {
+ ast_log(LOG_ERROR, "Client cannot be configured to be both a component and to distribute events! Event distribution will be disabled.\n");
+ } else {
+ if (ast_test_flag(&pubsubflags, AJI_PUBSUB)) {
+ ast_log(LOG_ERROR, "Only one connection can be configured for distributed events.\n");
+ } else {
+ ast_set_flag(&pubsubflags, AJI_PUBSUB);
+ client->distribute_events = 1;
+ }
+ }
+ }
+ } else if (!strcasecmp(var->name, "pubsub_node")) {
+ ast_copy_string(client->pubsub_node, var->value, sizeof(client->pubsub_node));
} else if (!strcasecmp(var->name, "usetls")) {
client->usetls = (ast_false(var->value)) ? 0 : 1;
} else if (!strcasecmp(var->name, "usesasl")) {
client->usesasl = (ast_false(var->value)) ? 0 : 1;
- } else if (!strcasecmp(var->name, "forceoldssl"))
+ } else if (!strcasecmp(var->name, "forceoldssl")) {
client->forcessl = (ast_false(var->value)) ? 0 : 1;
- else if (!strcasecmp(var->name, "keepalive"))
+ } else if (!strcasecmp(var->name, "keepalive")) {
client->keepalive = (ast_false(var->value)) ? 0 : 1;
- else if (!strcasecmp(var->name, "autoprune"))
+ } else if (!strcasecmp(var->name, "autoprune")) {
ast_set2_flag(&client->flags, ast_true(var->value), AJI_AUTOPRUNE);
- else if (!strcasecmp(var->name, "autoregister"))
+ } else if (!strcasecmp(var->name, "autoregister")) {
ast_set2_flag(&client->flags, ast_true(var->value), AJI_AUTOREGISTER);
- else if (!strcasecmp(var->name, "auth_policy")) {
+ } else if (!strcasecmp(var->name, "auth_policy")) {
if (!strcasecmp(var->value, "accept")) {
ast_set_flag(&client->flags, AJI_AUTOACCEPT);
} else {
ast_clear_flag(&client->flags, AJI_AUTOACCEPT);
}
- }
- else if (!strcasecmp(var->name, "buddy"))
+ } else if (!strcasecmp(var->name, "buddy")) {
aji_create_buddy((char *)var->value, client);
- else if (!strcasecmp(var->name, "priority"))
+ } else if (!strcasecmp(var->name, "priority")) {
client->priority = atoi(var->value);
- else if (!strcasecmp(var->name, "status")) {
- if (!strcasecmp(var->value, "unavailable"))
+ } else if (!strcasecmp(var->name, "status")) {
+ if (!strcasecmp(var->value, "unavailable")) {
client->status = IKS_SHOW_UNAVAILABLE;
- else
- if (!strcasecmp(var->value, "available")
- || !strcasecmp(var->value, "online"))
+ } else if (!strcasecmp(var->value, "available")
+ || !strcasecmp(var->value, "online")) {
client->status = IKS_SHOW_AVAILABLE;
- else
- if (!strcasecmp(var->value, "chat")
- || !strcasecmp(var->value, "chatty"))
+ } else if (!strcasecmp(var->value, "chat")
+ || !strcasecmp(var->value, "chatty")) {
client->status = IKS_SHOW_CHAT;
- else
- if (!strcasecmp(var->value, "away"))
+ } else if (!strcasecmp(var->value, "away")) {
client->status = IKS_SHOW_AWAY;
- else
- if (!strcasecmp(var->value, "xa")
- || !strcasecmp(var->value, "xaway"))
+ } else if (!strcasecmp(var->value, "xa")
+ || !strcasecmp(var->value, "xaway")) {
client->status = IKS_SHOW_XA;
- else
- if (!strcasecmp(var->value, "dnd"))
+ } else if (!strcasecmp(var->value, "dnd")) {
client->status = IKS_SHOW_DND;
- else
- if (!strcasecmp(var->value, "invisible"))
+ } else if (!strcasecmp(var->value, "invisible")) {
#ifdef IKS_SHOW_INVISIBLE
client->status = IKS_SHOW_INVISIBLE;
#else
- {
ast_log(LOG_WARNING, "Your iksemel doesn't support invisible status: falling back to DND\n");
client->status = IKS_SHOW_DND;
- }
#endif
- else
+ } else {
ast_log(LOG_WARNING, "Unknown presence status: %s\n", var->value);
+ }
}
/* no transport support in this version */
/* else if (!strcasecmp(var->name, "transport"))
@@ -3504,8 +4376,9 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
client->jid = iks_id_new(client->stack, resource);
ast_free(resource);
}
- } else
+ } else {
client->jid = iks_id_new(client->stack, client->user);
+ }
if (client->component) {
iks_filter_add_rule(client->f, aji_dinfo_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE);
iks_filter_add_rule(client->f, aji_ditems_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#items", IKS_RULE_DONE);
@@ -3514,12 +4387,15 @@ static int aji_create_client(char *label, struct ast_variable *var, int debug)
} else {
iks_filter_add_rule(client->f, aji_client_info_handler, client, IKS_RULE_NS, "http://jabber.org/protocol/disco#info", IKS_RULE_DONE);
}
+
iks_set_log_hook(client->p, aji_log_hook);
ASTOBJ_UNLOCK(client);
- ASTOBJ_CONTAINER_LINK(&clients,client);
+ ASTOBJ_CONTAINER_LINK(&clients, client);
return 1;
}
+
+
#if 0
/*!
* \brief creates transport.
@@ -3582,7 +4458,7 @@ static int aji_create_buddy(char *label, struct aji_client *client)
{
struct aji_buddy *buddy = NULL;
int flag = 0;
- buddy = ASTOBJ_CONTAINER_FIND(&client->buddies,label);
+ buddy = ASTOBJ_CONTAINER_FIND(&client->buddies, label);
if (!buddy) {
flag = 1;
buddy = ast_calloc(1, sizeof(*buddy));
@@ -3595,9 +4471,9 @@ static int aji_create_buddy(char *label, struct aji_client *client)
ASTOBJ_WRLOCK(buddy);
ast_copy_string(buddy->name, label, sizeof(buddy->name));
ASTOBJ_UNLOCK(buddy);
- if (flag)
+ if (flag) {
ASTOBJ_CONTAINER_LINK(&client->buddies, buddy);
- else {
+ } else {
ASTOBJ_UNMARK(buddy);
ASTOBJ_UNREF(buddy, aji_buddy_destroy);
}
@@ -3613,8 +4489,9 @@ static int aji_load_config(int reload)
struct ast_variable *var = NULL;
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
- if ((cfg = ast_config_load(JABBER_CONFIG, config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
+ if ((cfg = ast_config_load(JABBER_CONFIG, config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
return -1;
+ }
/* Reset flags to default value */
ast_set_flag(&globalflags, AJI_AUTOREGISTER | AJI_AUTOACCEPT);
@@ -3632,6 +4509,10 @@ static int aji_load_config(int reload)
ast_set2_flag(&globalflags, ast_true(var->value), AJI_AUTOPRUNE);
} else if (!strcasecmp(var->name, "autoregister")) {
ast_set2_flag(&globalflags, ast_true(var->value), AJI_AUTOREGISTER);
+ } else if (!strcasecmp(var->name, "collection_nodes")) {
+ ast_set2_flag(&pubsubflags, ast_true(var->value), AJI_XEP0248);
+ } else if (!strcasecmp(var->name, "pubsub_autocreate")) {
+ ast_set2_flag(&pubsubflags, ast_true(var->value), AJI_PUBSUB_AUTOCREATE);
} else if (!strcasecmp(var->name, "auth_policy")) {
if (!strcasecmp(var->value, "accept")) {
ast_set_flag(&globalflags, AJI_AUTOACCEPT);
@@ -3643,8 +4524,8 @@ static int aji_load_config(int reload)
while (cat) {
if (strcasecmp(cat, "general")) {
- var = ast_variable_browse(cfg, cat);
- aji_create_client(cat, var, debug);
+ var = ast_variable_browse(cfg, cat);
+ aji_create_client(cat, var, debug);
}
cat = ast_category_browse(cfg, cat);
}
@@ -3653,9 +4534,9 @@ static int aji_load_config(int reload)
}
/*!
- * \brief grab a aji_client structure by label name or JID
+ * \brief grab a aji_client structure by label name or JID
* (without the resource string)
- * \param name label or JID
+ * \param name label or JID
* \return aji_client.
*/
struct aji_client *ast_aji_get_client(const char *name)
@@ -3673,7 +4554,7 @@ struct aji_client *ast_aji_get_client(const char *name)
}
if (!strncasecmp(aux, name, strlen(aux))) {
client = iterator;
- }
+ }
});
}
@@ -3685,9 +4566,9 @@ struct aji_client_container *ast_aji_get_clients(void)
return &clients;
}
-/*!
+/*!
* \internal
- * \brief Send a Jabber Message via call from the Manager
+ * \brief Send a Jabber Message via call from the Manager
* \param s mansession Manager session
* \param m message Message to send
* \return 0
@@ -3695,10 +4576,10 @@ struct aji_client_container *ast_aji_get_clients(void)
static int manager_jabber_send(struct mansession *s, const struct message *m)
{
struct aji_client *client = NULL;
- 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");
+ 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");
@@ -3726,13 +4607,13 @@ static int manager_jabber_send(struct mansession *s, const struct message *m)
astman_append(s, "Response: Error\r\n");
}
if (!ast_strlen_zero(id)) {
- astman_append(s, "ActionID: %s\r\n",id);
+ astman_append(s, "ActionID: %s\r\n", id);
}
astman_append(s, "\r\n");
return 0;
}
-/*!
+/*!
* \internal
* \brief Reload the jabber module
*/
@@ -3753,11 +4634,16 @@ static int aji_reload(int reload)
if (iterator->state == AJI_DISCONNECTED) {
if (!iterator->thread)
ast_pthread_create_background(&iterator->thread, NULL, aji_recv_loop, iterator);
- } else if (iterator->state == AJI_CONNECTING)
+ } else if (iterator->state == AJI_CONNECTING) {
aji_get_roster(iterator);
+ if (iterator->distribute_events) {
+ aji_init_event_distribution(iterator);
+ }
+ }
+
ASTOBJ_UNLOCK(iterator);
});
-
+
return 1;
}
@@ -3776,6 +4662,12 @@ static int unload_module(void)
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);
ASTOBJ_CONTAINER_TRAVERSE(&clients, 1, {
@@ -3809,8 +4701,8 @@ static int load_module(void)
ast_register_application_xml(app_ajisend, aji_send_exec);
ast_register_application_xml(app_ajisendgroup, aji_sendgroup_exec);
ast_register_application_xml(app_ajistatus, aji_status_exec);
- ast_register_application_xml(app_ajijoin, aji_join_exec);
- ast_register_application_xml(app_ajileave, aji_leave_exec);
+ ast_register_application_xml(app_ajijoin, aji_join_exec);
+ ast_register_application_xml(app_ajileave, aji_leave_exec);
ast_cli_register_multiple(aji_cli, ARRAY_LEN(aji_cli));
ast_custom_function_register(&jabberstatus_function);
ast_custom_function_register(&jabberreceive_function);