royneary / mod_push

Other
68 stars 19 forks source link

mod_push

Note: This project is not maintained anymore. Mainline ejabberd now ships the app server part of XEP-0357.

mod_push implements XEP-0357 (Push) for ejabberd and includes a messaging-focussed app server for the common push notification services. These are

mod_push is a project in the Google Summer of Code 2015 and is still pre-alpha. Please send feedback.

Prerequisites

Installation

git clone https://github.com/royneary/mod_push.git
# copy the source code folder to the module sources folder of your ejabberd
# installation (may be different on your machine)
sudo cp -R mod_push /var/spool/jabber/.ejabberd-modules/sources/
# if done right ejabberdctl will list mod_push as available module
ejabberdctl modules_available
# automatically compile and install mod_push
ejabberdctl module_install mod_push 

Important implementation details

mod_push depends on stream management (XEP-0198) stream resumption. A client will only receive push notifications when the server detects a dead TCP connection. After that the server will wait for the client to resume the stream. We call this state "pending" state (a.k.a. zombie state). To enter the pending state clients are expected to close their TCP connection without sending </stream>. They will typically do so in a handler the mobile OS will call before moving the application into background.

Administrators sometimes have to restart XMPP servers. Ejabberd will close all open streams in that event which means that after a restart stream management will no longer notify mod_push about incoming stanzas for the previous push clients. As a solution to this problem mod_push will send a push notification to all previously pending push users after a restart so they can open a new connection. Currently this notification can not be distinguished from a "normal" notification (e.g. one sent in the event of an incoming message). This means a client will typically try to resume the stream which will fail as it does not exist anymore. Then it can start a new stream and re-enable push. Registrations at mod_push's app server will not be affected by server restarts so clients do not need to re-register.

Configuration

stream management

The option resume_timeout for module ejabberd_c2s in the listen-section of ejabberd.yml must be set to a value greater than 0 (default value is 300). This enables stream resumption but the value has no effect on push users since mod_push overwrites this value to keep them pending for a long time.

pubsub configuration

An XEP-0357 app server requires a pubsub service where XMPP servers can publish notifications. The pubsub service needs a dedicated hostname. If the internal app server shall be used, that is mod_push's option backends is not an empty list [], mod_pubsub must be configured to fulfill the requirements of XEP-0357. The push plugin delivered by mod_push takes care of that. For the internal app server nodetree: "virtual" must be set. The push plugin can also be used to provide a pubsub service for external app server, such as Oshiya. In that case nodetree = "tree" must be set.

mod_pubsub:
  host : "push.example.net"
  nodetree : "virtual"
  plugins: 
    - "push"

Note: switching from nodetree = "tree" to nodetree = "virtual" currently causes mod_pubsub crashes. A workaround is to clear the pubsub mnesia tables. This affects all pubsub services on the ejabberd instance.

ejabberdctl debug
mnesia:clear_table(pubsub_node).
mnesia:clear_table(pubsub_state).
mnesia:clear_table(pubsub_index).
mnesia:clear_table(pubsub_subscription).
mnesia:clear_table(pubsub_item).

If you want to allow other XMPP servers to use your app server you need a SRV record in your DNS server:

_xmpp-server._tcp.push.example.net. 86400 IN SRV 5 0 5269 example.net.

XEP-0357 configuration

User-definable options

There are user-definable config options to specify what contents should be in a push notification. You can set default values for them in the mod_push section:

example:

mod_push:
  include_senders: true
  include_message_count: true
  include_subscription_count: true

A user can obtain the current push configuration (the server's default configuration if he never changed it) by sending a service discovery info request to his bare jid. The response will contain a form of FORM_TYPE urn:xmpp:push:options.

<iq from='bill@example.net' to='bill@example.net/Home' id='x13' type='result'>
  <query xmlns='http://jabber.org/protocol/disco#info'>
    <x xmlns='jabber:x:data' type='result'>
      <field type='hidden' var='FORM_TYPE'><value>urn:xmpp:push:options</value></field>
      <field type='boolean' var='include-senders'><value>0</value></field>
      <field type='boolean' var='include-message-count'><value>1</value></field>
      <field type='boolean' var='include-subscription-count'><value>1</value></field>
      <field type='boolean' var='include-message-bodies'><value>0</value></field>
    </x>
    <identity category='account' type='registered'/>
    <feature var='http://jabber.org/protocol/disco#info'/>
    <feature var='urn:xmpp:push:0'/>
  </query>
</iq>

A can change her configuration by including a form of FORM_TYPE urn:xmpp:push:options into the enable request. Note that this configuration is a per-user configuration that is valid for all resources. To send publish-options which are passed to the pubsub-service when publishing a notification an other form of FORM_TYPE http://jabber.org/protocol/pubsub#publish-options can be included. In this example it contains a secret a pubsub service might require as credential. mod_push's internal app server does require providing the secret obtained during registration.

<iq type='set' to='example.net' id='x42'>                                                
  <enable xmlns='urn:xmpp:push:0' jid='push.example.net' node='v9S+H8VTFgEl'>
    <x xmlns='jabber:x:data' type='submit'>
      <field var='FORM_TYPE'><value>urn:xmpp:push:options</value></field>
      <field var='include-senders'><value>0</value></field>
      <field var='include-message-count'><value>1</value></field>
      <field var='include-message-bodies'><value>0</value></field>
      <field var='include-subscription-count'><value>1</value></field>
    </x>
    <x xmlns='jabber:x:data' type='submit'>
      <field var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#publish-options</value></field>
      <field var='secret'><value>szLo+l17Q0ZQr2dShnyQiYn/stqicShK</value></field>
    </x>
  </enable>                      
</iq>

mod_push provides in-band configuration although not recommended by XEP-0357. In order to prevent privilege escalation as mentioned in the XEP subsequent enable requests are only allowed to disable options, not enable them. A fresh configuration is only possible after all push-enabled resources have been disabled.

The response to an enable request with configuration form will include those values that have been accepted for the new configuration.

restrict access

The option access_backends allows restricting access to the app server backends using ejabberd's acl feature. access_backends may be defined in the mod_push section. The default value is all. The example configuration only allows local XMPP users to use the app server.

App server configuration

You can set up multiple app server backends for the different push notification services. This is not required, your users can use external app servers too.

Default options

Common options

APNS-specific options

GCM-specific options

WNS

Example configuration

access:
  local_users:
    local: allow

acl:
  local:
    server: "example.net"

modules:
  mod_pubsub:
    host : "push.example.net"
    nodetree : "virtual"
    plugins: 
      - "push"

  mod_push:
    include_senders: true
    access_backends: local_users
    certfile: "/etc/ssl/private/example.pem"
    backends:
      -
        type: ubuntu
        register_host: "example.net"
        pubsub_host: "push.example.net"
      -
        type: gcm
        app_name: "chatninja"
        register_host: "example.net"
        pubsub_host: "push.example.net"
        auth_key: "HgeGfbhwplu7F-fjCUf6gBfkauUaq12h0nHazqc" 
      -
        type: apns
        app_name: "chatninja"
        register_host: "example.net"
        pubsub_host: "push.example.net"
        certfile: "/etc/ssl/private/apns_example_app.pem"  

App server usage

Clients can communicate with the app server by sending adhoc requests containing an XEP-0004 data form: These are the available adhoc commands:

Example:

<iq type='set' to='example.net' id='exec1'>
  <command xmlns='http://jabber.org/protocol/commands'
           node='register-push-apns'
           action='execute'>
    <x xmlns='jabber:x:data' type='submit'>
      <field
      var='token'>
        <value>r3qpHKmzZHjYKYbG7yI4fhY+DWKqFZE5ZJEM8P+lDDo=</value>
      </field>
      <field var='device-name'><value>Home</value></field>
    </x>
  </command>
</iq>

There are common fields which a client has to include for every backend type and there are backend-specific fields.

Common fields for the register commands

register-push-apns fields

register-push-ubuntu fields

register-push-wns fields

unregister-push fields

register command response

The app server returns the jid of the pubsub host a pubsub node name and a secret. The client can pass those to its XMPP server in the XEP-0357 enable request. Example:

<iq from='example.net' to='steve@example.net/home' id='exec1' type='result'>
  <command xmlns='http://jabber.org/protocol/commands' sessionid='2015-06-15T01:05:03.380703Z' node='register-push-apns' status='completed'>
    <x xmlns='jabber:x:data' type='result'>
      <field var='jid'>
        <value>push.example.net</value>
      </field>
      <field var='node'>
        <value>2100994384</value>
      </field>
      <field var='secret'>
        <value>C46JMRFNEixmP1c5lXEUaIGKGVy-sv81</value>
      </field>
    </x>
  </command>
</iq>

unregister command response

When a list of nodes was given in the request, the response contains the list of nodes of the deleted registrations. Example:

<iq from='example.net' to='steve@example.net/home' id='exec1' type='result'>
  <command xmlns='http://jabber.org/protocol/commands' sessionid='2015-06-15T01:23:12.836386Z' node='unregister-push' status='completed'>
    <x xmlns='jabber:x:data' type='result'>
      <field type='list-multi' var='nodes'>
        <value>2100994384</value>
      </field>
    </x>
  </command>
</iq>

list registrations

A list of a user's push-enabled clients can be obtained using the list-push-registrations command. This might be important if a push client shall be unregistered without having access to the device anymore. If a client provided a device-name value during registration it is included in the response along with the node name.

<iq from='example.net' to='bill@example.net/home' id='exec1' type='result'>
  <command xmlns='http://jabber.org/protocol/commands' sessionid='2015-08-13T16:10:02.489807Z' node='list-push-registrations' status='completed'>
    <x xmlns='jabber:x:data' type='result'>
      <item>
        <field var='device-name'><value>iOS device</value></field>
        <field var='node'><value>2269691389</value></field>
      </item>
      <item>
        <field var='device-name'><value>Ubuntu device</value></field>
        <field var='node'><value>2393247634</value></field>
      </item>
    </x>
  </command>
</iq>