MT#63479 Discontinue ngcp-prosody-modules project

We are removing the prosody support from NGCP, so there is no need
anymore for this component.

Change-Id: I80f0bf4be31930c0de69e20b96beefbbec451c35
master
Guillem Jover 10 months ago
parent eb84520aec
commit f8ac2a875c

1
.gitignore vendored

@ -1 +0,0 @@
.pc

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2009-2015 Various Contributors (see individual files and source control)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,5 @@
Repository discontinued
-----------------------
The repository is for historical purposes only, it was obsoleted and
no longer available starting with mr14.0.

@ -1,128 +0,0 @@
examples of messages for testing funtionality
---------------------------------------------
* mod_sipwise_lastactivity:
```
<iq id='last1' to='43991002@192.168.1.102' type='get'>
<query xmlns='jabber:iq:last'/>
</iq>
```
```
<iq id="last1" to="43991002@192.168.1.102/profanity" type="result">
<query xmlns="jabber:iq:last" seconds="8"/>
</iq>
```
* mod_sipwise_vcard_cusax:
```
<iq id='bx81v356' to='43991002@192.168.1.102' type='get'>
<vCard xmlns='vcard-temp'/>
</iq>
```
response:
```
<iq id="bx81v356" to="43991002@192.168.1.102/profanity" type="result">
<vCard prodid="-//HandGen//NONSGML vGen v1.0//EN" xmlns="vcard-temp" version="2.0">
<JABBERID>43991002@192.168.1.102</JABBERID>
<TEL><VIDEO/><NUMBER>sip:43991002@192.168.1.102</NUMBER></TEL>
<TEL><VOICE/><NUMBER>43991002</NUMBER></TEL>
<EMAIL><INTERNET/><PREF/><USERID>default-customer@default.invalid</USERID></EMAIL>
</vCard>
</iq>
```
* mod_sipwise_vhosts_sql:
```
<iq id="discoitemsreq" to="192.168.1.102" type="get">
<query xmlns="http://jabber.org/protocol/disco#items"/>
</iq>
```
response:
```
<iq id="discoitemsreq" to="43991002@192.168.1.102/profanity" type="result" from="192.168.1.102">
<query xmlns="http://jabber.org/protocol/disco#items">
<item jid="search.192.168.1.102"/>
<item jid="conference.192.168.1.102"/>
</query>
</iq>
```
* mod_sipwise_vjud:
discover search fields
```
<iq type='get' to='search.192.168.1.102' id='search1' xml:lang='en'>
<query xmlns='jabber:iq:search'/>
</iq>
```
response:
```
<iq id="search1" to="43991002@192.168.1.102/profanity" type="result" from="search.192.168.1.102">
<query xmlns="jabber:iq:search">
<instructions>Use the enclosed form to search</instructions>
<x xmlns="jabber:x:data" type="form">
<title>User Directory Search</title>
<instructions>Please provide the following information to search for subscribers</instructions>
<field type="hidden" var="FORM_TYPE"><value>jabber:iq:search</value></field>
<field label="e164 Phone number" type="text-single" var="e164"/>
<field label="domain" type="text-single" var="domain"/>
</x>
<nick/>
</query>
</iq>
```
search domain:
```
<iq type='set' to='search.192.168.1.102' id='search2' xml:lang='en'><query xmlns='jabber:iq:search'>
<x xmlns='jabber:x:data' type='submit'>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<field var='domain'>
<value>192.168.1.102</value>
</field>
</x>
</query>
</iq>
```
response:
```
<iq id="search2" to="43991002@192.168.1.102/profanity" type="result" from="search.192.168.1.102">
<query xmlns="jabber:iq:search">
<x xmlns="jabber:x:data" type="result">
<field type="hidden" var="FORM_TYPE"><value>jabber:iq:search</value></field>
<reported><field label="domain" type="text-single" var="domain"/></reported>
<item><field var="domain"><value>192.168.1.102</value></field></item>
</x>
</query>
</iq>
```
search number:
```
<iq type='set' to='search.192.168.1.102' id='search3' xml:lang='en'><query xmlns='jabber:iq:search'>
<x xmlns='jabber:x:data' type='submit'>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<field var='e164'>
<value>43991003</value>
</field>
</x>
</query>
</iq>
```
response:
```
<iq id="search3" type="result" to="43991002@192.168.1.102/profanity" from="search.192.168.1.102">
<query xmlns="jabber:iq:search">
<x xmlns="jabber:x:data" type="result">
<field type="hidden" var="FORM_TYPE"><value>jabber:iq:search</value></field>
<reported><field label="e164 Phone number" type="text-single" var="e164"/></reported>
<item><field var="e164"><value>43991003@192.168.1.102</value></field></item>
</x>
</query>
</iq>
```

7
debian/.gitignore vendored

@ -1,7 +0,0 @@
*.debhelper
*.log
*.substvars
/.debhelper/
/debhelper-build-stamp
/files
/ngcp-prosody-modules/

676
debian/changelog vendored

@ -1,676 +0,0 @@
ngcp-prosody-modules (14.1.0.0+0~mr14.1.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 24 Nov 2025 16:57:25 +0100
ngcp-prosody-modules (14.0.0.0+0~mr14.0.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 08 Sep 2025 11:45:56 +0200
ngcp-prosody-modules (13.5.0.0+0~mr13.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Fri, 20 Jun 2025 10:44:56 +0200
ngcp-prosody-modules (13.4.0.0+0~mr13.4.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Thu, 10 Apr 2025 08:29:21 +0200
ngcp-prosody-modules (13.3.0.0+0~mr13.3.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 28 Jan 2025 15:15:59 +0100
ngcp-prosody-modules (13.2.0.0+0~mr13.2.0.0) unstable; urgency=medium
[ Richard Fuchs ]
* [01bb8cb] MT#53790 pushd: rename `gcm` to `fcm`
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 09 Dec 2024 11:28:18 +0100
ngcp-prosody-modules (13.1.0.0+0~mr13.1.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 01 Oct 2024 10:01:17 +0200
ngcp-prosody-modules (13.0.0.0+0~mr13.0.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Sun, 28 Jul 2024 12:04:04 +0200
ngcp-prosody-modules (12.5.0.0+0~mr12.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 22 May 2024 14:32:48 +0200
ngcp-prosody-modules (12.4.0.0+0~mr12.4.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Fri, 15 Mar 2024 08:49:29 +0100
ngcp-prosody-modules (12.3.0.0+0~mr12.3.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 15 Jan 2024 14:48:24 +0100
ngcp-prosody-modules (12.2.0.0+0~mr12.2.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 29 Nov 2023 13:38:06 +0100
ngcp-prosody-modules (12.1.0.0+0~mr12.1.0.0) unstable; urgency=medium
[ Guillem Jover ]
* [bef0870] MT#58356 Update packaging for bookworm
[ Victor Seva ]
* [6957e9c] MT#58471 refresh modules
[ Marco Capetta ]
* [02a6ec0] MT#58471 Fix mod_carbons_adhoc call to adhoc module
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Sun, 15 Oct 2023 08:25:07 +0200
ngcp-prosody-modules (12.0.1.0+0~mr12.0.1.0) unstable; urgency=medium
[ Mykola Malkov ]
* [29dbb14] MT#57453 Switch docker image to bookworm
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 30 Aug 2023 18:05:32 +0200
ngcp-prosody-modules (11.5.0.0+0~mr11.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 26 Jun 2023 14:11:45 +0200
ngcp-prosody-modules (11.4.0.0+0~mr11.4.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Fri, 24 Mar 2023 13:05:07 +0100
ngcp-prosody-modules (11.3.0.0+0~mr11.3.0.0) unstable; urgency=medium
[ Michael Prokop ]
* [0fac3f5] MT#55819 MT#55524 Depend on either lua-rex-pcre or lua-rex-pcre2
[ Victor Seva ]
* [50c3253] MT#55920 Support rex_pcre2 with Debian/bookworm
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 17 Jan 2023 17:44:38 +0100
ngcp-prosody-modules (11.2.0.0+0~mr11.2.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 04 Oct 2022 13:52:12 +0200
ngcp-prosody-modules (11.1.0.0+0~mr11.1.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 22 Aug 2022 20:02:57 +0200
ngcp-prosody-modules (11.0.0.0+0~mr11.0.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 07 Jun 2022 11:45:14 +0200
ngcp-prosody-modules (10.5.0.0+0~mr10.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Thu, 31 Mar 2022 00:01:48 +0200
ngcp-prosody-modules (10.4.0.0+0~mr10.4.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 24 Jan 2022 16:57:42 +0100
ngcp-prosody-modules (10.3.0.0+0~mr10.3.0.0) unstable; urgency=medium
[ Guillem Jover ]
* [2080a38] TT#124273 Update packaging for bullseye
* [13049ba] TT#124273 Move override_dh_auto_clean actions into debian/clean
* [b8e90ca] TT#124273 Use execute_after_ instead of override_ for dh command
[ Victor Seva ]
* [1511c9a] TT#150950 sipwise_pushd: support new mobile_push_enable values
* [6ae871f] TT#150950 sipwise_pushd: don't need to check dom preferences
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 23 Nov 2021 14:19:49 +0100
ngcp-prosody-modules (10.2.0.0+0~mr10.2.0.0) unstable; urgency=medium
[ Victor Seva ]
* [45c42ff] TT#137602 vhost_sql: prepare host from sql
* [a7e1cd5] TT#137602 auth_sql: lower domain for comparation
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 05 Oct 2021 13:20:47 +0200
ngcp-prosody-modules (10.1.0.0+0~mr10.1.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 24 Aug 2021 16:53:07 +0200
ngcp-prosody-modules (10.0.0.0+0~mr10.0.0.0) unstable; urgency=medium
[ Manuel Montecelo ]
* [5f829f3] TT#118659 Switch Dockerfile from Debian buster to bullseye
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 14 Jun 2021 16:38:22 +0200
ngcp-prosody-modules (9.5.0.0+0~mr9.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 20 Apr 2021 11:59:27 +0200
ngcp-prosody-modules (9.4.0.0+0~mr9.4.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 15 Feb 2021 17:54:21 +0100
ngcp-prosody-modules (9.3.0.0+0~mr9.3.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 05 Jan 2021 15:33:09 +0100
ngcp-prosody-modules (9.2.0.0+0~mr9.2.0.0) unstable; urgency=medium
[ Victor Seva ]
* [181deaf] TT#92952 sipwise_pushd: fix mobile_push_silent_list query
* [4b1b193] TT#96550 mod_sipwise_redis_*: add some more debug info at add_host()
* [e7ecca6] TT#15822 mod_sipwise_pushd: fix error introduced back in 2017
* [addb5e5] TT#97400 sipwise_vhosts_sql: fix [de]activation of vhost via ngcp-panel
* [2cee084] TT#99900 remove utils/table.lua and use the one from lua-ngcp-kamailio
* [dde6a2f] TT#96550 mod_muc interface changed since 0.11.0
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 09 Nov 2020 16:26:34 +0100
ngcp-prosody-modules (9.1.0.0+0~mr9.1.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 31 Aug 2020 21:55:41 +0200
ngcp-prosody-modules (9.0.0.0+0~mr9.0.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 21 Jul 2020 14:49:26 +0200
ngcp-prosody-modules (8.6.0.0+0~mr8.6.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 14 Jul 2020 11:54:14 +0200
ngcp-prosody-modules (8.5.0.0+0~mr8.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 19 May 2020 11:11:46 +0200
ngcp-prosody-modules (8.4.0.0+0~mr8.4.0.0) unstable; urgency=medium
[ Richard Fuchs ]
* [06bdfa9] TT#20350 mod_auth_sql: check subscriber lock level
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 16 Mar 2020 11:17:03 +0100
ngcp-prosody-modules (8.3.0.0+0~mr8.3.0.0) unstable; urgency=medium
[ Victor Seva ]
* [0f25a1c] TT#72257 refresh plugins from upstream
* [34e3345] TT#35053 remove mod_mam patch for sipwise_archive.lib
* [4fb063d] TT#66583 remove modules merged at prosody 0.11 version
* [937d0dd] TT#66583 remove patch for own module
* [f9b0c6f] TT#66583 fix load module error
* [157f5fe] TT#66583 changes needed for support 0.11
* [a1721ca] TT#66583 remove unussed mod_sipwise_offline
[ Alexander Lutay ]
* [731a058] TT#66583 Add util/prosody_sipwise_migrator for upgrade to prosody 0.11
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 27 Jan 2020 12:25:14 +0100
ngcp-prosody-modules (8.2.0.0+0~mr8.2.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 25 Nov 2019 14:58:26 +0100
ngcp-prosody-modules (8.1.0.0+0~mr8.1.0.0) unstable; urgency=medium
[ Manuel Montecelo ]
* [5c9c265] Revert "TT#44917 Ship certain modules only on Debian/stretch"
* [3f09215] TT#66582 Change the dependency on prosody, it does not work with later versions
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 08 Oct 2019 09:11:47 +0200
ngcp-prosody-modules (8.0.0.0+0~mr8.0.0.0) unstable; urgency=medium
[ Alexander Lutay ]
* [b898a6c] TT#60950 Switch prosody docker to buster
[ Guillem Jover ]
* [9cbd440] TT#61954 Add missing COPYING file from upstream
* [c873da3] TT#61954 Update copyright Format URL to use https
* [22a5ddd] TT#61954 Rework debian/copyright file
* [4211f6d] TT#61954 Update copyright years
* [3ab509e] TT#61954 Set debhelper compat level in Build-Depends instead of debian/compat
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 05 Aug 2019 20:59:38 +0200
ngcp-prosody-modules (7.5.0.0+0~mr7.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 17 Jun 2019 14:39:21 +0200
ngcp-prosody-modules (7.4.0.0+0~mr7.4.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Thu, 25 Apr 2019 14:31:30 +0200
ngcp-prosody-modules (7.3.0.0+0~mr7.3.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 25 Feb 2019 22:58:44 +0100
ngcp-prosody-modules (7.2.0.0+0~mr7.2.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 09 Jan 2019 22:03:22 +0100
ngcp-prosody-modules (7.1.0.0+0~mr7.1.0.0) unstable; urgency=medium
[ Michael Prokop ]
* [039086e] TT#44917 Ship certain modules only on Debian/stretch
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 12 Nov 2018 22:39:48 +0100
ngcp-prosody-modules (7.0.0.0+0~mr7.0.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 24 Sep 2018 18:58:25 +0200
ngcp-prosody-modules (6.5.0.0+0~mr6.5.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 24 Jul 2018 13:40:44 +0200
ngcp-prosody-modules (6.4.0.0+0~mr6.4.0.0) unstable; urgency=medium
[ Andreas Granig ]
* [a0418df] TT#35053 Fix MAM by retrieving also inbound msg
* [fde6324] TT#35604 Implement silent flag for push
[ Guillem Jover ]
* [c17d295] TT#26264 Remove maintainer script directly calling init scripts
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 29 May 2018 10:09:00 +0200
ngcp-prosody-modules (6.3.0.0+0~mr6.3.0.0) unstable; urgency=medium
[ Victor Seva ]
* [b84c7b3] [util/table] fix implode function
* [dd76b25] TT#33379 Merge https://github.com/sipwise/prosody/pull/2
[ Andreas Granig ]
* [5784273] TT#27653 Strip trailing trash from uuid
[ Richard Fuchs ]
* [bffc901] TT#34705 merge https://github.com/sipwise/prosody/pull/3
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 03 Apr 2018 22:32:05 +0200
ngcp-prosody-modules (6.2.0.0+0~mr6.2.0.0) unstable; urgency=medium
[ Sergii Kipot ]
* [cd32e76] TT#29456 Create prosody-jessie docker for luacheck
* [48fbebb] TT#29456 Create prosody-stretch docker for luacheck
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 29 Jan 2018 21:56:46 +0100
ngcp-prosody-modules (6.1.0.0+0~mr6.1.0.0) unstable; urgency=medium
[ Guillem Jover ]
* [a6fa2c1] TT#22072 Update packaging
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 06 Dec 2017 00:13:15 +0100
ngcp-prosody-modules (6.0.0.0+0~mr6.0.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 02 Oct 2017 23:43:33 +0200
ngcp-prosody-modules (5.5.0.0+0~mr5.5.0.0) unstable; urgency=medium
[ Victor Seva ]
* [1057e3a] TT#17657 mod_sipwise_pushd: send full_message field to pushd
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 17 Jul 2017 22:47:07 +0200
ngcp-prosody-modules (5.4.0.0+0~mr5.4.0.0) unstable; urgency=medium
[ Victor Seva ]
* [fb99302] TT#13055 mod_sipwise_pushd: support muc_config
* [5b993fc] TT#14103 update upstream modules to 2e1a4740adee
* [0117591] TT#14278 mam: add all supported versions to disco
* [d6f87e7] TT#14258 mod_sipwise_pushd: add nick attribute for MUC presence
* [23e737f] TT#15822 mod_sipwise_pushd: detect attachment message
* [ef87c7b] TT#14103 update upstream modules to 9b43b7fc3558
* [a70b099] TT#16400 support exclude config muc rooms from sending push notifications
[ Andreas Granig ]
* [0521a03] TT#14099 Send muc room name in push
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Tue, 16 May 2017 21:43:06 +0200
ngcp-prosody-modules (5.3.0.0+0~mr5.3.0.0) unstable; urgency=medium
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 22 Mar 2017 11:00:59 +0100
ngcp-prosody-modules (5.2.0.0+0~mr5.2.0.0) unstable; urgency=medium
[ Victor Seva ]
* [c52d6cd] MT#16059 mod_sipwise_vhosts_sql: fix debug output
* [bb1360f] TT#7560 mod_sipwise_offline
* [9e65f10] TT#7482 mod_man: support mam version 0.5 (urn:xmpp:mam:1)
* [754fb5b] TT#8297 mam: implement archive to DB
* [c2dec69] TT#8766 mod_sipwise_pushd: fix error when caller is not from local domain
* [9e93fb6] TT#9303 mod_mam: sipwise_archive.lib fix crash at find()
* [b17aec9] TT#9311 mod_mam/sipwise_archive.lib: full username
* [7fc4d61] TT#9317 mod_mam: don't store bodyless chat messages
* [0fbc57e] TT#9400 use quilt to maintain patches to upstream
* [e191475] TT#9402 refresh modules from upstream
* [11f3f0b] TT#9504 sipwise_offline: do not store bodyless chat messages
* [d5491e2] TT#9503 sipwise_pushd: wrong offline badge
* [937ff0f] TT#9314: support emojis for DB
* [ff4c2e0] TT#9551 set charset on reconnection
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Fri, 03 Feb 2017 00:41:42 +0100
ngcp-prosody-modules (5.1.0.0+0~mr5.1.0.0) unstable; urgency=medium
[ Victor Seva ]
* [e4a378a] MT#10921 add mod_man plugin
* [5003261] TT#6605 mod_sipwise_pushd: fix MUC offline when sender has more than one session
* [0d7e7ac] TT#6646 support XEP-0352: Client State Indication
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Thu, 17 Nov 2016 23:19:59 +0100
ngcp-prosody-modules (5.0.0.0+0~mr5.0.0.0) unstable; urgency=medium
[ Victor Seva ]
* [b09f209] MT#19579 pushd: define get_muc_room() to filter possible errors
-- Alexander Lutay <alutay@sipwise.com> Fri, 21 Oct 2016 15:18:37 +0200
ngcp-prosody-modules (1.15.0.0+0~mr5.0.0.0) unstable; urgency=medium
[ Victor Seva ]
* [608b9d6] MT#20021 mod_sipwise_vjud: add some debug messages related to normalize_number and search_by_number
* [bc610ad] Revert "MT#18055 mod_sipwise_vjud: use dataforms for searching e164 values"
* [9b811d4] MT#18055 mod_sipwise_vjud: use dataforms for searching e164 values
* [778b400] TT#1597 sipwise_vjud: add domain field
* [12c628f] TT#1597 sipwise_vjud: fix dataform reply format
* [c51c1f6] TT#1597 sipwise_vjud: fix dataform reply format
[ Guillem Jover ]
* [4a32147] MT#20479 Fix the package to be architecture all
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 01 Aug 2016 16:35:39 +0200
ngcp-prosody-modules (1.14.0.0+0~mr4.5.0.0) unstable; urgency=medium
[ Victor Seva ]
* [5181464] MT#18725 mod_sipwise_groups: reconnect_check for lookup functions
* [b83138c] MT#18905 support pushd_blocking
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 23 May 2016 19:00:19 +0200
ngcp-prosody-modules (1.13.0.0+0~mr4.4.0.0) unstable; urgency=medium
[ Victor Seva ]
* [dad9a2c] MT#17545 upgrade community modules
* [9474448] MT#17541 support XEP-191 mod_blocking
* [c122e4e] MT#18041 mod_sipwise_vjud: fix discovery
* [f6031ab] MT#18041 mod_sipwise_vjud: fix discovery, clean code
* [32cbf31] MT#18055 mod_sipwise_vjud: use dataforms for searching e164 values
[ Michael Prokop ]
* [aa5ffbc] MT#17699 Bump Standards-Version to 3.9.7
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 21 Mar 2016 22:29:16 +0100
ngcp-prosody-modules (1.12.0.0+0~mr4.3.0.0) unstable; urgency=medium
[ Victor Seva ]
* [ff763a7] MT#15837 mod_sipwise_vcard_cusax: add error when no info is retrive
* [24c1cc4] MT#15893 mod_sipwise_vcard_cusax: return error if subscriber does not exist
* [bbb6f0f] MT#15819 mod_sipwise_pushd: set apns_alert message on muc
* [c5f0784] MT#6407 cluster using mod_shard* as base
* [babc44f] MT#6407 support MUC on cluster
* [320bdef] MT#16059 save MUC participants on redis too
* [8243371] MT#16059 mod_sipwise_redis_mucs: fix unbind hook. fixes commit 320bdefb33c
* [d346079] MT#16059 mod_sipwise_pushd: support groupchat messages
* [a943438] MT#15893 mod_sipwise_vcard_cusax: fix error if domain has no users
* [c579c89] MT#16059 mod_sipwise_vhosts_sql: fix load sipwise_vjud component at search domain
* [7e27dba] MT#16059 mod_sipwise_vjud: fix load module
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 16 Dec 2015 09:30:28 +0100
ngcp-prosody-modules (1.11.0.0+0~mr4.2.0.0) unstable; urgency=medium
[ Victor Seva ]
* [d20b62f] MT#14281 pushd: use formencode() in order to urlencode the http request
* [ee207e5] MT#14293 luacheck project config
* [be8663b] MT#14921 add uuid push_id parameter
* [6abfd0f] MT#15119 sipwise_log_debug module
* [f672d8e] MT#15303 add mod_limit_auth
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 12 Oct 2015 15:43:59 +0200
ngcp-prosody-modules (1.10.0.0+0~mr4.1.0.0) unstable; urgency=low
[ Victor Seva ]
* [5a87e97] MT#8951 fix vcard format v2 and email
* [d55bd7e] MT#11583 lastactivity: fix debug output and remove .save() .restore()
* [d08c713] MT#12267 merge changes from 0.9.8
* [8447071] MT#12267 add mod_carbons and sync mod_smacks
* [e2ae328] MT#12267 add mod_carbons_* and sync mod_smacks ( license header )
* [1e3496f] MT#12953 pushd: fix error when the user is not provided properly
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 13 Jul 2015 15:32:17 +0200
ngcp-prosody-modules (1.9.0.0+0~mr4.0.0.0) unstable; urgency=low
[ Victor Seva ]
* [7780d3c] MT#10849: add mod_smacks plugin
* [aae45da] MT#6411 redis_session: new key schema
* [3f2c23f] MT#6407 prosody cluster
* [5021516] MT#9763 pushd: add prefix with sender_name at apns_alert field
* [73fde6c] MT#9763 pushd: set the push msg for muc invitations
* [af940bf] MT#6407 mod_sipwise_cluster: remove unused host config
* [f1631d6] MT#9763 pushd: fix get_caller_info()
* [26c11e6] MT#11125 don't restart prosody after install if node is not proxy
* [c4633d8] MT#11125 fix bashism at 26c11e6e3841a. Don't restart prosody after install if node is not proxy
* [a017dbf] MT#11583 add our own lastactivity module
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 18 Mar 2015 14:12:57 +0100
ngcp-prosody-modules (1.8.0.0+0~mr3.8.0.0) unstable; urgency=low
[ Victor Seva ]
* [f031997] MT#9985 vhosts: enable MUC for domains
* [7945b1b] MT#9985 vhosts: rework enable MUC for domains
* [6dfa707] MT#9763 pushd: extended gcm and aspn parameters.
* [dd8da8e] MT#7141 upgrade package to 3.0 style
* [a6e0655] MT#9763 Pushd: use new format for pushd query. Skip empty messages
* [d4995ce] MT#9763 push: add data_sender_number and data_sender_sip
[ Alexander Lutay ]
* [5914b4f] MT#9763 Pushd: updated Apple sound files + typo fix s/aspn/apns/
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Wed, 07 Jan 2015 21:37:29 +0100
ngcp-prosody-modules (1.7.0.0+0~mr3.7.0.0) unstable; urgency=low
* New release.
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Thu, 30 Oct 2014 17:29:07 +0100
ngcp-prosody-modules (1.6.0.0+0~mr3.6.0.0) unstable; urgency=low
[ Victor Seva ]
* [0d82202] MT#8851 add mod_sipwise_pushd module
* [c84cc7e] MT#7505 add .gitreview file
* [2c0bf78] MT#8951 sipwise_vcard_cusax: add numbers, name and email
* [31fb42e] MT#8951 fix vcard format and querys
* [963cfd4] MT#8851 mod_sipwise_pushd: fix 0d8220219aeb2 domain query
* [4201b42] MT#8851 mod_sipwise_pushd: fix 0d8220219aeb2 domain query call
* [39e4b7d] MT#8851 pushd: use from info when available if origin is null
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Thu, 09 Oct 2014 16:52:28 +0200
ngcp-prosody-modules (1.5.0.0+0~mr3.5.0.0) unstable; urgency=low
[ Victor Seva ]
* [f9d8739] MT#7545 Adapt queries to the new voip_pbx_groups schema.
* [9a736c5] MT#6431 restart prosody service after instalation
* [a02bb76] MT#7545 remove pilot from the list of subscribers
* [fce80c1] MT#6431 fix postinst script if node is not active
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Thu, 10 Jul 2014 15:35:26 +0200
ngcp-prosody-modules (1.4.0.0+0~mr3.4.0.0) unstable; urgency=low
[ Victor Seva ]
* [47678b7] MT#6285 add mod_log_auth plugin
[ Sipwise Jenkins Builder ]
-- Sipwise Jenkins Builder <jenkins@sipwise.com> Mon, 26 May 2014 15:58:51 +0200
ngcp-prosody-modules (1.3.0.0+0~mr3.3.0.0) unstable; urgency=low
[ Victor Seva ]
* [47f488b] Add licence note on source files
* [8dd77ca] MT#6409 Create mod_sipwise_admin_telnet
* [f4cef17] MT#6411 Add mod_sipwise_redis_sessions module.
* [efb226e] MT#6285 add mod_log_auth plugin
* [f53d100] MT#7053 add database name to the queries. Use auth_sql prosody config.
[ Michael Prokop ]
* [80bcd45] MT#6511 Bump Standards-Version to 3.9.5
-- Alexander Lutay <alutay@sipwise.com> Wed, 14 May 2014 11:10:31 +0200
ngcp-prosody-modules (1.2.0.0+0~mr3.3.0.0) unstable; urgency=low
[ Andreas Granig ]
* [68fb4e4] MT#5811 Add xmpp-over-websocket plugin.
-- Alexander Lutay <alutay@sipwise.com> Fri, 21 Mar 2014 15:42:58 +0100
ngcp-prosody-modules (1.1.0) unstable; urgency=low
[ Victor Seva ]
* [09d8ccd] Initial version of sipwise_groups module.
[ Andreas Granig ]
* [c71c975] Update copyright to cover all files.
-- Victor Seva <vseva@sipwise.com> Tue, 29 Oct 2013 14:59:54 +0100
ngcp-prosody-modules (1.0.1) unstable; urgency=low
* [dea7650] Implicitely add domain with "search" prefix.
-- Andreas Granig <agranig@sipwise.com> Thu, 08 Aug 2013 00:03:08 +0200
ngcp-prosody-modules (1.0.0) unstable; urgency=low
* [804143c] Remove authentication by number.
* [28b5fc5] Implement XEP-0055 with implicit rewrite rules.
-- Andreas Granig <agranig@sipwise.com> Wed, 07 Aug 2013 22:38:07 +0200
ngcp-prosody-modules (0.0.1) unstable; urgency=low
* Initial release
-- Michael Prokop <mika@sipwise.com> Tue, 02 Jul 2013 01:54:04 +0200

1
debian/clean vendored

@ -1 +0,0 @@
debian/README_mod_*.*

58
debian/control vendored

@ -1,58 +0,0 @@
Source: ngcp-prosody-modules
Section: net
Priority: optional
Maintainer: Sipwise Development Team <support@sipwise.com>
Homepage: https://www.sipwise.com/
Standards-Version: 4.6.2
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
quilt,
Package: ngcp-prosody-modules
Architecture: all
Depends:
lua-bitop,
lua-dbi-common,
lua-dbi-mysql,
lua-ngcp-kamailio,
lua-redis,
lua-rex-pcre | lua-rex-pcre2,
lua-sec,
lua5.2,
prosody (>= 0.11~),
${misc:Depends},
Description: ngcp modules for the prosody Jabber/XMPP server
This package provides modules for the Prosody Jabber/XMPP server
Prosody, especially for usage with the Sipwise Next Generation
Communication Platform.
.
The following modules are available:
.
* mod_sipwise_admin_telnet: admin_telnet with inteface selection
* mod_sipwise_groups.lua: inserts PBX groups to the user contact list
* mod_sipwise_lastactivity: XEP-0012
* mod_sipwise_pushd: push notification to mobile users
* mod_sipwise_redis_sessions: keep server's session info on redis
* mod_sipwise_vcard_cusax.lua: return a vcard containing SIP URIs for
phone/video of the requested user
* mod_sipwise_vhosts_sql.lua: load vhosts from DB on startup of Prosody
XMPP server
* mod_sipwise_vjud.lua: implement Jabber Search with implicit number
normalization using the NGCP rewrite rules
.
The following modules are from prosody-modules:
.
* mod_auth_sql.lua: simple SQL Authentication module
* mod_blocking: allows the client to manage a simple
list of blocked JIDs
* mod_carbons_adhoc
* mod_carbons_copies
* mod_filter_chatstates: will filter Chat State Notifications out
while the session is considered inactive
* mod_limit_auth: lets you put a per-IP limit on the number of
failed authentication attempts
* mod_log_auth: logs IP address in a failed authentication attempt
* mod_smacks: XEP-0198: Stream Management
* mod_throttle_presence: cuts down on presence traffic when clients
indicate they are inactive

98
debian/copyright vendored

@ -1,98 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: https://www.sipwise.com/
Upstream-Name: ngcp-prosody
Upstream-Contact: Sipwise GmbH <development@sipwise.com>
Files:
*
Copyright:
2013-2023 Sipwise GmbH, Austria
License: MIT
Files:
plugins/mod_auth_sql.lua
Copyright:
2011 Tomasz Sterna <tomek@xiaoka.com>
2011 Waqas Hussain <waqas20@gmail.com>
2013-2023 Sipwise GmbH, Austria
License: MIT
Files:
plugins/mod_sipwise_vcard_cusax.lua
Copyright:
2008-2010 Matthew Wild <mwild1@gmail.com>
2008-2010 Waqas Hussain <waqas20@gmail.com>
2013-2023 Sipwise GmbH, Austria
License: MIT
Files:
plugins/mod_carbons.lua
Copyright:
2011 Kim Alvefur <zash@zash.se>
License: MIT
Files:
plugins/mod_carbons_copies.lua
Copyright:
2012 Michael Holzt <kju@fqdn.org>
2014 Florian Zeitz <florob@babelmonkeys.de>
License: MIT
Files:
plugins/mod_carbons_adhoc.lua
Copyright:
2012 Michael Holzt <kju@fqdn.org>
2014 Florian Zeitz <florob@babelmonkeys.de>
License: MIT
Files:
plugins/mod_sipwise_lastactivity.lua
Copyright:
2008-2010 Matthew Wild <mwild1@gmail.com>
2008-2010 Waqas Hussain <waqas20@gmail.com>
2015-2023 Sipwise GmbH, Austria
License: MIT
Files:
plugins/mod_log_auth.lua
Copyright:
2013 Kim Alvefur <zash@zash.se>
License: MIT
Files:
plugins/mod_smacks.lua
Copyright:
2010-2015 Matthew Wild <mwild1@gmail.com>
2010 Waqas Hussain <waqas20@gmail.com>
2012-2015 Kim Alvefur <zash@zash.se>
2012 Thijs Alkemade <thijsalkemade@gmail.com>
2014 Florian Zeitz <florob@babelmonkeys.de>
License: MIT
Files:
plugins/mod_blocking.lua
Copyright:
2010-2015 Kim Alvefur <zash@zash.se>
2013 Tobias Markmann <tm@ayena.de>
2011 Paul Aurich <paul@darkrain42.org>
2010 Waqas Hussain <waqas20@gmail.com>
2010 Matthew Wild <mwild1@gmail.com>
License: MIT
License: MIT
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1 +0,0 @@
debian/README_mod*

@ -1,2 +0,0 @@
plugins/* /usr/lib/prosody/modules/
util/* /usr/lib/prosody/util/

@ -1,21 +0,0 @@
From: Sipwise Development Team <support@sipwise.com>
Date: Wed, 11 Oct 2023 09:52:08 +0200
Subject: fix_mod_carbons_adhoc_call_to_adhoc
Change-Id: If2ce4cf8dff63c3a8491845d76f0b33b472dc8b6
---
plugins/mod_carbons_adhoc/mod_carbons_adhoc.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/mod_carbons_adhoc/mod_carbons_adhoc.lua b/plugins/mod_carbons_adhoc/mod_carbons_adhoc.lua
index 406a0b0..e8af962 100644
--- a/plugins/mod_carbons_adhoc/mod_carbons_adhoc.lua
+++ b/plugins/mod_carbons_adhoc/mod_carbons_adhoc.lua
@@ -35,6 +35,6 @@ local function adhoc_status(self, data, state)
end
local status_desc = adhoc_new("Carbons: Get Status",
- "mod_carbons_adhoc#status", adhoc_status);
+ "mod_carbons_adhoc#status", adhoc_status, 'any');
module:add_item("adhoc", status_desc);

@ -1,37 +0,0 @@
From: Andreas Granig <agranig@sipwise.com>
Date: Wed, 7 Aug 2013 22:34:34 +0200
Subject: sipwise custom auth
---
plugins/mod_auth_sql/mod_auth_sql.lua | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/plugins/mod_auth_sql/mod_auth_sql.lua b/plugins/mod_auth_sql/mod_auth_sql.lua
index 2947a91..631768d 100644
--- a/plugins/mod_auth_sql/mod_auth_sql.lua
+++ b/plugins/mod_auth_sql/mod_auth_sql.lua
@@ -71,10 +71,13 @@ local function getsql(sql, ...)
end
local function get_password(username)
- local stmt, err = getsql("SELECT `password` FROM `authreg` WHERE `username`=? AND `realm`=?", username, module.host);
+ local stmt, err = getsql("SELECT `s`.`password`, COALESCE(`p`.`value`, 0) AS `locklevel` FROM `subscriber` AS `s` LEFT JOIN usr_preferences AS `p` ON `p`.`username` = `s`.`username` AND `p`.`domain` = `s`.`domain` AND `p`.`attribute` = 'lock' WHERE `s`.`username`= ? AND lower(`s`.`domain`)= ?", username, module.host);
if stmt then
for row in stmt:rows(true) do
- return row.password;
+ local locklevel = 0 + row.locklevel;
+ if locklevel < 4 then
+ return row.password;
+ end
end
end
end
@@ -109,7 +112,7 @@ function provider.get_sasl_handler()
end
function provider.users()
- local stmt, err = getsql("SELECT `username` FROM `authreg` WHERE `realm`=?", module.host);
+ local stmt, err = getsql("SELECT `username` FROM `subscriber` WHERE lower(`domain`)=?", module.host);
if stmt then
local next, state = stmt:rows(true)
return function()

@ -1,2 +0,0 @@
mod_auth_sql.path
fix_mod_carbons_adhoc_call_to_adhoc.patch

16
debian/rules vendored

@ -1,16 +0,0 @@
#!/usr/bin/make -f
# Uncomment this to turn on verbose mode.
# export DH_VERBOSE=1
%:
dh $@ --with quilt
override_dh_auto_configure:
while read module ; do \
if [ -f plugins/$$module/README.markdown ] ; then \
cp plugins/$$module/README.markdown debian/README_$$module.markdown; \
fi; \
done < prosody-modules.list; \
execute_after_dh_install:
find debian/ngcp-prosody-modules/ -name README.\* -delete

@ -1 +0,0 @@
3.0 (native)

@ -1,40 +0,0 @@
---
labels:
- 'Type-Auth'
- 'Stage-Stable'
summary: SQL Database authentication module
...
Introduction
============
Allow client authentication to be handled by an SQL database query.
Unlike mod\_storage\_sql (which is supplied with Prosody) this module
allows for custom schemas (though currently it is required to edit the
source).
Configuration
=============
As with all auth modules, there is no need to add this to
modules\_enabled. Simply add in the global section, or for the relevant
hosts:
authentication = "sql"
This module reuses the database configuration of
[mod\_storage\_sql](http://prosody.im/doc/modules/mod_storage_sql) (the
'sql' option), which you can set even if you are not using SQL as
Prosody's primary storage backend.
The query is currently hardcoded in the module, so you will need to edit
the module to change it. The default query is compatible with jabberd2
DB schema.
Compatibility
=============
----- -------
0.8 Works
----- -------

@ -1,125 +0,0 @@
-- Simple SQL Authentication module for Prosody IM
-- Copyright (C) 2011 Tomasz Sterna <tomek@xiaoka.com>
-- Copyright (C) 2011 Waqas Hussain <waqas20@gmail.com>
--
local log = require "util.logger".init("auth_sql");
local new_sasl = require "util.sasl".new;
local DBI = require "DBI"
local connection;
local params = module:get_option("auth_sql", module:get_option("sql"));
local resolve_relative_path = require "core.configmanager".resolve_relative_path;
local function test_connection()
if not connection then return nil; end
if connection:ping() then
return true;
else
module:log("debug", "Database connection closed");
connection = nil;
end
end
local function connect()
if not test_connection() then
prosody.unlock_globals();
local dbh, err = DBI.Connect(
params.driver, params.database,
params.username, params.password,
params.host, params.port
);
prosody.lock_globals();
if not dbh then
module:log("debug", "Database connection failed: %s", tostring(err));
return nil, err;
end
module:log("debug", "Successfully connected to database");
dbh:autocommit(true); -- don't run in transaction
connection = dbh;
return connection;
end
end
do -- process options to get a db connection
params = params or { driver = "SQLite3" };
if params.driver == "SQLite3" then
params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
end
assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
assert(connect());
end
local function getsql(sql, ...)
if params.driver == "PostgreSQL" then
sql = sql:gsub("`", "\"");
end
if not test_connection() then connect(); end
-- do prepared statement stuff
local stmt, err = connection:prepare(sql);
if not stmt and not test_connection() then error("connection failed"); end
if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end
-- run query
local ok, err = stmt:execute(...);
if not ok and not test_connection() then error("connection failed"); end
if not ok then return nil, err; end
return stmt;
end
local function get_password(username)
local stmt, err = getsql("SELECT `password` FROM `authreg` WHERE `username`=? AND `realm`=?", username, module.host);
if stmt then
for row in stmt:rows(true) do
return row.password;
end
end
end
provider = {};
function provider.test_password(username, password)
return password and get_password(username) == password;
end
function provider.get_password(username)
return get_password(username);
end
function provider.set_password(username, password)
return nil, "Setting password is not supported.";
end
function provider.user_exists(username)
return get_password(username) and true;
end
function provider.create_user(username, password)
return nil, "Account creation/modification not supported.";
end
function provider.get_sasl_handler()
local profile = {
plain = function(sasl, username, realm)
local password = get_password(username);
if not password then return "", nil; end
return password, true;
end
};
return new_sasl(module.host, profile);
end
function provider.users()
local stmt, err = getsql("SELECT `username` FROM `authreg` WHERE `realm`=?", module.host);
if stmt then
local next, state = stmt:rows(true)
return function()
for row in next, state do
return row.username;
end
end
end
return stmt, err;
end
module:provides("auth", provider);

@ -1,54 +0,0 @@
---
labels:
- 'Stage-Alpha'
summary: 'XEP-0191: Simple Communications Blocking support'
...
Introduction
============
Privacy lists are a widely implemented protocol for instructing your
server on blocking communications with selected users and services.
However experience has shown that the power and flexibility of the
rule-based system that privacy lists allow is very often much more
complex than the user needs, and that in most cases a simple block on
all communications to or from a list of specified JIDs would suffice.
Such a protocol would also allow much simpler user interface design than
the current attempts at full privacy list interfaces.
Details
=======
Simple Communications Blocking was developed to solve the above issues,
and allows the client to manage a simple list of blocked JIDs. This
plugin implements support for that protocol in Prosody, however the
actual blocking is still managed by mod\_privacy, so it is **required**
for that plugin to be loaded (this may change in future).
An XEP-0191 implementation without dependency on mod\_privacy is
available in Prosody 0.10 as [mod\_blocklist][doc:modules:mod_blocklist].
Configuration
=============
Simply ensure that mod\_privacy (or [mod\_privacy\_lists] in 0.10+) and
mod\_blocking are loaded in your modules\_enabled list:
modules_enabled = {
-- ...
"privacy", -- or privacy_lists in Prosody 0.10+
"blocking",
-- ...
Compatibility
=============
------ ---------------------------------------------
0.10 Works but will conflict with mod\_blocklist
0.9 Works
0.8 Works
0.7 Works
0.6 Doesn't work
------ ---------------------------------------------

@ -1,173 +0,0 @@
local jid_split = require "util.jid".split;
local st = require "util.stanza";
local datamanager = require"util.datamanager";
local xmlns_blocking = "urn:xmpp:blocking";
module:add_feature("urn:xmpp:blocking");
-- Add JID to default privacy list
function add_blocked_jid(username, host, jid)
local privacy_lists = datamanager.load(username, host, "privacy") or {lists = {}};
local default_list_name = privacy_lists.default;
if not privacy_lists.lists then
privacy_lists.lists = {}
end
if not default_list_name then
default_list_name = "blocklist";
privacy_lists.default = default_list_name;
end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then
default_list = { name = default_list_name, items = {} };
privacy_lists.lists[default_list_name] = default_list;
end
local items = default_list.items;
local order = items[1] and items[1].order or 0; -- Must come first
for i=1,#items do -- order must be unique
local item = items[i];
item.order = item.order + 1;
if item.type == "jid" and item.action == "deny" and item.value == jid then
return false;
end
end
table.insert(items, 1, { type = "jid"
, action = "deny"
, value = jid
, message = false
, ["presence-out"] = false
, ["presence-in"] = false
, iq = false
, order = order
});
datamanager.store(username, host, "privacy", privacy_lists);
return true;
end
-- Remove JID from default privacy list
function remove_blocked_jid(username, host, jid)
local privacy_lists = datamanager.load(username, host, "privacy") or {};
local default_list_name = privacy_lists.default;
if not default_list_name then return; end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then return; end
local items = default_list.items;
local item, removed = nil, false;
for i=1,#items do -- order must be unique
item = items[i];
if item.type == "jid" and item.action == "deny" and item.value == jid then
table.remove(items, i);
removed = true;
break;
end
end
if removed then
datamanager.store(username, host, "privacy", privacy_lists);
end
return removed;
end
function remove_all_blocked_jids(username, host)
local privacy_lists = datamanager.load(username, host, "privacy") or {};
local default_list_name = privacy_lists.default;
if not default_list_name then return; end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then return; end
local items = default_list.items;
local item;
for i=#items,1,-1 do -- order must be unique
item = items[i];
if item.type == "jid" and item.action == "deny" then
table.remove(items, i);
end
end
datamanager.store(username, host, "privacy", privacy_lists);
return true;
end
function get_blocked_jids(username, host)
-- Return array of blocked JIDs in default privacy list
local privacy_lists = datamanager.load(username, host, "privacy") or {};
local default_list_name = privacy_lists.default;
if not default_list_name then return {}; end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then return {}; end
local items = default_list.items;
local item;
local jid_list = {};
for i=1,#items do -- order must be unique
item = items[i];
if item.type == "jid" and item.action == "deny" then
jid_list[#jid_list+1] = item.value;
end
end
return jid_list;
end
local function send_push_iqs(username, host, command_type, jids)
local bare_jid = username.."@"..host;
local stanza_content = st.stanza(command_type, { xmlns = xmlns_blocking });
for _, jid in ipairs(jids) do
stanza_content:tag("item", { jid = jid }):up();
end
for resource, session in pairs(prosody.bare_sessions[bare_jid].sessions) do
local iq_push_stanza = st.iq({ type = "set", to = bare_jid.."/"..resource, id = "blocking-push" });
iq_push_stanza:add_child(stanza_content);
session.send(iq_push_stanza);
end
end
function handle_blocking_command(event)
local session, stanza = event.origin, event.stanza;
local username, host = jid_split(stanza.attr.from);
if stanza.attr.type == "set" then
if stanza.tags[1].name == "block" then
local block = stanza.tags[1];
local block_jid_list = {};
for item in block:childtags() do
block_jid_list[#block_jid_list+1] = item.attr.jid;
end
if #block_jid_list == 0 then
session.send(st.error_reply(stanza, "modify", "bad-request"));
else
for _, jid in ipairs(block_jid_list) do
add_blocked_jid(username, host, jid);
end
session.send(st.reply(stanza));
send_push_iqs(username, host, "block", block_jid_list);
end
return true;
elseif stanza.tags[1].name == "unblock" then
local unblock = stanza.tags[1];
local unblock_jid_list = {};
for item in unblock:childtags() do
unblock_jid_list[#unblock_jid_list+1] = item.attr.jid;
end
if #unblock_jid_list == 0 then
remove_all_blocked_jids(username, host);
else
for _, jid_to_unblock in ipairs(unblock_jid_list) do
remove_blocked_jid(username, host, jid_to_unblock);
end
end
session.send(st.reply(stanza));
send_push_iqs(username, host, "unblock", unblock_jid_list);
return true;
end
elseif stanza.attr.type == "get" and stanza.tags[1].name == "blocklist" then
local reply = st.reply(stanza):tag("blocklist", { xmlns = xmlns_blocking });
local blocked_jids = get_blocked_jids(username, host);
for _, jid in ipairs(blocked_jids) do
reply:tag("item", { jid = jid }):up();
end
session.send(reply);
return true;
end
end
module:hook("iq/self/urn:xmpp:blocking:blocklist", handle_blocking_command);
module:hook("iq/self/urn:xmpp:blocking:block", handle_blocking_command);
module:hook("iq/self/urn:xmpp:blocking:unblock", handle_blocking_command);

@ -1,40 +0,0 @@
-- Implement a Adhoc command which will show a user
-- the status of carbons generation in regard to his clients
--
-- Copyright (C) 2012 Michael Holzt
--
-- This file is MIT/X11 licensed.
local st = require "util.stanza";
local jid_bare = require "util.jid".bare;
local adhoc_new = module:require "adhoc".new;
local xmlns_carbons_v2 = "urn:xmpp:carbons:2";
local xmlns_carbons_v1 = "urn:xmpp:carbons:1";
local xmlns_carbons_v0 = "urn:xmpp:carbons:0";
local bare_sessions = bare_sessions;
local function adhoc_status(self, data, state)
local bare_jid = jid_bare(data.from);
local user_sessions = bare_sessions[bare_jid];
local result = "";
user_sessions = user_sessions and user_sessions.sessions;
for _, session in pairs(user_sessions) do
if session.full_jid then
result = result .. session.full_jid .. ": " ..
( (session.want_carbons == xmlns_carbons_v2 and "v2" ) or
(session.want_carbons == xmlns_carbons_v1 and "v1" ) or
(session.want_carbons == xmlns_carbons_v0 and "v0" ) or
"none" ) .. "\n";
end
end
return { info = result, status = "completed" };
end
local status_desc = adhoc_new("Carbons: Get Status",
"mod_carbons_adhoc#status", adhoc_status);
module:add_item("adhoc", status_desc);

@ -1,63 +0,0 @@
-- Send carbons v0 style copies of incoming messages to clients which
-- are not (yet) capable of Message Carbons (XEP-0280).
--
-- This extension integrates with the mod_carbons plugin in such a way
-- that a client capable of Message Carbons will not get a v0 copy.
--
-- This extension can be enabled for all users by default by setting
-- carbons_copies_default = true.
--
-- Alternatively or additionally setting carbons_copies_adhoc = true
-- will allow the user to enable or disable copies through Adhoc
-- commands.
--
-- Copyright (C) 2012 Michael Holzt
--
-- This file is MIT/X11 licensed.
local jid_split = require "util.jid".split;
local dm_load = require "util.datamanager".load;
local dm_store = require "util.datamanager".store;
local adhoc_new = module:require "adhoc".new;
local xmlns_carbons_v0 = "urn:xmpp:carbons:0";
local storename = "mod_carbons_copies";
local function toggle_copies(data, on)
local username, hostname, resource = jid_split(data.from);
dm_store(username, hostname, storename, { enabled = on });
end
local function adhoc_enable_copies(self, data, state)
toggle_copies(data, true);
return { info = "Copies are enabled for you now.\nPlease restart/reconnect clients.", status = "completed" };
end
local function adhoc_disable_copies(self, data, state)
toggle_copies(data, false);
return { info = "Copies are disabled for you now.\nPlease restart/reconnect clients.", status = "completed" };
end
module:hook("resource-bind", function(event)
local session = event.session;
local username, hostname, resource = jid_split(session.full_jid);
local store = dm_load(username, hostname, storename) or
{ enabled =
module:get_option_boolean("carbons_copies_default") };
if store.enabled then
session.want_carbons = xmlns_carbons_v0;
module:log("debug", "%s enabling copies", session.full_jid);
end
end);
-- Adhoc-Support
if module:get_option_boolean("carbons_copies_adhoc") then
local enable_desc = adhoc_new("Carbons: Enable Copies",
"mod_carbons_copies#enable", adhoc_enable_copies);
local disable_desc = adhoc_new("Carbons: Disable Copies",
"mod_carbons_copies#disable", adhoc_disable_copies);
module:add_item("adhoc", enable_desc);
module:add_item("adhoc", disable_desc);
end

@ -1,26 +0,0 @@
---
summary: Drop chat states from messages to inactive sessions
...
Introduction
============
Some mobile XMPP client developers consider [Chat State
Notifications](http://xmpp.org/extensions/xep-0085.html) to be a waste
of power and bandwidth, especially when the user is not actively looking
at their device. This module will filter them out while the session is
considered inactive. It depends on [mod\_csi](/mod_csi.html) for
deciding when to begin and end filtering.
Configuration
=============
There is no configuration for this module, just add it to
modules\_enabled as normal.
Compatibility
=============
----- -------
0.9 Works
----- -------

@ -1,31 +0,0 @@
local filters = require "util.filters";
local st = require "util.stanza";
module:depends("csi");
local function chatstate_tag_filter(tag)
if tag.attr.xmlns ~= "http://jabber.org/protocol/chatstates" then
return tag;
end
end
local function filter_chatstates(stanza)
if stanza.name == "message" then
stanza = st.clone(stanza);
stanza:maptags(chatstate_tag_filter);
if #stanza.tags == 0 then
return nil;
end
end
return stanza;
end
module:hook("csi-client-inactive", function (event)
local session = event.origin;
filters.add_filter(session, "stanzas/out", filter_chatstates);
end);
module:hook("csi-client-active", function (event)
local session = event.origin;
filters.remove_filter(session, "stanzas/out", filter_chatstates);
end);

@ -1,35 +0,0 @@
---
summary: Throttle authentication attempts with optional tarpit
...
Introduction
============
This module lets you put a per-IP limit on the number of failed
authentication attempts.
It features an optioanal
[tarpit](https://en.wikipedia.org/wiki/Tarpit_%28networking%29), i.e.
waiting some time before returning an "authentication failed" response.
Configuration
=============
``` {.lua}
modules_enabled = {
-- your other modules
"limit_auth";
}
limit_auth_period = 30 -- over 30 seconds
limit_auth_max = 5 -- tolerate no more than 5 failed attempts
-- Will only work with Prosody trunk:
limit_auth_tarpit_delay = 10 -- delay answer this long
```
Compatibility
=============
Requires 0.9 or later. The tarpit feature requires Prosody trunk.

@ -1,58 +0,0 @@
-- mod_limit_auth
local st = require"util.stanza";
local new_throttle = require "util.throttle".create;
local period = math.max(module:get_option_number(module.name.."_period", 30), 0);
local max = math.max(module:get_option_number(module.name.."_max", 5), 1);
local tarpit_delay = module:get_option_number(module.name.."_tarpit_delay", nil);
if tarpit_delay then
local waiter = require "util.async".waiter;
local delay = tarpit_delay;
function tarpit_delay()
local wait, done = waiter();
module:add_timer(delay, done);
wait();
end
else
function tarpit_delay() end
end
local throttles = module:shared"throttles";
local reply = st.stanza("failure", { xmlns = "urn:ietf:params:xml:ns:xmpp-sasl" }):tag("temporary-auth-failure");
local function get_throttle(ip)
local throttle = throttles[ip];
if not throttle then
throttle = new_throttle(max, period);
throttles[ip] = throttle;
end
return throttle;
end
module:hook("stanza/urn:ietf:params:xml:ns:xmpp-sasl:auth", function (event)
local origin = event.origin;
if origin.type ~= "c2s_unauthed" then return end
if not get_throttle(origin.ip):peek(1) then
origin.log("warn", "Too many authentication attepmts for ip %s", origin.ip);
tarpit_delay();
origin.send(reply);
return true;
end
end, 10);
module:hook("authentication-failure", function (event)
get_throttle(event.session.ip):poll(1);
end);
module:add_timer(14400, function (now)
local old = now - 86400;
for ip, throttle in pairs(throttles) do
if throttle.t < old then
throttles[ip] = nil;
end
end
end);

@ -1,49 +0,0 @@
---
labels:
- 'Stage-Stable'
summary: Log failed authentication attempts with their IP address
...
Introduction
============
Prosody doesn't write IP addresses to its log file by default for
privacy reasons (unless debug logging is enabled).
This module enables logging of the IP address in a failed authentication
attempt so that those trying to break into accounts for example can be
blocked.
fail2ban configuration
======================
fail2ban is a utility for monitoring log files and automatically
blocking "bad" IP addresses at the firewall level.
With this module enabled in Prosody you can use the following example
configuration for fail2ban:
# /etc/fail2ban/filter.d/prosody-auth.conf
# Fail2Ban configuration file for prosody authentication
[Definition]
failregex = Failed authentication attempt \(not-authorized\) for user .* from IP: <HOST>
ignoreregex =
And at the appropriate place (usually the bottom) of
/etc/fail2ban/jail.conf add these lines:
[prosody]
enabled = true
port = 5222
filter = prosody-auth
logpath = /var/log/prosody/prosody*.log
maxretry = 6
Compatibility
-------------
------- --------------
trunk Works
0.9 Works
0.8 Doesn't work
------- --------------

@ -1,19 +0,0 @@
local mode = module:get_option_string("log_auth_ips", "failure");
assert(({ all = true, failure = true, success = true })[mode],
"Unknown log mode: "..tostring(mode).." - valid modes are 'all', 'failure', 'success'");
if mode == "failure" or mode == "all" then
module:hook("authentication-failure", function (event)
local session = event.session;
local username = session.username or session.sasl_handler and session.sasl_handler.username or "?";
session.log("info", "Failed authentication attempt (%s) for user %s@%s from IP: %s",
event.condition or "unknown-condition", username, module.host, session.ip or "?");
end);
end
if mode == "success" or mode == "all" then
module:hook("authentication-success", function (event)
local session = event.session;
session.log("info", "Successful authentication as %s@%s from IP: %s", session.username, module.host, session.ip or "?");
end);
end

@ -1,196 +0,0 @@
-- Prosody IM
-- Copyright (C) 2015 Robert Norris <robn@robn.io>
-- Copyright (C) 2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:depends("sipwise_redis_sessions");
module:depends("sipwise_redis_mucs");
local redis_sessions = module:shared("/*/sipwise_redis_sessions/redis_sessions");
local redis_mucs = module:shared("/*/sipwise_redis_mucs/redis_mucs");
local hosts = prosody.hosts;
local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local fire_event = prosody.events.fire_event;
local st = require "util.stanza";
local set = require "util.set";
local shard_name = module:get_option("shard_name", nil);
if not shard_name then
error("shard_name not configured", 0);
end
local persistent_rooms = module:open_store("persistent", "map");
module:log("info", "%s added to shard %s", module.host, shard_name);
local function build_query_result(rooms, stanza)
local xmlns = 'http://jabber.org/protocol/disco#items';
local s = stanza:query(xmlns);
for room in rooms do
s:tag("item", {jid=room}):up();
end
return stanza;
end
local function get_local_rooms(host)
local rooms = set.new();
for room in hosts[host].modules.muc.live_rooms() do
rooms:add(room.jid);
end
return rooms;
end
local function is_local_room(room_jid)
local node, host, _ = jid_split(room_jid);
if not node then return false; end
if not hosts[host] or not hosts[host].modules.muc then
return false;
end
for room in hosts[host].modules.muc.live_rooms() do
if room.jid == room_jid then return true; end
end
return false;
end
local function is_persistent_room(jid)
local room_jid = jid_bare(jid);
local res = persistent_rooms:get(nil, room_jid);
module:log("debug", "[%s] is_persistent:%s", room_jid, tostring(res));
if res then return true else return false end;
end
local function check_redis_info(room_jid)
local rhost = redis_mucs.get_room_host(room_jid);
if not rhost then return; end
if rhost ~= shard_name then
module:log("info", "clean wrong shard info for room[%s]", room_jid);
redis_mucs.clean_room_host(room_jid, rhost);
redis_mucs.set_room_host(room_jid, shard_name);
end
end
local function handle_room_event(event)
local to = event.stanza.attr.to;
local node, host, _ = jid_split(to);
local rhost, room_jid;
if node then
room_jid = jid_bare(to);
if is_local_room(room_jid) then
module:log("debug", "room[%s] is hosted here. Nothing to do", room_jid);
check_redis_info(room_jid);
return nil;
end
module:log("debug", "looking up target room shard for %s", to);
rhost = redis_mucs.get_room_host(to);
else
-- TODO: remove me this is just for check if there are missing rooms
local rooms = set.union(get_local_rooms(host),
redis_mucs.get_rooms(host));
module:log("debug", "rooms: %s", tostring(rooms));
local stanza = build_query_result(rooms, st.reply(event.stanza));
module:log("debug", "reply[%s]", tostring(stanza));
event.origin.send(stanza);
return true;
end
if not rhost then
assert(room_jid);
if is_persistent_room(room_jid) then
module:log("info",
"restore missing info for persistent room[%s]", room_jid);
redis_mucs.set_room_host(room_jid, shard_name);
else
module:log("debug", "room[%s] not found, nothing to do", room_jid);
end
return nil;
end
if rhost == shard_name then
module:log("debug", "room[%s] is hosted here, nothing to do", room_jid);
return nil
end
fire_event("shard/send", { shard = rhost, stanza = event.stanza });
return true;
end
local function handle_event (event)
local to = event.stanza.attr.to;
local node, host, resource = jid_split(to);
local stop_process_local;
if not host then
module:log("debug", "no host, nothing to do here");
return nil
end
if hosts[host].modules.muc then
module:log("debug", "to MUC %s detected", host);
return handle_room_event(event);
end
if resource and prosody.full_sessions[to] then
module:log("debug", "%s has a session here, nothing to do", to);
return nil
end
if not node then
module:log("debug", "no node, nothing to do here");
return nil
end
module:log("debug", "looking up target shard for %s", to);
local rhosts = redis_sessions.get_hosts(to);
for shard,resources in pairs(rhosts) do
if shard and shard ~= shard_name then
for _,r in pairs(resources) do
local stanza_c = st.clone(event.stanza);
stanza_c.attr.to = node..'@'..host..'/'..r;
module:log("debug", "target shard for %s is %s",
stanza_c.attr.to ,shard);
fire_event("shard/send", { shard = shard, stanza = stanza_c });
stop_process_local = true;
end
end
end
if prosody.bare_sessions[jid_bare(to)] then
module:log("debug", "%s has a bare session here."..
" stanza will be processed here too", to);
return nil;
end
return stop_process_local;
end
local function handle_shard_error(event)
local server_id = event.shard
local stanza = event.stanza
local jid = stanza.attr.to;
local _, host, _ = jid_split(jid);
if hosts[host].modules.muc then
module:log("debug",
"to MUC %s detected, clean conference %s", host, jid);
redis_mucs.clean_room_host(jid, server_id)
else
redis_sessions.clean_host(jid, server_id)
end
end
module:hook("iq/bare", handle_event, 1000);
module:hook("iq/full", handle_event, 1000);
module:hook("iq/host", handle_event, 1000);
module:hook("message/bare", handle_event, 1000);
module:hook("message/full", handle_event, 1000);
module:hook("message/host", handle_event, 1000);
module:hook("presence/bare", handle_event, 1000);
module:hook("presence/full", handle_event, 1000);
module:hook("presence/host", handle_event, 1000);
module:hook_global("shard/error", handle_shard_error);
module:log("debug", "hooked at %s", module:get_host());

@ -1,230 +0,0 @@
-- Prosody IM
-- Copyright (C) 2015 Robert Norris <robn@robn.io>
-- Copyright (C) 2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:set_global();
local socket = require "socket"
local ut = require "ngcp.utils";
local logger = require "util.logger";
local st = require "util.stanza";
local new_xmpp_stream = require "util.xmppstream".new;
local wrapclient = require "net.server".wrapclient;
local log = module._log;
local shard_name = module:get_option("shard_name", nil);
if not shard_name then
error("shard_name not configured", 0);
end
local opt_keepalives = module:get_option_boolean("shard_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true));
local conns = {};
local queue = {};
local listener = {};
local sessions = module:shared("sessions");
local xmlns_shard = 'prosody:shard';
local stream_callbacks = { default_ns = xmlns_shard };
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
function stream_callbacks.error(session, error, data, _)
if session.destroyed then return; end
module:log("warn", "Error processing shard stream: %s", tostring(error));
if error == "no-stream" then
session:close("invalid-namespace");
elseif error == "parse-error" then
session.log("warn", "External shard %s XML parse error: %s", tostring(session.host), tostring(data));
session:close("not-well-formed");
elseif error == "stream-error" then
local condition, text = "undefined-condition";
for child in data:children() do
if child.attr.xmlns == xmlns_xmpp_streams then
if child.name ~= "text" then
condition = child.name;
else
text = child:get_text();
end
if condition ~= "undefined-condition" and text then
break;
end
end
end
text = condition .. (text and (" ("..text..")") or "");
session.log("info", "Session closed by remote with error: %s", text);
session:close(nil, text);
end
end
function stream_callbacks.streamclosed(session)
session.log("debug", "Received </stream:stream>");
session:close();
end
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
local function session_close(session, reason)
if session.destroyed then return; end
if session.conn then
if session.notopen then
session.send("<?xml version='1.0'?>");
session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
end
if reason then
if type(reason) == "string" then -- assume stream error
module:log("info", "Disconnecting component, <stream:error> is: %s", reason);
session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
elseif type(reason) == "table" then
if reason.condition then
local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
if reason.text then
stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
end
if reason.extra then
stanza:add_child(reason.extra);
end
module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
session.send(stanza);
elseif reason.name then -- a stanza
module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
session.send(reason);
end
end
end
session.send("</stream:stream>");
session.conn:close();
listener.ondisconnect(session.conn, "stream error");
end
end
function listener.onconnect(conn)
local shard = conn:ip();
local session = { type = "shard", conn = conn, send = function (data) return conn:write(tostring(data)); end, shard = shard };
-- Logging functions --
local conn_name = "sc"..tostring(session):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
session.close = session_close;
if opt_keepalives then
conn:setoption("keepalive", opt_keepalives);
end
session.log("info", "outgoing shard connection");
local stream = new_xmpp_stream(session, stream_callbacks);
session.stream = stream;
function session.data(_, data)
local ok, err = stream:feed(data);
if ok then return; end
module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
session:close("not-well-formed");
end
session.dispatch_stanza = stream_callbacks.handlestanza;
session.notopen = true;
session.send(st.stanza("stream:stream", {
to = conn:ip(),
["xmlns:stream"] = 'http://etherx.jabber.org/streams';
xmlns = xmlns_shard;
}):top_tag());
local _queue = queue[shard];
for _,s in pairs(_queue) do
conn:write(tostring(s));
end
queue[shard] = nil;
sessions[conn] = session;
end
function listener.onincoming(conn, data)
local session = sessions[conn];
session.data(conn, data);
end
function listener.ondisconnect(conn, err)
local session = sessions[conn];
if (session) then
(session.log or log)("info", "shard disconnected: %s (%s)", tostring(session.shard), tostring(err));
if session.on_destroy then session:on_destroy(err); end
sessions[conn] = nil;
conns[session.shard] = nil;
for k in pairs(session) do
if k ~= "log" and k ~= "close" then
session[k] = nil;
end
end
session.destroyed = true;
end
module:log("error", "connection lost");
module:fire_event("shard/disconnected", { reason = err, shard = conn:ip() });
end
local function connect(shard)
local conn = socket.tcp ( )
conn:settimeout ( 10 )
local ok, err = conn:connect (shard, 7473)
if not ok and err ~= "timeout" then
return nil, err;
end
local handler, _ = wrapclient ( conn , shard , 7473 , listener , "*a")
return handler;
end
module:hook_global("server-stopping", function(event)
local reason = event.reason;
for _, session in pairs(sessions) do
session:close{ condition = "system-shutdown", text = reason };
end
end, 1000);
local function handle_send(event)
local shard = event.shard;
local stanza = event.stanza;
module:log("debug", "got stanza for shard "..shard)
local conn = conns[shard];
if conn == nil then
module:log("debug", "connecting to "..shard.." for delivery");
local err;
conn, err = connect(shard);
if not conn then
module:log("error", "couldn't connect to "..shard..": "..err);
module:fire_event("shard/error", {shard = shard, stanza = stanza});
return;
end
conns[shard] = conn;
queue[shard] = {};
end
if stanza.attr.via then
local via = ut.explode(';', stanza.attr.via);
module:log("debug", "via:%s", ut.table.tostring(via));
if ut.table.contains(via, shard_name) then
module:log("error", "loop detected, stanza[%s]", stanza);
return;
end
table.insert(via, shard_name);
stanza.attr.via = ut.implode(';', via);
else
stanza.attr.via = shard_name;
end
module:log("debug", "new via:%s", stanza.attr.via);
local session = sessions[conn]
if session == nil then
table.insert(queue[shard], stanza)
else
conn:write(tostring(stanza));
end
end
module:hook_global("shard/send", handle_send, 1000);

@ -1,208 +0,0 @@
-- Prosody IM
-- Copyright (C) 2015 Robert Norris <robn@robn.io>
-- Copyright (C) 2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:set_global();
local logger = require "util.logger"
local log = logger.init("shard_server");
local st = require "util.stanza";
local jid_split = require "util.jid".split;
local new_xmpp_stream = require "util.xmppstream".new;
local uuid_gen = require "util.uuid".generate;
local core_process_stanza = prosody.core_process_stanza;
local hosts = prosody.hosts;
local traceback = debug.traceback;
local shard_name = module:get_option("shard_name", nil);
if not shard_name then
error("shard_name not configured", 0);
end
local opt_keepalives = module:get_option_boolean("shard_tcp_keepalives", module:get_option_boolean("tcp_keepalives", true));
local sessions = module:shared("sessions");
--- Network and stream part ---
local xmlns_shard = 'prosody:shard';
local listener = {};
--- Callbacks/data for xmppstream to handle streams for us ---
local stream_callbacks = { default_ns = xmlns_shard };
local xmlns_xmpp_streams = "urn:ietf:params:xml:ns:xmpp-streams";
function stream_callbacks.error(session, error, data, _)
if session.destroyed then return; end
module:log("warn", "Error processing component stream: %s", tostring(error));
if error == "no-stream" then
session:close("invalid-namespace");
elseif error == "parse-error" then
session.log("warn", "External component %s XML parse error: %s", tostring(session.host), tostring(data));
session:close("not-well-formed");
elseif error == "stream-error" then
local condition, text = "undefined-condition";
for child in data:children() do
if child.attr.xmlns == xmlns_xmpp_streams then
if child.name ~= "text" then
condition = child.name;
else
text = child:get_text();
end
if condition ~= "undefined-condition" and text then
break;
end
end
end
text = condition .. (text and (" ("..text..")") or "");
session.log("info", "Session closed by remote with error: %s", text);
session:close(nil, text);
end
end
function stream_callbacks.streamopened(session, attr)
if attr.to ~= shard_name then
session:close{ condition = "host-unknown", text = "unknown shard name "..tostring(attr.to) };
return;
end
session.host = attr.to;
session.streamid = uuid_gen();
session.notopen = nil;
end
function stream_callbacks.streamclosed(session)
session.log("debug", "Received </stream:stream>");
session:close();
end
local function handleerr(err) log("error", "Traceback[component]: %s", traceback(tostring(err), 2)); end
function stream_callbacks.handlestanza(_, stanza)
local to = stanza.attr.to;
local _, host = jid_split(to);
if not host then return end
local h = hosts[host];
local nh = {};
for k,v in pairs(h) do nh[k] = v; end
nh.type = "component";
return xpcall(function () return core_process_stanza(nh, stanza) end, handleerr);
end
--- Closing a component connection
local stream_xmlns_attr = {xmlns='urn:ietf:params:xml:ns:xmpp-streams'};
local default_stream_attr = { ["xmlns:stream"] = "http://etherx.jabber.org/streams", xmlns = stream_callbacks.default_ns, version = "1.0", id = "" };
local function session_close(session, reason)
if session.destroyed then return; end
if session.conn then
if session.notopen then
session.send("<?xml version='1.0'?>");
session.send(st.stanza("stream:stream", default_stream_attr):top_tag());
end
if reason then
if type(reason) == "string" then -- assume stream error
module:log("info", "Disconnecting component, <stream:error> is: %s", reason);
session.send(st.stanza("stream:error"):tag(reason, {xmlns = 'urn:ietf:params:xml:ns:xmpp-streams' }));
elseif type(reason) == "table" then
if reason.condition then
local stanza = st.stanza("stream:error"):tag(reason.condition, stream_xmlns_attr):up();
if reason.text then
stanza:tag("text", stream_xmlns_attr):text(reason.text):up();
end
if reason.extra then
stanza:add_child(reason.extra);
end
module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(stanza));
session.send(stanza);
elseif reason.name then -- a stanza
module:log("info", "Disconnecting component, <stream:error> is: %s", tostring(reason));
session.send(reason);
end
end
end
session.send("</stream:stream>");
session.conn:close();
listener.ondisconnect(session.conn, "stream error");
end
end
--- Component connlistener
function listener.onconnect(conn)
local _send = conn.write;
local session = { type = "shard", conn = conn, send = function (data) return _send(conn, tostring(data)); end };
-- Logging functions --
local conn_name = "ss"..tostring(session):match("[a-f0-9]+$");
session.log = logger.init(conn_name);
session.close = session_close;
if opt_keepalives then
conn:setoption("keepalive", opt_keepalives);
end
session.log("info", "incoming shard connection");
local stream = new_xmpp_stream(session, stream_callbacks);
session.stream = stream;
session.notopen = true;
function session.reset_stream()
session.notopen = true;
session.stream:reset();
end
function session.data(_, data)
local ok, err = stream:feed(data);
if ok then return; end
module:log("debug", "Received invalid XML (%s) %d bytes: %s", tostring(err), #data, data:sub(1, 300):gsub("[\r\n]+", " "):gsub("[%z\1-\31]", "_"));
session:close("not-well-formed");
end
session.dispatch_stanza = stream_callbacks.handlestanza;
sessions[conn] = session;
end
function listener.onincoming(conn, data)
local session = sessions[conn];
session.data(conn, data);
end
function listener.ondisconnect(conn, err)
local session = sessions[conn];
if session then
(session.log or log)("info", "component disconnected: %s (%s)", tostring(session.host), tostring(err));
if session.on_destroy then session:on_destroy(err); end
sessions[conn] = nil;
for k in pairs(session) do
if k ~= "log" and k ~= "close" then
session[k] = nil;
end
end
session.destroyed = true;
end
end
function listener.ondetach(conn)
sessions[conn] = nil;
end
module:provides("net", {
name = "shard";
private = true;
listener = listener;
default_port = 7473;
multiplex = {
pattern = "^<.*:stream.*%sxmlns%s*=%s*(['\"])"..xmlns_shard.."%1.*>";
};
});

File diff suppressed because it is too large Load Diff

@ -1,287 +0,0 @@
-- Load PBX groups from DDBB
-- Copyright (C) 2013 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local lookup_query = [[
SELECT g.username, s.username, d.domain
FROM provisioning.voip_subscribers AS s
LEFT JOIN provisioning.voip_domains AS d ON s.domain_id = d.id
LEFT JOIN provisioning.voip_pbx_groups AS ug ON s.id = ug.subscriber_id
LEFT JOIN provisioning.voip_subscribers AS g ON g.id = ug.group_id
LEFT JOIN provisioning.voip_usr_preferences as p ON p.subscriber_id = s.id
WHERE s.account_id = ? AND s.is_pbx_group = 0 AND s.is_pbx_pilot = 0
AND ug.group_id IS NOT NULL
AND (p.attribute_id = ? AND p.value = '1')
ORDER BY s.username;
]];
local lookup_with_dn_query = [[
SELECT s.username, d.domain, p.value
FROM provisioning.voip_subscribers AS s
LEFT JOIN provisioning.voip_domains AS d ON s.domain_id = d.id
LEFT JOIN provisioning.voip_usr_preferences as p ON p.subscriber_id = s.id
LEFT JOIN provisioning.voip_pbx_groups AS ug ON s.id = ug.subscriber_id
WHERE s.account_id = ? AND s.is_pbx_group = 0 AND s.is_pbx_pilot = 0
AND ug.group_id IS NOT NULL
AND p.attribute_id = ?
ORDER BY s.username;
]];
local lookup_user_group_query = [[
SELECT g.username
FROM provisioning.voip_subscribers AS s
LEFT JOIN provisioning.voip_domains AS d ON s.domain_id = d.id
LEFT JOIN provisioning.voip_pbx_groups AS ug ON s.id = ug.subscriber_id
LEFT JOIN provisioning.voip_subscribers AS g ON g.id = ug.group_id
WHERE s.account_id = ? AND
s.username = ? AND d.domain = ? AND
s.is_pbx_group = 0 AND s.is_pbx_pilot = 0 AND ug.group_id IS NOT NULL;
]];
local lookup_users_by_groups_query = [[
SELECT g.username, s.username, d.domain
FROM provisioning.voip_subscribers AS s
LEFT JOIN provisioning.voip_domains AS d ON s.domain_id = d.id
LEFT JOIN provisioning.voip_pbx_groups AS ug ON s.id = ug.subscriber_id
LEFT JOIN provisioning.voip_subscribers AS g ON g.id = ug.group_id
LEFT JOIN provisioning.voip_usr_preferences as p ON p.subscriber_id = s.id
WHERE s.account_id = ? AND s.is_pbx_group = 0 AND s.is_pbx_pilot = 0
AND ug.group_id IS NOT NULL
AND (p.attribute_id = ? AND p.value = '1') AND g.username in (?)
ORDER BY s.username;
]];
local lookup_all_query = [[
SELECT s.username, d.domain
FROM provisioning.voip_subscribers AS s
LEFT JOIN provisioning.voip_domains AS d ON s.domain_id = d.id
LEFT JOIN provisioning.voip_usr_preferences as p ON p.subscriber_id = s.id
WHERE s.account_id = ? AND s.is_pbx_group = 0 AND s.is_pbx_pilot = 0
AND (p.attribute_id = ? AND p.value = '1')
ORDER BY s.username;
]];
local account_id_query = [[
SELECT account_id
FROM provisioning.voip_subscribers
WHERE username = ? AND
domain_id = ( SELECT id FROM provisioning.voip_domains where domain = ?);
]]
local lookupt_preference_id_query = [[
SELECT id
FROM provisioning.voip_preferences
WHERE attribute = ?;
]]
-- from table to string
-- t = {'a','b'}
-- implode(",",t,"'")
-- "'a','b'"
-- implode("#",t)
-- "a#b"
local function implode(delimiter, list, quoter)
local len = #list
if not delimiter then
error("delimiter is nil")
end
if len == 0 then
return nil
end
if not quoter then
quoter = ""
end
local string = quoter .. list[1] .. quoter
for i = 2, len do
string = string .. delimiter .. quoter .. list[i] .. quoter
end
return string
end
local sql = require "util.sql";
local default_params = module:get_option("sql");
local engine;
-- Reconnect to DB if necessary
local function reconect_check()
if not engine.conn:ping() then
engine.conn = nil;
module:log("debug", "DDBB reconecting");
engine:connect();
engine:execute("SET NAMES 'utf8' COLLATE 'utf8_bin';");
end
end
-- returns the attribute_id of 'shared_buddylist_visiblility' preference
local function lookup_buddy_id()
reconect_check();
local res = engine:select(lookupt_preference_id_query,
'shared_buddylist_visibility');
for row in res do -- luacheck: ignore 512
return row[1]
end
module:log("error", "no 'shared_buddylist_visiblility' preference found!");
end
local buddylist_preference_id;
-- returns the attribute_id of 'display_name' preference
local function lookup_displayname_id()
reconect_check();
local res = engine:select(lookupt_preference_id_query,
'display_name');
for row in res do -- luacheck: ignore 512
return row[1]
end
module:log("error", "no 'display_name' preference found!");
end
local displayname_preference_id;
-- "roster-load" callback
local function inject_roster_contacts(event)
local username, host, roster = event.username, event.host, event.roster;
module:log("debug", "Injecting group members to roster");
local bare_jid = username.."@"..host;
local account_id, groups, display_names;
-- returns the account_id of username@host subscriber
local function lookup_account_id()
--module:log("debug", "lookup user '%s@%s'", username, host);
reconect_check();
for row in engine:select(account_id_query, username, host) do -- luacheck: ignore 512
module:log("debug", "user '%s@%s' belongs to %d",
username, host, row[1]);
return row[1];
end
module:log("debug", "no account_id found!");
end
-- returns a table with the pbx groups the subscriber
-- belongs to
local function lookup_user_groups()
local result = {};
reconect_check();
local res = engine:select(lookup_user_group_query, account_id,
username, host, buddylist_preference_id);
for row in res do
module:log("debug", "found group:'%s'", row[1]);
table.insert(result, row[1]);
end
return result;
end
-- returns a table with the subscribers display_name
-- key is username@domain
local function lookup_users_dn()
local result = {};
reconect_check();
local res = engine:select(lookup_with_dn_query, account_id,
displayname_preference_id);
for row in res do
result[row[1].."@"..row[2]] = row[3];
end
return result;
end
-- returns a dictionary with all the subscribers of the account
-- key is the name of the pbx group
-- if all is true a 'all' group will be added with all subscribers
-- if all_groups is false only the groups that bare_jid belongs will be added
local function lookup_groups(all, all_groups)
local res;
local result = {};
if account_id then
reconect_check();
if all_groups then
module:log("debug", "lookup_groups for account_id:%s",
account_id);
res = engine:select(lookup_query, account_id,
buddylist_preference_id);
else
module:log("debug", "lookup_groups for account_id:%s jid:%s@%s",
account_id, username, host);
local user_groups = lookup_user_groups();
res = engine:select(lookup_users_by_groups_query,
account_id, buddylist_preference_id,
implode(",",user_groups));
end
for row in res do
if not result[row[1]] then
result[row[1]] = {};
end
table.insert(result[row[1]], row[2].."@"..row[3]);
end
if all then
result['all'] = {};
for row in engine:select(lookup_all_query,
account_id, buddylist_preference_id) do
table.insert(result['all'], row[1].."@"..row[2]);
end
end
end
return result;
end
account_id = lookup_account_id();
-- TODO: set this parameters from usr_preferences
groups = lookup_groups(true, true);
display_names = lookup_users_dn();
local function import_jids_to_roster(group_name)
for _,jid in pairs(groups[group_name]) do
-- Add them to roster
module:log("debug", "processing jid %s in group %s",
tostring(jid), tostring(group_name));
if jid ~= bare_jid then
if not roster[jid] then roster[jid] = {}; end
roster[jid].subscription = "both";
-- If we have the subscriber display name
if display_names[jid] then
roster[jid].name = display_names[jid];
end
if not roster[jid].groups then
roster[jid].groups = { [group_name] = true };
end
roster[jid].groups[group_name] = true;
roster[jid].persist = false;
end
end
end
for group_name in pairs(groups) do
module:log("debug", "Importing group %s", group_name);
import_jids_to_roster(group_name);
end
if roster[false] then
roster[false].version = true;
end
end
local function normalize_params(params)
assert(params.driver and params.database,
"Configuration error: Both the SQL driver and the database need to be specified");
return params;
end
function module.load()
if prosody.prosodyctl then return; end
local engines = module:shared("/*/sql/connections");
local params = normalize_params(module:get_option("auth_sql", default_params));
engine = engines[sql.db2uri(params)];
if not engine then
module:log("debug", "Creating new engine");
engine = sql:create_engine(params);
engines[sql.db2uri(params)] = engine;
end
engine:connect();
buddylist_preference_id = lookup_buddy_id();
displayname_preference_id = lookup_displayname_id();
module:hook("roster-load", inject_roster_contacts);
module:log("debug", "Groups loaded successfully");
end

@ -1,59 +0,0 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- TODO: permanent storage
local st = require "util.stanza";
local is_contact_subscribed = require "core.rostermanager".is_contact_subscribed;
local jid_bare = require "util.jid".bare;
local jid_split = require "util.jid".split;
local sessions = module:shared("sessions");
module:add_feature("jabber:iq:last");
local map = {};
-- lastactivity: any change of presence
module:hook("pre-presence/bare", function(event)
local stanza = event.stanza;
if not(stanza.attr.to) then
local t = os.time();
local s = stanza:get_child_text("status");
map[event.origin.username] = {s = s, t = t};
module:log("debug", string.format("change of presence:%s from:%s",
tostring(s), tostring(event.origin.username)));
end
end, 10);
module:hook("iq-get/bare/jabber:iq:last:query", function(event)
local origin, stanza = event.origin, event.stanza;
local username = jid_split(stanza.attr.to) or origin.username;
if not stanza.attr.to or is_contact_subscribed(username, module.host, jid_bare(stanza.attr.from)) then
local seconds, text = nil, "";
if not sessions[origin.conn] and map[username] then
seconds = tostring(os.difftime(os.time(), map[username].t));
text = map[username].s;
end
origin.send(st.reply(stanza):tag('query', {xmlns='jabber:iq:last', seconds=seconds}):text(text));
else
origin.send(st.error_reply(stanza, 'auth', 'forbidden'));
end
return true;
end);
local function msg_handler(event)
local origin, stanza = event.origin, event.stanza;
local username = jid_split(stanza.attr.from) or origin.username;
map[username] = {s = "online", t = os.time()};
module:log("debug", string.format("%s from: %s",
tostring(stanza.attr.type), tostring(username)));
end
-- lastactivity: any message sent
module:hook("pre-message/bare", msg_handler);
module:hook("pre-message/full", msg_handler);

@ -1,78 +0,0 @@
-- Copyright (C) 2015 Sipwise GmbH <development@sipwise.com>
--
-- enable log stanzas/[in|out] by jid
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:set_global();
local jid = require "util.jid";
local add_filter = require "util.filters".add_filter;
local ut = require "ngcp.utils";
local log = module._log;
local config_default = {
level = "debug", -- log level
prefix_out = "Sent",
prefix_in = "Received",
jids = {}, -- jids to log
filter = {'*'} -- 'message', 'iq'
};
local config
local function filter_stanza(stanza)
if ut.table.contains(config.filter, '*') then
return true
end
for _,v in pairs(config.filter) do
if stanza.name == v then
return true
end
if stanza:get_child(v) then
return true
end
end
return false
end
local function log_stanza(stanza, session, prefix)
if filter_stanza(stanza) then
session.log(config.level, "%s[%s]: %s", prefix, session.full_jid,
tostring(stanza))
return stanza
end
return stanza
end
local function log_in_stanza (stanza, session)
return log_stanza(stanza, session,
config.prefix_in or config_default.prefix_in)
end
local function log_out_stanza (stanza, session)
return log_stanza(stanza, session,
config.prefix_out or config_default.prefix_out)
end
local function resource_bind(event)
local session = event.session;
local node, domain, _ = jid.split(session.full_jid);
local bare_jid = node.."@"..domain;
if ut.table.contains(config.jids, bare_jid) then
add_filter(session, "stanzas/out", log_out_stanza, 1000);
add_filter(session, "stanzas/in", log_in_stanza, 1000);
module:log("info", "log_debug activated for %s for filter %s",
session.full_jid, ut.table.tostring(config.filter))
end
end
function module.load()
config = module:get_option("log_debug", config_default);
log("info", "jids: %s", ut.table.tostring(config.jids))
end
function module.add_host(module)
module:hook("resource-bind", resource_bind, 200);
end

@ -1,564 +0,0 @@
--
-- Copyright (C) 2014-2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:depends("sipwise_vcard_cusax");
module:depends("sipwise_pushd_blocking");
local datamanager = require "util.datamanager";
local sql = require "util.sql";
local format = string.format;
local jid_split = require "util.jid".split;
local jid_bare = require "util.jid".bare;
local jid_host = require "util.jid".host;
local jid_resource = require "util.jid".resource;
local hosts = prosody.hosts;
local http = require "net.http";
local uuid = require "util.uuid";
local ut = require "ngcp.utils";
local set = require "util.set";
local st = require "util.stanza";
local sipwise_offline = module:shared("sipwise_offline/sipwise_offline");
local pushd_blocking = module:shared("sipwise_pushd_blocking/pushd_blocking");
local muc_config = {
force_persistent = true,
owner_on_join = true,
exclude = {}
};
local pushd_config = {
url = "https://127.0.0.1:8080/push",
fcm = true,
apns = true,
call_sound = 'incoming_call.caf',
msg_sound = 'incoming_message.caf',
muc_config = muc_config
};
local push_usr_query = [[
SELECT vp.attribute, vup.value FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
WHERE vp.attribute = 'mobile_push_enable'
AND vs.username = ?
AND vd.domain = ?;
]];
local push_dom_query = [[
SELECT vp.attribute, vup.value FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_dom_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vup.domain_id
WHERE vp.attribute = 'mobile_push_enable'
AND vd.domain = ?;
]];
local push_silent_query = [[
SELECT vp.id FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
WHERE vp.attribute = 'mobile_push_silent_list'
AND vs.username = ?
AND vd.domain = ?
AND vup.value = ?;
]];
local default_params = module:get_option("sql");
local engine;
-- Reconnect to DB if necessary
local function reconect_check()
if not engine.conn:ping() then
engine.conn = nil;
engine:connect();
end
end
-- luacheck: ignore request
local function process_response(response, code, request)
if code >= 200 and code < 299 then
module:log("debug", "pushd response OK[%s] %s",
tostring(code), tostring(response));
else
module:log("error", "pushd response KO[%s] %s",
tostring(code), tostring(response));
end
end
local function push_silent(username, domain, other)
reconect_check();
for row in engine:select(push_silent_query, username, domain, other) do
module:log("debug", "silent push preference mobile_push_silent_list matches");
return true;
end
return false;
end
local function push_enable(username, domain)
reconect_check();
local flag
for row in engine:select(push_usr_query, username, domain) do
if row[2] == "never" then
module:log("debug", "usr mobile_push_enable pref set to 'never'");
return false;
end
module:log("debug", "usr pref %s", ut.table.tostring(row))
flag = true
end
if flag then return true end
for row in engine:select(push_dom_query, domain) do
if row[2] == "never" then
module:log("debug", "domain mobile_push_enable pref set to 'never'");
return false;
end
module:log("debug", "dom pref %s", ut.table.tostring(row))
end
return true;
end
local function is_local_domain(dom)
return ut.table.contains(ut.table.keys(hosts), dom);
end
local function get_caller_info(jid, caller_defaults)
if not jid then
return nil;
end
local node, host = jid_split(jid);
if not is_local_domain(host) then
module:log("debug", "caller[%s] not local", jid);
return nil;
end
local vcard = module:shared(format("/%s/sipwise_vcard_cusax/vcard", host));
if vcard and vcard.get_subscriber_info then
local info = vcard.get_subscriber_info(node, host);
module:log("debug", "caller_info of %s", jid);
if not info.display_name then
module:log("debug", "set display_name to %s", node);
info.display_name = node;
end
if not info.aliases then
info.aliases = caller_defaults.aliases;
end
return info;
end
end
local function get_members(muc_room)
local res = set.new();
if muc_room then
for o_jid, _, _ in muc_room:each_affiliation() do
res:add(o_jid);
end
end
return res;
end
local function get_occupants(muc_room)
local res = set.new();
for _, occupant in muc_room:each_occupant() do
res:add(jid_bare(occupant.jid));
end
return res;
end
local function get_muc_room(room_jid)
local host = jid_host(room_jid);
if not hosts[host] then
module:log("warn", "host [%s] not defined", tostring(host));
return nil
end
if not hosts[host].modules.muc then
module:log("warn", "muc not enabled here [%s]", tostring(host));
return nil
end
return hosts[host].modules.muc.get_room_from_jid(jid_bare(room_jid));
end
local function get_muc_caller(room_jid)
local nick = jid_resource(room_jid);
local room = get_muc_room(room_jid);
if not room then
module:log("warn", "room %s not here", jid_bare(room_jid));
return nil;
end
return room:get_occupant_by_nick(nick);
end
local function get_callee_badge(jid)
local node, host = jid_split(jid);
local result = 0;
if sipwise_offline.get_num then
result = sipwise_offline.get_num(node, host);
else
local offline = datamanager.list_load(node, host, "offline");
if offline then
result = #offline;
end
end
-- plus the one in process
result = result + 1;
module:log("debug", "%d offline messages for %s", result, jid);
return result;
end
local function is_invite(stanza)
if stanza:get_child('x', 'http://jabber.org/protocol/muc#user') then
return true
end
end
local function is_attachment(stanza)
if stanza:get_child('x', 'jabber:x:oob') then
return true
end
end
local function get_message(stanza)
local body = stanza:get_child('body');
if body then
return body:get_text();
end
end
local function get_muc_info(stanza, caller_info)
local from = stanza.attr.from;
local muc_stanza = stanza:get_child('x', 'jabber:x:conference');
local muc = {};
local room;
if muc_stanza then
muc.jid = muc_stanza.attr.jid;
else
muc.jid = jid_bare(from);
end
muc.name, muc.domain = jid_split(muc.jid);
if not is_local_domain(muc.domain) then
module:log("debug", "not from local host[%s]", tostring(muc.domain));
return nil;
end
local room_host = hosts[muc.domain].modules.muc;
if not room_host then
module:log("debug", "not from MUC host[%s]", muc.domain);
return nil;
end
room = room_host.get_room_from_jid(muc.jid);
if room then
muc.room = room:get_description() or muc.name;
if muc_stanza then
muc.invite = format("Group chat invitation to '%s' from %s",
muc.room, caller_info.display_name);
end
return muc;
end
end
local function is_pushd_blocked(from, to)
local node_to, host_to = jid_split(to);
local blocked_list = pushd_blocking.get_blocked_jids(node_to, host_to);
module:log("debug", "pushd_blockedlist: %s for [%s]", ut.table.tostring(blocked_list), to);
local _, host_from, resource_from = jid_split(from);
if ut.table.contains(blocked_list, from)
or ut.table.contains(blocked_list, jid_bare(from))
or ut.table.contains(blocked_list, host_from) then
return true;
end
if resource_from and ut.table.contains(blocked_list, host_from.."/"..resource_from) then
return true;
end
return false;
end
local function handle_offline(event)
module:log("debug", "handle_offline");
local origin, stanza = event.origin, event.stanza;
local to = stanza.attr.to;
local from = stanza.attr.from;
local node, host;
local caller = { username = 'unknow', host = 'unknown.local' };
local caller_defaults = {display_name = '', aliases = {''}};
local caller_info;
-- defaults to "application/x-www-form-urlencoded"
local http_options = {
method = "POST",
body = "",
}
local message;
local function build_push_common_query(msg, muc)
if muc then
msg.data_room_jid = muc['jid'];
msg.data_room_description = muc['room'];
if msg.data_type == 'invite' then
msg.data_message = muc['invite'];
end
end
msg.data_sender_number = tostring(caller_info.aliases[1]);
msg.data_sender_name = tostring(caller_info.display_name);
return msg;
end
local function build_push_apns_query(msg, muc, silent)
if silent then
msg['apns_content-available'] = '1';
else
if msg.data_type ~= 'invite' then
msg.apns_alert = string.format("Message from %s:\n",
caller_info.display_name) .. msg.data_message;
else
msg.apns_alert = muc['invite'];
end
msg.apns_sound = pushd_config.msg_sound or '';
msg.apns_badge = tostring(get_callee_badge(to));
end
return msg;
end
local function build_push_query(msg)
local muc;
local caller_jid;
local silent;
if stanza.attr.type == 'groupchat' then
msg.data_type = 'groupchat'
caller_jid = get_muc_caller(stanza.attr.from);
module:log("debug", "from:%s -> caller_jid:%s",
tostring(stanza.attr.from), tostring(caller_jid));
caller_info = get_caller_info(caller_jid, caller_defaults) or
caller_defaults;
muc = get_muc_info(stanza, caller_info);
silent = push_silent(msg.callee, msg.domain, muc['jid']);
else
msg.data_type = 'message';
caller_jid = format("%s@%s",
origin.username or caller.username,
origin.host or caller.host);
if is_invite(stanza) then
msg.data_type = 'invite';
caller_jid = jid_bare(stanza:get_child('invite').attr.from) or
caller_jid;
elseif is_attachment(stanza) then
msg.data_message = "new attachment";
end
caller_info = get_caller_info(caller_jid, caller_defaults) or
caller_defaults;
muc = get_muc_info(stanza, caller_info);
silent = push_silent(msg.callee, msg.domain, caller_jid);
end
if silent then
msg.data_silent = '1';
else
msg.data_silent = '0';
end
msg.data_sender_jid = caller_jid;
msg.data_sender_sip = jid_bare(caller_jid);
msg.push_id = uuid.generate();
msg = build_push_common_query(msg, muc);
if pushd_config.apns then
msg = build_push_apns_query(msg, muc, silent);
end
return msg;
end
module:log("debug", "stanza[%s]", tostring(stanza));
if from then
caller.username, caller.host = jid_split(from);
end
if to then
node, host = jid_split(to);
if from and is_pushd_blocked(from, to) then
module:log("debug", "skip pushd message from blocked jid [%s]", from);
return nil;
end
message = {
data_message = get_message(stanza),
data_full_message = tostring(stanza),
callee = node,
domain = host,
};
if message.data_message then
module:log("debug", "message OK");
if push_enable(node, host) then
message = build_push_query(message);
module:log("debug", "message: %s", ut.table.tostring(message));
http_options.body = http.formencode(message);
if http_options.body then
module:log("debug", "Sending http pushd request: %s data: %s",
pushd_config.url, http_options.body);
http.request(pushd_config.url, http_options, process_response);
end
else
module:log("debug", "no mobile_push_enable pref set for %s", to);
end
else
module:log("debug", "no message body");
end
end
end
local function fire_offline_message(event, muc_room, off_jid)
local stanza_c = st.clone(event.stanza);
local orig_from = event.stanza.attr.from;
stanza_c.attr.to = off_jid;
stanza_c.attr.from = muc_room:get_occupant_jid(orig_from);
module:log("debug", "stanza[%s] stanza_c[%s]",
tostring(event.stanza), tostring(stanza_c));
if not stanza_c.attr.from then
module:log("error", "original from[%s] not found at muc_room",
tostring(orig_from));
return nil;
end
if is_pushd_blocked(stanza_c.attr.from, stanza_c.attr.to) then
module:log("debug", "skip pushd message from blocked jid [%s]",
stanza_c.attr.from);
elseif is_pushd_blocked(orig_from, stanza_c.attr.to) then
module:log("debug", "skip pushd message from blocked jid [%s]",
orig_from);
else
module:fire_event('message/offline/handle', {
origin = event.origin,
stanza = stanza_c,
});
end
end
local function handle_muc_offline(event, room_jid)
local _, host = jid_split(room_jid);
local muc = hosts[host].modules.muc;
if muc then
local muc_room = muc.get_room_from_jid(room_jid);
if not muc_room then
module:log("debug", "muc room[%s] not here. Nothing to do",
room_jid);
return nil;
elseif ut.table.contains(pushd_config.muc_config.exclude, room_jid) then
module:log("debug", "muc room[%s] excluded from pushd", room_jid);
return nil;
end
local muc_members = get_members(muc_room);
local muc_occ_online = get_occupants(muc_room);
local muc_occ_offline = set.difference(muc_members,muc_occ_online);
module:log("debug", "muc_members[%s]", tostring(muc_members));
module:log("debug", "muc_occ_online[%s]", tostring(muc_occ_online));
module:log("debug", "muc_occ_offline[%s]", tostring(muc_occ_offline));
for off_jid in muc_occ_offline do
module:log("debug", "fire_offline_message[%s]", off_jid);
fire_offline_message(event, muc_room, off_jid);
end
end
end
local function handle_msg(event)
module:log("debug", "handle_msg");
local stanza = event.stanza;
local room_jid = stanza.attr.to;
if stanza.attr.type ~= 'groupchat' then
module:log("debug",
"message[%s] not of type groupchat. Nothing to do here",
tostring(stanza));
return nil;
end
module:log("debug", "handle_msg room_jid[%s]", tostring(room_jid));
return handle_muc_offline(event, room_jid);
end
local function handle_muc_created(event)
local room = event.room;
if pushd_config.muc_config.force_persistent then
room:set_persistent(true);
module:log("debug", "persistent room[%s] forced on creation",
room:get_name());
end
end
local function handle_muc_config(event)
local room, fields = event.room, event.fields;
local name = fields['muc#roomconfig_roomname'] or room:get_name();
local persistent = fields['muc#roomconfig_persistentroom'];
if pushd_config.muc_config.force_persistent and not persistent then
fields['muc#roomconfig_persistentroom'] = true;
event.changed = true;
module:log("debug", "persistent room[%s] forced", name);
end
end
local function handle_muc_presence(event)
if not pushd_config.muc_config.owner_on_join then return; end
local stanza = event.stanza;
if stanza.attr.type == "unavailable" then return; end
local room = get_muc_room(stanza.attr.to);
if not room then return; end
local from_jid = jid_bare(stanza.attr.from);
local affiliation = room:get_affiliation(from_jid);
if affiliation ~= "owner" then
room:set_affiliation(true, from_jid, "owner");
module:log("debug", "[%s] set affiliation to 'owner' for [%s]",
room:get_name(), from_jid);
end
end
local function handle_muc_presence_out(event)
local stanza = event.stanza;
local muc_stanza = stanza:get_child('x',
'http://jabber.org/protocol/muc#user');
if muc_stanza then
local item = muc_stanza:get_child('item');
if not item.nick and stanza.attr.from then
local nick = select(3, jid_split(stanza.attr.from));
if nick then
item.attr.nick = nick;
module:log("debug", "added nick[%s] tag", nick);
end
end
end
end
if module:get_host_type() == "component" then
module:hook("muc-room-created", handle_muc_created, 20);
module:hook("muc-config-submitted", handle_muc_config, 20);
module:hook("presence/full", handle_muc_presence, 501);
else
module:hook("presence/full", handle_muc_presence_out, 501);
end
module:hook("message/bare", handle_msg, 20);
module:hook("message/offline/handle", handle_offline, 20);
local function normalize_params(params)
assert(params.driver and params.database,
"Configuration error: Both the SQL driver and the database need to be specified");
return params;
end
function module.load()
if prosody.prosodyctl then return; end
local engines = module:shared("/*/sql/connections");
local params = normalize_params(module:get_option("auth_sql", default_params));
engine = engines[sql.db2uri(params)];
if not engine then
module:log("debug", "Creating new engine");
engine = sql:create_engine(params);
engines[sql.db2uri(params)] = engine;
end
engine:connect();
pushd_config = module:get_option("pushd_config", pushd_config);
if not pushd_config.muc_config then
pushd_config.muc_config = muc_config
end
module:log("debug", "load OK");
end

@ -1,175 +0,0 @@
local jid_split = require "util.jid".split;
local st = require "util.stanza";
local datamanager = require"util.datamanager";
local xmlns_blocking = "urn:xmpp:pushd_blocking";
local datastore = "pushd_privacy";
module:add_feature("urn:xmpp:pushd_blocking");
local pushd_blocking = module:shared("pushd_blocking");
-- Add JID to default privacy list
local function add_blocked_jid(username, host, jid)
local privacy_lists = datamanager.load(username, host, datastore) or {lists = {}};
local default_list_name = privacy_lists.default;
if not privacy_lists.lists then
privacy_lists.lists = {}
end
if not default_list_name then
default_list_name = "blocklist";
privacy_lists.default = default_list_name;
end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then
default_list = { name = default_list_name, items = {} };
privacy_lists.lists[default_list_name] = default_list;
end
local items = default_list.items;
local order = items[1] and items[1].order or 0; -- Must come first
for i=1,#items do -- order must be unique
local item = items[i];
item.order = item.order + 1;
if item.type == "jid" and item.action == "deny" and item.value == jid then
return false;
end
end
table.insert(items, 1, { type = "jid"
, action = "deny"
, value = jid
, message = false
, ["presence-out"] = false
, ["presence-in"] = false
, iq = false
, order = order
});
datamanager.store(username, host, datastore, privacy_lists);
return true;
end
-- Remove JID from default privacy list
local function remove_blocked_jid(username, host, jid)
local privacy_lists = datamanager.load(username, host, datastore) or {};
local default_list_name = privacy_lists.default;
if not default_list_name then return; end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then return; end
local items = default_list.items;
local item, removed = nil, false;
for i=1,#items do -- order must be unique
item = items[i];
if item.type == "jid" and item.action == "deny" and item.value == jid then
table.remove(items, i);
removed = true;
break;
end
end
if removed then
datamanager.store(username, host, datastore, privacy_lists);
end
return removed;
end
local function remove_all_blocked_jids(username, host)
local privacy_lists = datamanager.load(username, host, datastore) or {};
local default_list_name = privacy_lists.default;
if not default_list_name then return; end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then return; end
local items = default_list.items;
local item;
for i=#items,1,-1 do -- order must be unique
item = items[i];
if item.type == "jid" and item.action == "deny" then
table.remove(items, i);
end
end
datamanager.store(username, host, datastore, privacy_lists);
return true;
end
function pushd_blocking.get_blocked_jids(username, host)
-- Return array of blocked JIDs in default privacy list
local privacy_lists = datamanager.load(username, host, datastore) or {};
local default_list_name = privacy_lists.default;
if not default_list_name then return {}; end
local default_list = privacy_lists.lists[default_list_name];
if not default_list then return {}; end
local items = default_list.items;
local item;
local jid_list = {};
for i=1,#items do -- order must be unique
item = items[i];
if item.type == "jid" and item.action == "deny" then
jid_list[#jid_list+1] = item.value;
end
end
return jid_list;
end
local function send_push_iqs(username, host, command_type, jids)
local bare_jid = username.."@"..host;
local stanza_content = st.stanza(command_type, { xmlns = xmlns_blocking });
for _, jid in ipairs(jids) do
stanza_content:tag("item", { jid = jid }):up();
end
for resource, session in pairs(prosody.bare_sessions[bare_jid].sessions) do
local iq_push_stanza = st.iq({ type = "set", to = bare_jid.."/"..resource });
iq_push_stanza:add_child(stanza_content);
session.send(iq_push_stanza);
end
end
local function handle_blocking_command(event)
local session, stanza = event.origin, event.stanza;
local username, host = jid_split(stanza.attr.from);
if stanza.attr.type == "set" then
if stanza.tags[1].name == "block" then
local block = stanza.tags[1];
local block_jid_list = {};
for item in block:childtags() do
block_jid_list[#block_jid_list+1] = item.attr.jid;
end
if #block_jid_list == 0 then
session.send(st.error_reply(stanza, "modify", "bad-request"));
else
for _, jid in ipairs(block_jid_list) do
add_blocked_jid(username, host, jid);
end
session.send(st.reply(stanza));
send_push_iqs(username, host, "block", block_jid_list);
end
return true;
elseif stanza.tags[1].name == "unblock" then
local unblock = stanza.tags[1];
local unblock_jid_list = {};
for item in unblock:childtags() do
unblock_jid_list[#unblock_jid_list+1] = item.attr.jid;
end
if #unblock_jid_list == 0 then
remove_all_blocked_jids(username, host);
else
for _, jid_to_unblock in ipairs(unblock_jid_list) do
remove_blocked_jid(username, host, jid_to_unblock);
end
end
session.send(st.reply(stanza));
send_push_iqs(username, host, "unblock", unblock_jid_list);
return true;
end
elseif stanza.attr.type == "get" and stanza.tags[1].name == "blocklist" then
local reply = st.reply(stanza):tag("blocklist", { xmlns = xmlns_blocking });
local blocked_jids = pushd_blocking.get_blocked_jids(username, host);
for _, jid in ipairs(blocked_jids) do
reply:tag("item", { jid = jid }):up();
end
session.send(reply);
return true;
end
end
module:hook("iq/self/urn:xmpp:pushd_blocking:blocklist", handle_blocking_command);
module:hook("iq/self/urn:xmpp:pushd_blocking:block", handle_blocking_command);
module:hook("iq/self/urn:xmpp:pushd_blocking:unblock", handle_blocking_command);

@ -1,154 +0,0 @@
-- Prosody IM
-- Copyright (C) 2014-2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:set_global();
local set = require "util.set";
local ut = require "ngcp.utils";
local jid = require "util.jid";
local redis = require 'redis';
local redis_config = {
port = 6739, host = "127.0.0.1",
server_id = "0", redis_db = "2"
};
local redis_client;
local redis_mucs = module:shared("redis_mucs");
local function test_connection()
if not redis_client then return nil; end
local ok, _ = pcall(redis_client.ping, redis_client);
if not ok then
redis_client = nil;
end
end
local function client_connect()
redis_client = redis.connect(redis_config.host, redis_config.port);
if redis_config.redis_db then
redis_client:select(redis_config.redis_db);
end
end
local function muc_created(event)
local room = event.room;
local rhost = redis_client:get(room.jid);
if rhost then
module:log("warn", "[%s] already at %s", room.jid, rhost);
return nil;
end
redis_mucs.set_room_host(room.jid, redis_config.server_id);
module:log("debug", "muc-room-created %s", room.jid);
end
local function muc_pre_restore(event)
local room_jid = event.jid;
local rhost = redis_client:get(room_jid);
if rhost ~= redis_config.server_id then
module:log("warn", "[%s] already at %s", room_jid, rhost);
return false;
end
module:log("debug", "[%s] already here", room_jid);
return true;
end
local function muc_restored(event)
local room = event.room;
redis_mucs.set_room_host(room.jid, redis_config.server_id);
module:log("debug", "muc-room-restored %s", room.jid);
end
local function muc_destroyed(event)
local room = event.room;
redis_mucs.clean_room_host(room.jid, redis_config.server_id);
module:log("debug", "muc-room-destroyed %s", room.jid);
end
local function split_key(key)
local t = ut.explode(':', key);
return t[1], t[2];
end
function redis_mucs.get_rooms(host)
local res = set.new();
local l, r;
module:log("debug", "search rooms at host[%s]", tostring(host));
if not test_connection() then client_connect() end
l = redis_client:smembers(host);
for _,v in pairs(l) do
_, r = split_key(v);
res:add(r..'@'..host);
end
module:log("debug", "found [%s]", tostring(res));
return res;
end
function redis_mucs.set_room_host(room_jid, server_id)
local node, host, _ = jid.split(room_jid);
local bare_jid = node.."@"..host;
if not test_connection() then client_connect() end
-- TODO: check that there is no other "bare_jid" value?
if redis_client:set(bare_jid, server_id) then
module:log("debug", "save [%s]=%s", bare_jid, server_id);
end
if redis_client:sadd(host, server_id..":"..node) > 0 then
module:log("debug", "append [%s]=>%s:%s", host,
server_id, node);
end
end
function redis_mucs.get_room_host(room_jid)
local node, domain, _ = jid.split(room_jid);
local bare_jid = node.."@"..domain;
module:log("debug", "search room:%s host", bare_jid);
if not test_connection() then client_connect() end
return redis_client:get(bare_jid);
end
function redis_mucs.clean_room_host(room_jid, server_id)
local node, host, _ = jid.split(room_jid);
local bare_jid = node.."@"..host;
if not test_connection() then client_connect() end
if redis_client:del(bare_jid) > 0 then
module:log("debug", "remove [%s]=%s", bare_jid, server_id);
end
if redis_client:srem(host, server_id..":"..node) > 0 then
module:log("debug", "remove [%s]=>%s:%s", host, server_id, node);
end
end
function module.load()
redis_config = module:get_option("redis_sessions_auth", redis_config);
end
function shutdown_component()
local rooms = redis_mucs.get_rooms(module:get_host());
for room_jid in rooms do
redis_mucs.clean_room_host(room_jid, redis_config.server_id);
end
end
module:hook_global("server-stopping", shutdown_component, -300);
function module.add_host(module)
local host = module:get_host();
local _type = module:get_host_type();
if _type == "component" then
module:hook("muc-room-created", muc_created, 200);
module:hook("muc-room-destroyed", muc_destroyed, 200);
module:hook("muc-room-restored", muc_restored, 200);
module:hook("muc-room-pre-restore", muc_pre_restore, 200);
end
module:log("debug", "hooked at %s as %s", host, _type);
end

@ -1,141 +0,0 @@
-- Prosody IM
-- Copyright (C) 2014-2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:set_global();
local ut = require "ngcp.utils";
local jid = require "util.jid";
local array = require "util.array";
local redis = require 'redis';
local redis_config = {
port = 6739, host = "127.0.0.1",
server_id = "0", redis_db = "2",
clean_local_sessions = true,
};
local redis_client;
local redis_sessions = module:shared("redis_sessions");
local function test_connection()
if not redis_client then return nil end;
local ok, _ = pcall(redis_client.ping, redis_client);
if not ok then
redis_client = nil;
end
end
local function client_connect()
redis_client = redis.connect(redis_config.host, redis_config.port);
--module:log("debug", "connected to redis server %s:%d",
--redis_config.host, redis_config.port);
if redis_config.redis_db then
redis_client:select(redis_config.redis_db);
end
end
local function resource_bind(event)
local session = event.session;
local node, domain, resource = jid.split(session.full_jid);
local full_jid, bare_jid = session.full_jid, node.."@"..domain;
module:log("debug", "resource-bind from %s", session.host);
module:log("debug", "save [%s]=%s", full_jid, redis_config.server_id);
if not test_connection() then client_connect() end
redis_client:set(full_jid, redis_config.server_id);
module:log("debug", "append [%s]=>%s:%s", bare_jid, redis_config.server_id, resource);
redis_client:sadd(bare_jid, redis_config.server_id..":"..resource);
end
local function remove_resource(full_jid)
local node, domain, resource = jid.split(full_jid);
local bare_jid = node.."@"..domain;
if not test_connection() then client_connect() end
redis_client:del(full_jid);
module:log("debug", "remove [%s]=%s", full_jid, redis_config.server_id);
redis_client:srem(bare_jid, redis_config.server_id..":"..resource);
module:log("debug", "remove [%s]=>%s:%s", bare_jid,
redis_config.server_id, resource);
end
local function resource_unbind(event)
local session, _ = event.session, event.error;
module:log("debug", "resource-unbind from %s", session.host);
remove_resource(session.full_jid);
end
local function split_key(key)
local t = ut.explode(':', key);
return t[1], t[2];
end
local function clean_local_sessions(host)
if not test_connection() then client_connect() end
local l = redis_client:keys('*@'..host);
for _, bare_jid in ipairs(l) do
local resources = redis_client:smembers(bare_jid);
module:log("debug", "clean previous sessions for %s on %s",
bare_jid, redis_config.server_id);
for _, resource in ipairs(resources) do
local server, old_resource = split_key(resource);
if server == redis_config.server_id then
module:log("debug", "remove old resource %s", old_resource);
remove_resource(bare_jid..'/'..old_resource);
end
end
end
end
function redis_sessions.get_hosts(j)
local node, domain = jid.split(j);
local bare_jid = node.."@"..domain;
local res = {};
local l, h, r;
module:log("debug", "search session:%s host", bare_jid);
if not test_connection() then client_connect() end
l = redis_client:smembers(bare_jid);
--module:log("debug", "l:%s", ut.table.tostring(l));
for _,v in pairs(l) do
h, r = split_key(v);
--module:log("debug", "h:%s r:%s", tostring(h), tostring(r));
if not res[h] then res[h] = array() end
res[h]:push(r);
end
return res;
end
function redis_sessions.clean_host(j, server_id)
local bare_jid = jid.bare(j);
module:log("debug", "clean jid %s from %s", bare_jid, server_id);
if not test_connection() then client_connect() end
local l = redis_client:smembers(bare_jid);
for _,v in pairs(l) do
local h, _ = split_key(v);
if h == server_id then
redis_client:srem(bare_jid, v);
redis_client:del(v);
module:log("debug", "removed %s from %s", v, bare_jid);
end
end
end
function module.load()
redis_config = module:get_option("redis_sessions_auth", redis_config);
end
function module.add_host(module)
local host = module:get_host();
local _type = module:get_host_type();
if _type ~= "component" then
module:hook("resource-bind", resource_bind, 200);
module:hook("resource-unbind", resource_unbind, 200);
clean_local_sessions(host);
end
module:log("debug", "hooked at %s as %s", host, _type);
end

@ -1,195 +0,0 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2013-2015 Sipwise GmbH
--
-- This is a stripped down version of mod_vcard for returning
-- simply a vcard containing some info of
-- the requested user.
-- http://xmpp.org/extensions/xep-0054.html
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st = require "util.stanza";
local jid_split = require "util.jid".split;
local vcard = module:shared("vcard");
local hosts = prosody.hosts;
local email_query = [[
SELECT bc.email, bccc.email FROM billing.voip_subscribers AS vs
LEFT join billing.contacts AS bc ON vs.contact_id = bc.id
LEFT join billing.contracts AS bcc ON vs.contract_id = bcc.id
LEFT join billing.contacts AS bccc ON bcc.contact_id = bccc.id
LEFT join billing.domains AS bd ON vs.domain_id = bd.id
WHERE vs.username = ? AND bd.domain = ?;
]]
local display_usr_query = [[
SELECT vp.attribute, vup.value FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_subscribers AS ps ON ps.id = vup.subscriber_id
LEFT JOIN provisioning.voip_domains AS pd ON ps.domain_id = pd.id
WHERE vp.attribute = 'display_name'
AND ps.username = ? AND pd.domain = ?
]];
local aliases_query = [[
SELECT pa.username, pa.is_primary FROM provisioning.voip_dbaliases AS pa
LEFT JOIN provisioning.voip_subscribers AS ps ON ps.id = pa.subscriber_id
LEFT JOIN provisioning.voip_domains AS pd ON ps.domain_id = pd.id
WHERE ps.username = ? AND pd.domain = ? ORDER BY pa.is_primary DESC;
]];
local um_user_exists = require "core.usermanager".user_exists;
local sql = require "util.sql";
local default_params = module:get_option("sql");
local engine;
module:add_feature("vcard-temp");
local function reconect_check()
if not engine.conn:ping() then
engine.conn = nil;
engine:connect();
end
end
function vcard.get_subscriber_info(user, host)
if not hosts[host] or not hosts[host].users then
return nil;
end
if not um_user_exists(user, host) then
return nil;
end
local info = { user = user, domain = host, aliases = {} };
reconect_check();
for row in engine:select(display_usr_query, user, host) do
info['display_name'] = row[2];
module:log("debug", string.format("display_name:[%s]", row[2]));
end
for row in engine:select(aliases_query, user, host) do
table.insert(info['aliases'], row[1]);
module:log("debug", string.format("aliases:%s", row[1]));
end
for row in engine:select(email_query, user, host) do
local email;
-- NULL is not nil here, so checking length
for i= 1, 2 do
if row[i] and row[i]:len() > 0 then
email = row[i];
break;
end
end
if email then
info['email'] = email;
module:log("debug", "email:", tostring(email));
end
end
return info;
end
local function generate_vcard(info)
local function add(t, name, value)
local tmp = st.stanza( name,
{ xmlns = "vcard-temp" });
if value then tmp:text(value) end
if t then
t:add_child(tmp);
else
return tmp;
end
end
local vCard = st.stanza("vCard",
{
xmlns = "vcard-temp",
prodid = "-//HandGen//NONSGML vGen v1.0//EN",
version = "2.0"
}
);
local uri = info["user"] .. '@' .. info["domain"];
add(vCard, "JABBERID", uri);
local t = add(nil, "TEL")
add(t, "VIDEO");
add(t, "NUMBER", "sip:" .. uri);
vCard:add_child(t);
for _,v in ipairs(info['aliases']) do
t = add(nil, "TEL");
add(t, "VOICE");
add(t, "NUMBER", v);
vCard:add_child(t);
end
if info['display_name'] then
add(vCard, "FN", info['display_name']);
end
if info['email'] then
t = add(nil, "EMAIL");
add(t, "INTERNET");
add(t, "PREF");
add(t, "USERID", info['email']);
vCard:add_child(t)
end
return vCard;
end
local function handle_vcard(event)
module:log("debug", "handle_vcard");
local session, stanza = event.origin, event.stanza;
local to = stanza.attr.to;
if stanza.attr.type == "get" then
local user, host;
if to then
user, host = jid_split(to);
else
user = session.username;
host = session.host;
end
if not (user and host) then
module:log("error", "bad-request stanza: %s",
tostring(stanza))
session.send(st.error_reply(stanza, "modify", "jid-malformed"));
end
local info = vcard.get_subscriber_info(user, host);
if info then
local vCard = generate_vcard(info);
local reply = st.reply(stanza):add_child(st.deserialize(vCard));
session.send(reply);
else
module:log("error", "No info retrieve from stanza: %s",
tostring(stanza))
session.send(st.error_reply(stanza, "cancel", "item-not-found"));
end
else
module:log("debug", "reject setting vcard");
session.send(st.error_reply(stanza, "auth", "forbidden"));
end
return true;
end
module:hook("iq/bare/vcard-temp:vCard", handle_vcard);
module:hook("iq/host/vcard-temp:vCard", handle_vcard);
local function normalize_params(params)
assert(params.driver and params.database,
"Configuration error: Both the SQL driver and the database need to be specified");
return params;
end
function module.load()
if prosody.prosodyctl then return; end
local engines = module:shared("/*/sql/connections");
local params = normalize_params(module:get_option("auth_sql", default_params));
engine = engines[sql.db2uri(params)];
if not engine then
module:log("debug", "Creating new engine");
engine = sql:create_engine(params);
engines[sql.db2uri(params)] = engine;
end
engine:connect();
module:log("debug", "load OK");
end

@ -1,177 +0,0 @@
-- Load vhosts from DB on startup of Prosody XMPP server.
-- Copyright (C) 2013-2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:set_global();
local DBI = require "DBI"
local nameprep = require "util.encodings".stringprep.nameprep;
local hostmanager = require "core.hostmanager";
local configmanager = require "core.configmanager";
local ut = require "ngcp.utils";
local set = require "util.set";
local connection;
local params = module:get_option("auth_sql", module:get_option("sql"));
local prosody = _G.prosody;
local vhosts = set.new();
local function test_connection()
module:log("debug", "test_connection");
if not connection then return nil; end
if connection:ping() then
module:log("debug", "test_connection ok");
return true;
else
module:log("debug", "Database connection closed");
connection = nil;
end
end
local function connect()
module:log("debug", "connect");
if not test_connection() then
prosody.unlock_globals();
local dbh, err = DBI.Connect(
params.driver, params.database,
params.username, params.password,
params.host, params.port
);
prosody.lock_globals();
if not dbh then
module:log("debug",
"Database connection failed: %s", tostring(err));
return nil, err;
end
module:log("debug", "Successfully connected to database");
dbh:autocommit(true); -- don't run in transaction
connection = dbh;
return connection;
end
end
do -- process options to get a db connection
params = params or { driver = "SQLite3" };
if params.driver == "SQLite3" then
params.database = configmanager.resolve_relative_path(
prosody.paths.data or ".",
params.database or "prosody.sqlite");
end
assert(params.driver and params.database,
"Both the SQL driver and the database need to be specified");
assert(connect());
end
local function getsql(sql, ...)
if params.driver == "PostgreSQL" then
sql = sql:gsub("`", "\"");
end
if not test_connection() then connect(); end
-- do prepared statement stuff
local stmt, err = connection:prepare(sql);
if not stmt and not test_connection() then error("connection failed"); end
if not stmt then
module:log("error", "QUERY FAILED: %s %s", err, debug.traceback());
return nil, err;
end
-- run query
local ok
ok, err = stmt:execute(...);
if not ok and not test_connection() then error("connection failed"); end
if not ok then return nil, err; end
return stmt;
end
local function activate_search(host)
module:log("debug", "activate implicit search.%s", host);
configmanager.set("search."..host, "component_module",
"sipwise_vjud");
local search_config = configmanager.getconfig()["search."..host];
hostmanager.activate("search."..host, search_config);
end
local function activate_muc(host)
local host_modules = configmanager.get(host, "modules_enabled");
local conference_modules = {};
module:log("debug", "modules_enabled[%s]: %s", host,
ut.table.tostring(host_modules));
configmanager.set("conference."..host, "component_module",
"muc");
if ut.table.contains(host_modules, "shard") then
ut.table.add(conference_modules, "sipwise_redis_mucs");
ut.table.add(conference_modules, "shard");
end
if ut.table.contains(host_modules, "sipwise_pushd") then
ut.table.add(conference_modules, "sipwise_pushd");
end
if ut.table.contains(host_modules, "mam") then
ut.table.add(conference_modules, "muc_mam");
end
module:log("debug", "conference_modules[%s]: %s",
"conference."..host, ut.table.tostring(host_modules));
configmanager.set("conference."..host, "modules_enabled",
conference_modules);
local conference_config = configmanager.getconfig()["conference."..host];
conference_config['restrict_room_creation'] = 'local';
hostmanager.activate("conference."..host, conference_config);
module:log("debug", "modules_enabled[%s]: %s", "conference."..host,
ut.table.tostring(conference_config['modules_enabled']));
end
local function add_vhost(host)
local host_config = { enable = true };
module:log("debug", "activate host %s", host);
vhosts:add(host);
hostmanager.activate(host, host_config);
activate_search(host);
activate_muc(host);
end
local function load_vhosts_from_db()
local stmt, _ = getsql("SELECT `domain` FROM `domain`");
if stmt then
for row in stmt:rows(true) do
local host = nameprep(row.domain);
add_vhost(host);
end
end
end
local function check_vhosts(host)
if ut.string.starts(host, 'search.') or
ut.string.starts(host, 'conference.') or
vhosts:contains(host) then
module:log("debug", "%s activated, nothing to do", host);
return
end
module:log("info", "%s activate subdomains", host);
activate_search(host);
activate_muc(host);
vhosts:add(host);
end
local function deactivate_vhost(host)
if ut.string.starts(host, 'search.') or
ut.string.starts(host, 'conference.') or
not vhosts:contains(host) then
module:log("debug", "%s deactivated, nothing to do", host);
return
end
module:log("info", "%s deactivate subdomains", host);
hostmanager.deactivate("search."..host, 'main domain deactivated');
hostmanager.deactivate("conference."..host, 'main domain deactivated');
vhosts:remove(host);
end
module:hook("server-started", load_vhosts_from_db);
module:hook("host-activated", check_vhosts);
module:hook("host-deactivated", deactivate_vhost);

@ -1,336 +0,0 @@
--
-- Copyright (C) 2013-2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:depends("disco");
local softreq = require "util.dependencies".softreq;
local ut_jid = require "util.jid";
local sql = require "util.sql";
local st = require "util.stanza";
local template = require "util.template";
local prosodyctl = require "util.prosodyctl"
local dataforms_new = require "util.dataforms".new;
local ut = require "ngcp.utils";
local hosts = prosody.hosts;
local rex = softreq "rex_pcre";
if not rex then
rex = require "rex_pcre2";
end
local form_layout = dataforms_new{
title= 'User Directory Search';
instructions = 'Please provide the following information to search for subscribers';
{
type = 'text-single',
label = 'e164 Phone number',
name = 'e164',
required = false,
},
{
type = 'text-single',
label = 'domain',
name = 'domain',
required = false,
},
};
local form_item = template[[
<item>
<field var='{name}'>
<value>{value}</value>
</field>
</item>
]];
local form_reply = {
domain = template[[
<query xmlns='jabber:iq:search'>
<x xmlns='jabber:x:data' type='result'>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<reported>
<field type='text-single'
label='domain'
var='domain'/>
</reported>
</x>
</query>
]],
e164 = template[[
<query xmlns='jabber:iq:search'>
<x xmlns='jabber:x:data' type='result'>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<reported>
<field type='text-single'
label='e164 Phone number'
var='e164'/>
</reported>
</x>
</query>
]],
form = template[[
<query xmlns='jabber:iq:search'>
<instructions>Use the enclosed form to search</instructions>
<x xmlns='jabber:x:data' type='form'>
<title>User Directory Search</title>
<instructions>Please provide the following information to search for subscribers</instructions>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<field type='text-single'
label='e164 Phone number'
var='e164'/>
<field type='text-single'
label='domain'
var='domain'/>
</x>
<nick/>
</query>
]]
};
local usr_replacements_query = [[
SELECT vrr.match_pattern, vrr.replace_pattern FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
LEFT JOIN provisioning.voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vup.value
LEFT JOIN provisioning.voip_rewrite_rules vrr ON vrr.set_id = vrrs.id
AND vrr.direction = 'in'
AND vrr.field = 'callee'
WHERE vp.attribute = 'rewrite_callee_in_dpid' AND vs.username = ? AND vd.domain = ?
ORDER BY vrr.priority ASC;
]];
local dom_replacements_query = [[
SELECT vrr.match_pattern, vrr.replace_pattern FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_dom_preferences vdp ON vdp.attribute_id = vp.id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vdp.domain_id
LEFT JOIN provisioning.voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vdp.value
LEFT JOIN provisioning.voip_rewrite_rules vrr ON vrr.set_id = vrrs.id
AND vrr.direction = 'in' AND vrr.field = 'callee'
WHERE vp.attribute = 'rewrite_callee_in_dpid' AND vd.domain = ?
ORDER BY vrr.priority ASC;
]];
local locale_query = [[
SELECT vp.attribute, vup.value FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
WHERE (vp.attribute = 'ac' or vp.attribute = 'cc')
AND vs.username = ?
AND vd.domain = ?;
]];
local lookup_query = [[
SELECT username,domain FROM kamailio.dbaliases
WHERE alias_username=?;
]];
local default_params = module:get_option("sql");
local engine;
-- Reconnect to DB if necessary
local function reconect_check()
if not engine.conn:ping() then
engine.conn = nil;
engine:connect();
end
end
local function normalize_number(user, host, number)
local locale_info = {};
reconect_check();
for row in engine:select(locale_query, user, host) do
locale_info["caller_"..row[1]] = row[2];
end
local replacement_regexes = {};
local usr_pref = 0;
for row in engine:select(usr_replacements_query, user, host) do
usr_pref = 1;
module:log("debug", "user rewrite_callee_in_dpid preference found");
local patt, repl = row[1], row[2]
:gsub("%$avp%(s:([a-zA-Z_]+)%)", locale_info)
:gsub("\\", "%%"):gsub("%%%%", "\\");
table.insert(replacement_regexes, { patt, repl });
end
if usr_pref == 0 then
for row in engine:select(dom_replacements_query, host) do
module:log("debug", "domain rewrite_callee_in_dpid preference found");
local patt, repl = row[1], row[2]
:gsub("%$avp%(s:([a-zA-Z_]+)%)", locale_info)
:gsub("\\", "%%"):gsub("%%%%", "\\");
table.insert(replacement_regexes, { patt, repl });
end
end
for _, rule in ipairs(replacement_regexes) do
local new_number, n_matches = rex.gsub(number, rule[1], rule[2]);
if n_matches > 0 then
module:log("debug", "rule [%s] matched [%s]->[%s]",
tostring(rule[1]), tostring(number), tostring(new_number));
number = new_number;
break;
end
end
return number;
end
local function search_by_number(number)
local results = {};
reconect_check();
module:log("debug", "search jids with number:[%s]", tostring(number));
for result in engine:select(lookup_query, number) do
table.insert(results, result[1].."@"..result[2]);
end
return results;
end
local function get_dataform_items(vals, fieldname)
local res = {};
for _,v in ipairs(vals) do
table.insert(res, form_item.apply({ name = fieldname, value = v }));
end
return res;
end
local function search_domains(dom)
local hosts_keys = ut.table.keys(hosts);
local res = {};
module:log("debug", "search for domain: %s", tostring(dom));
if dom == '*' then
res = get_dataform_items(hosts_keys, 'domain');
elseif ut.table.contains(hosts_keys, dom) then
res = get_dataform_items({dom,}, 'domain');
end
return res;
end
function module.command(arg)
local warn = prosodyctl.show_warning;
local command = arg[1];
if not command then
warn("Valid subcommands: normalize");
return 0;
end
table.remove(arg, 1);
if command == "normalize" then
if #arg ~= 2 then
warn("Usage: normalize USER@HOST NUMBER");
return 1;
end
local user_jid, number = arg[1], arg[2];
local user, host = ut_jid.prepped_split(user_jid);
if not (user and host) then
warn("Invalid JID: "..user_jid);
return 1;
end
print(normalize_number(user, host, number));
elseif command == "query" then
if #arg ~= 1 then
warn("Usage: query NUMBER");
warn(" NUMBER must be normalized (see 'normalize' command)");
return 1;
end
local results = search_by_number(arg[1]);
for _, jid in ipairs(results) do
print("", jid);
end
end
return 0;
end
local function append_x_items(form, values, reply)
local x = form:get_child('x', 'jabber:x:data');
for _, v in ipairs(values) do
x:add_child(v):up();
end
reply:add_child(x);
end
if module:get_host_type() ~= "component" then
error("Don't load mod_sipwise_vjud manually,"..
" it should be for a component", 0);
end
module:add_feature("jabber:iq:search");
module:add_feature("jabber:x:data"); -- dataforms
module:hook("iq/host/jabber:iq:search:query", function(event)
local origin, stanza = event.origin, event.stanza;
module:log("debug", "stanza[%s]", tostring(stanza));
if stanza.attr.type == "get" then
return origin.send(st.reply(stanza):add_child(form_reply.form.apply({})));
else
local user, host = ut_jid.split(stanza.attr.from);
local query = stanza:get_child("query",'jabber:iq:search');
local form_stanza = query:get_child("x",'jabber:x:data');
local reply;
local search_number = stanza.tags[1]:get_child_text("nick");
local search_domain
if form_stanza then
local form_data, form_errors = form_layout:data(form_stanza);
if form_errors and form_errors['e164'] then
reply = st.error_reply(stanza, "modify",
"bad-request", "e164: "..form_errors['e164']);
return origin.send(reply);
else
search_number = form_data['e164'] or search_number;
search_domain = form_data['domain'];
end
end
if search_number then
local number = normalize_number(user, host, search_number);
local data = search_by_number(number);
reply = st.reply(stanza):query("jabber:iq:search");
if form_stanza then
append_x_items(form_reply.e164.apply({}),
get_dataform_items(data, 'e164'), reply);
else
for _, jid in ipairs(data) do
reply:tag("item", { jid = jid }):up();
end
end
elseif search_domain then
reply = st.reply(stanza):query("jabber:iq:search");
append_x_items(form_reply.domain.apply({}),
search_domains(search_domain), reply);
else
reply = st.error_reply(stanza, "modify",
"bad-request", "no domain, nick or e164 field found");
end
return origin.send(reply);
end
end);
local function normalize_params(params)
assert(params.driver and params.database,
"Configuration error: Both the SQL driver and the database need to be specified");
return params;
end
function module.load()
if prosody.prosodyctl then return; end
local engines = module:shared("/*/sql/connections");
local params = normalize_params(module:get_option("auth_sql", default_params));
engine = engines[sql.db2uri(params)];
if not engine then
module:log("debug", "Creating new engine");
engine = sql:create_engine(params);
engines[sql.db2uri(params)] = engine;
end
engine:connect();
module:log("debug", "load OK");
end

@ -1,91 +0,0 @@
---
labels:
- 'Stage-Alpha'
summary: 'XEP-0198: Reliability and fast reconnects for XMPP'
...
Introduction
============
By default XMPP is as reliable as your network is. Unfortunately in some
cases that is not very reliable - in some network conditions disconnects
can be frequent and message loss can occur.
To overcome this, XMPP has an optional extension (XEP-0198: Stream
Management) which, when supported by both the client and server, can
allow a client to resume a disconnected session, and prevent message
loss.
Details
=======
When using XEP-0198 both the client and the server keep a queue of the
most recently sent stanzas - this is cleared when the other end
acknowledges they have received the stanzas. If the client disconnects,
instead of marking the user offline the server pretends the client is
still online for a short (configurable) period of time. If the client
reconnects within this period, any stanzas in the queue that the client
did not receive are re-sent.
If the client fails to reconnect before the timeout it will be marked as
offline like prosody does on disconnect without mod_smacks.
If the client is the last one for this jid, all message stanzas are added to
the offline store and all other stanzas stanzas are returned with an
"recipient-unavailable" error. If the client is not the last one with an
open smacks session, *all* stanzas are returned with an "recipient-unavailable" error.
If you deliberately disabled [mod_offline], all message stanzas of the last client
are also returned with an "recipient-unavailable" error, because the can not be
added to the offline storage.
If you don't want this behaviour you can use [mod_nooffline_noerror] to suppress the error.
This is generally only advisable, if you are sure that all your clients are using MAM!
This module also provides some events used by [mod_cloud_notify].
These events are: "smacks-ack-delayed", "smacks-hibernation-start" and
"smacks-hibernation-end". See [mod_cloud_notify] for details on how this
events are used there.
Use prosody 0.10+ to have per user limits on allowed sessions in hibernation
state and allowed sessions for which the h-value is kept even after the
hibernation timed out.
These are settable using `smacks_max_hibernated_sessions` and `smacks_max_old_sessions`.
Configuration
=============
Option Default Description
---------------------------------- ----------------- ------------------------------------------------------------------------------------------------------------------
`smacks_hibernation_time` 600 (10 minutes) The number of seconds a disconnected session should stay alive for (to allow reconnect)
`smacks_enabled_s2s` true Enable Stream Management on server connections? *Experimental*
`smacks_s2s_resend` false Attempt to re-send unacked messages on s2s disconnect *Experimental*
`smacks_max_unacked_stanzas` 0 How many stanzas to send before requesting acknowledgement
`smacks_max_ack_delay` 30 (1/2 minute) The number of seconds an ack must be unanswered to trigger an "smacks-ack-delayed" event
`smacks_max_hibernated_sessions` 10 The number of allowed sessions in hibernated state (limited per user)
`smacks_max_old_sessions` 10 The number of allowed sessions with timed out hibernation for which the h-value is still kept (limited per user)
Compatibility
=============
------- -------
trunk Works
0.11 Works
------- -------
Clients
=======
Clients that support [XEP-0198]:
- Gajim (Linux, Windows, OS X)
- Conversations (Android)
- ChatSecure (iOS)
- Swift (but not resumption, as of version 2.0 and alphas of 3.0)
- Psi (in an unreleased branch)
- Yaxim (Android)
- Monal (iOS)
[7693724881b3]: //hg.prosody.im/prosody-modules/raw-file/7693724881b3/mod_smacks/mod_smacks.lua
[mod_offline]: //modules.prosody.im/mod_offline
[mod_nooffline_noerror]: //modules.prosody.im/mod_nooffline_noerror
[mod_cloud_notify]: //modules.prosody.im/mod_cloud_notify

@ -1,690 +0,0 @@
-- XEP-0198: Stream Management for Prosody IM
--
-- Copyright (C) 2010-2015 Matthew Wild
-- Copyright (C) 2010 Waqas Hussain
-- Copyright (C) 2012-2021 Kim Alvefur
-- Copyright (C) 2012 Thijs Alkemade
-- Copyright (C) 2014 Florian Zeitz
-- Copyright (C) 2016-2020 Thilo Molitor
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st = require "util.stanza";
local dep = require "util.dependencies";
local cache = dep.softreq("util.cache"); -- only available in prosody 0.10+
local uuid_generate = require "util.uuid".generate;
local jid = require "util.jid";
local t_remove = table.remove;
local math_min = math.min;
local math_max = math.max;
local os_time = os.time;
local tonumber, tostring = tonumber, tostring;
local add_filter = require "util.filters".add_filter;
local timer = require "util.timer";
local datetime = require "util.datetime";
local xmlns_mam2 = "urn:xmpp:mam:2";
local xmlns_sm2 = "urn:xmpp:sm:2";
local xmlns_sm3 = "urn:xmpp:sm:3";
local xmlns_errors = "urn:ietf:params:xml:ns:xmpp-stanzas";
local xmlns_delay = "urn:xmpp:delay";
local sm2_attr = { xmlns = xmlns_sm2 };
local sm3_attr = { xmlns = xmlns_sm3 };
local resume_timeout = module:get_option_number("smacks_hibernation_time", 600);
local s2s_smacks = module:get_option_boolean("smacks_enabled_s2s", true);
local s2s_resend = module:get_option_boolean("smacks_s2s_resend", false);
local max_unacked_stanzas = module:get_option_number("smacks_max_unacked_stanzas", 0);
local max_inactive_unacked_stanzas = module:get_option_number("smacks_max_inactive_unacked_stanzas", 256);
local delayed_ack_timeout = module:get_option_number("smacks_max_ack_delay", 30);
local max_hibernated_sessions = module:get_option_number("smacks_max_hibernated_sessions", 10);
local max_old_sessions = module:get_option_number("smacks_max_old_sessions", 10);
local core_process_stanza = prosody.core_process_stanza;
local sessionmanager = require"core.sessionmanager";
assert(max_hibernated_sessions > 0, "smacks_max_hibernated_sessions must be greater than 0");
assert(max_old_sessions > 0, "smacks_max_old_sessions must be greater than 0");
local c2s_sessions = module:shared("/*/c2s/sessions");
local function init_session_cache(max_entries, evict_callback)
-- old prosody version < 0.10 (no limiting at all!)
if not cache then
local store = {};
return {
get = function(user, key)
if not user then return nil; end
if not key then return nil; end
return store[key];
end;
set = function(user, key, value)
if not user then return nil; end
if not key then return nil; end
store[key] = value;
end;
};
end
-- use per user limited cache for prosody >= 0.10
local stores = {};
return {
get = function(user, key)
if not user then return nil; end
if not key then return nil; end
if not stores[user] then
stores[user] = cache.new(max_entries, evict_callback);
end
return stores[user]:get(key);
end;
set = function(user, key, value)
if not user then return nil; end
if not key then return nil; end
if not stores[user] then stores[user] = cache.new(max_entries, evict_callback); end
stores[user]:set(key, value);
-- remove empty caches completely
if not stores[user]:count() then stores[user] = nil; end
end;
};
end
local old_session_registry = init_session_cache(max_old_sessions, nil);
local session_registry = init_session_cache(max_hibernated_sessions, function(resumption_token, session)
if session.destroyed then return true; end -- destroyed session can always be removed from cache
session.log("warn", "User has too much hibernated sessions, removing oldest session (token: %s)", resumption_token);
-- store old session's h values on force delete
-- save only actual h value and username/host (for security)
old_session_registry.set(session.username, resumption_token, {
h = session.handled_stanza_count,
username = session.username,
host = session.host
});
return true; -- allow session to be removed from full cache to make room for new one
end);
local function stoppable_timer(delay, callback)
local stopped = false;
local timer = module:add_timer(delay, function (t)
if stopped then return; end
return callback(t);
end);
if timer and timer.stop then return timer; end -- new prosody api includes stop() function
return {
stop = function(self) stopped = true end;
timer;
};
end
local function delayed_ack_function(session, stanza)
-- fire event only if configured to do so and our session is not already hibernated or destroyed
if delayed_ack_timeout > 0 and session.awaiting_ack
and not session.hibernating and not session.destroyed then
session.log("debug", "Firing event 'smacks-ack-delayed', queue = %d",
session.outgoing_stanza_queue and #session.outgoing_stanza_queue or 0);
module:fire_event("smacks-ack-delayed", {origin = session, queue = session.outgoing_stanza_queue, stanza = stanza});
end
session.delayed_ack_timer = nil;
end
local function can_do_smacks(session, advertise_only)
if session.smacks then return false, "unexpected-request", "Stream management is already enabled"; end
local session_type = session.type;
if session.username then
if not(advertise_only) and not(session.resource) then -- Fail unless we're only advertising sm
return false, "unexpected-request", "Client must bind a resource before enabling stream management";
end
return true;
elseif s2s_smacks and (session_type == "s2sin" or session_type == "s2sout") then
return true;
end
return false, "service-unavailable", "Stream management is not available for this stream";
end
module:hook("stream-features",
function (event)
if can_do_smacks(event.origin, true) then
event.features:tag("sm", sm2_attr):tag("optional"):up():up();
event.features:tag("sm", sm3_attr):tag("optional"):up():up();
end
end);
module:hook("s2s-stream-features",
function (event)
if can_do_smacks(event.origin, true) then
event.features:tag("sm", sm2_attr):tag("optional"):up():up();
event.features:tag("sm", sm3_attr):tag("optional"):up():up();
end
end);
local function request_ack_if_needed(session, force, reason, stanza)
local queue = session.outgoing_stanza_queue;
local expected_h = session.last_acknowledged_stanza + #queue;
-- session.log("debug", "*** SMACKS(1) ***: awaiting_ack=%s, hibernating=%s", tostring(session.awaiting_ack), tostring(session.hibernating));
local max_unacked = max_unacked_stanzas;
if session.state == "inactive" then
max_unacked = max_inactive_unacked_stanzas;
end
if session.awaiting_ack == nil and not session.hibernating then
-- this check of last_requested_h prevents ack-loops if missbehaving clients report wrong
-- stanza counts. it is set when an <r> is really sent (e.g. inside timer), preventing any
-- further requests until a higher h-value would be expected.
-- session.log("debug", "*** SMACKS(2) ***: #queue=%s, max_unacked_stanzas=%s, expected_h=%s, last_requested_h=%s", tostring(#queue), tostring(max_unacked_stanzas), tostring(expected_h), tostring(session.last_requested_h));
if (#queue > max_unacked and expected_h ~= session.last_requested_h) or force then
session.log("debug", "Queuing <r> (in a moment) from %s - #queue=%d", reason, #queue);
session.awaiting_ack = false;
session.awaiting_ack_timer = stoppable_timer(1e-06, function ()
-- session.log("debug", "*** SMACKS(3) ***: awaiting_ack=%s, hibernating=%s", tostring(session.awaiting_ack), tostring(session.hibernating));
-- only request ack if needed and our session is not already hibernated or destroyed
if not session.awaiting_ack and not session.hibernating and not session.destroyed then
session.log("debug", "Sending <r> (inside timer, before send) from %s - #queue=%d", reason, #queue);
(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }))
if session.destroyed then return end -- sending something can trigger destruction
session.awaiting_ack = true;
-- expected_h could be lower than this expression e.g. more stanzas added to the queue meanwhile)
session.last_requested_h = session.last_acknowledged_stanza + #queue;
session.log("debug", "Sending <r> (inside timer, after send) from %s - #queue=%d", reason, #queue);
if not session.delayed_ack_timer then
session.delayed_ack_timer = stoppable_timer(delayed_ack_timeout, function()
delayed_ack_function(session, nil); -- we don't know if this is the only new stanza in the queue
end);
end
end
end);
end
end
-- Trigger "smacks-ack-delayed"-event if we added new (ackable) stanzas to the outgoing queue
-- and there isn't already a timer for this event running.
-- If we wouldn't do this, stanzas added to the queue after the first "smacks-ack-delayed"-event
-- would not trigger this event (again).
if #queue > max_unacked and session.awaiting_ack and session.delayed_ack_timer == nil then
session.log("debug", "Calling delayed_ack_function directly (still waiting for ack)");
delayed_ack_function(session, stanza); -- this is the only new stanza in the queue --> provide it to other modules
end
end
local function outgoing_stanza_filter(stanza, session)
-- XXX: Normally you wouldn't have to check the xmlns for a stanza as it's
-- supposed to be nil.
-- However, when using mod_smacks with mod_websocket, then mod_websocket's
-- stanzas/out filter can get called before this one and adds the xmlns.
local is_stanza = stanza.attr and
(not stanza.attr.xmlns or stanza.attr.xmlns == 'jabber:client')
and not stanza.name:find":";
if is_stanza and not stanza._cached then
local queue = session.outgoing_stanza_queue;
local cached_stanza = st.clone(stanza);
cached_stanza._cached = true;
if cached_stanza and cached_stanza.name ~= "iq" and cached_stanza:get_child("delay", xmlns_delay) == nil then
cached_stanza = cached_stanza:tag("delay", {
xmlns = xmlns_delay,
from = jid.bare(session.full_jid or session.host),
stamp = datetime.datetime()
});
end
queue[#queue+1] = cached_stanza;
if session.hibernating then
session.log("debug", "hibernating since %s, stanza queued", datetime.datetime(session.hibernating));
module:fire_event("smacks-hibernation-stanza-queued", {origin = session, queue = queue, stanza = cached_stanza});
return nil;
end
request_ack_if_needed(session, false, "outgoing_stanza_filter", stanza);
end
return stanza;
end
local function count_incoming_stanzas(stanza, session)
if not stanza.attr.xmlns then
session.handled_stanza_count = session.handled_stanza_count + 1;
session.log("debug", "Handled %d incoming stanzas", session.handled_stanza_count);
end
return stanza;
end
local function wrap_session_out(session, resume)
if not resume then
session.outgoing_stanza_queue = {};
session.last_acknowledged_stanza = 0;
end
add_filter(session, "stanzas/out", outgoing_stanza_filter, -999);
local session_close = session.close;
function session.close(...)
if session.resumption_token then
session_registry.set(session.username, session.resumption_token, nil);
old_session_registry.set(session.username, session.resumption_token, nil);
session.resumption_token = nil;
end
-- send out last ack as per revision 1.5.2 of XEP-0198
if session.smacks and session.conn and session.handled_stanza_count then
(session.sends2s or session.send)(st.stanza("a", { xmlns = session.smacks, h = string.format("%d", session.handled_stanza_count) }));
end
return session_close(...);
end
return session;
end
local function wrap_session_in(session, resume)
if not resume then
session.handled_stanza_count = 0;
end
add_filter(session, "stanzas/in", count_incoming_stanzas, 999);
return session;
end
local function wrap_session(session, resume)
wrap_session_out(session, resume);
wrap_session_in(session, resume);
return session;
end
function handle_enable(session, stanza, xmlns_sm)
local ok, err, err_text = can_do_smacks(session);
if not ok then
session.log("warn", "Failed to enable smacks: %s", err_text); -- TODO: XEP doesn't say we can send error text, should it?
(session.sends2s or session.send)(st.stanza("failed", { xmlns = xmlns_sm }):tag(err, { xmlns = xmlns_errors}));
return true;
end
module:log("debug", "Enabling stream management");
session.smacks = xmlns_sm;
wrap_session(session, false);
local resume_token;
local resume = stanza.attr.resume;
if resume == "true" or resume == "1" then
resume_token = uuid_generate();
session_registry.set(session.username, resume_token, session);
session.resumption_token = resume_token;
end
(session.sends2s or session.send)(st.stanza("enabled", { xmlns = xmlns_sm, id = resume_token, resume = resume, max = tostring(resume_timeout) }));
return true;
end
module:hook_stanza(xmlns_sm2, "enable", function (session, stanza) return handle_enable(session, stanza, xmlns_sm2); end, 100);
module:hook_stanza(xmlns_sm3, "enable", function (session, stanza) return handle_enable(session, stanza, xmlns_sm3); end, 100);
module:hook_stanza("http://etherx.jabber.org/streams", "features",
function (session, stanza)
stoppable_timer(1e-6, function ()
if can_do_smacks(session) then
if stanza:get_child("sm", xmlns_sm3) then
session.sends2s(st.stanza("enable", sm3_attr));
session.smacks = xmlns_sm3;
elseif stanza:get_child("sm", xmlns_sm2) then
session.sends2s(st.stanza("enable", sm2_attr));
session.smacks = xmlns_sm2;
else
return;
end
wrap_session_out(session, false);
end
end);
end);
function handle_enabled(session, stanza, xmlns_sm)
module:log("debug", "Enabling stream management");
session.smacks = xmlns_sm;
wrap_session_in(session, false);
-- FIXME Resume?
return true;
end
module:hook_stanza(xmlns_sm2, "enabled", function (session, stanza) return handle_enabled(session, stanza, xmlns_sm2); end, 100);
module:hook_stanza(xmlns_sm3, "enabled", function (session, stanza) return handle_enabled(session, stanza, xmlns_sm3); end, 100);
function handle_r(origin, stanza, xmlns_sm)
if not origin.smacks then
module:log("debug", "Received ack request from non-smack-enabled session");
return;
end
module:log("debug", "Received ack request, acking for %d", origin.handled_stanza_count);
-- Reply with <a>
(origin.sends2s or origin.send)(st.stanza("a", { xmlns = xmlns_sm, h = string.format("%d", origin.handled_stanza_count) }));
-- piggyback our own ack request if needed (see request_ack_if_needed() for explanation of last_requested_h)
local expected_h = origin.last_acknowledged_stanza + #origin.outgoing_stanza_queue;
if #origin.outgoing_stanza_queue > 0 and expected_h ~= origin.last_requested_h then
request_ack_if_needed(origin, true, "piggybacked by handle_r", nil);
end
return true;
end
module:hook_stanza(xmlns_sm2, "r", function (origin, stanza) return handle_r(origin, stanza, xmlns_sm2); end);
module:hook_stanza(xmlns_sm3, "r", function (origin, stanza) return handle_r(origin, stanza, xmlns_sm3); end);
function handle_a(origin, stanza)
if not origin.smacks then return; end
origin.awaiting_ack = nil;
if origin.awaiting_ack_timer then
origin.awaiting_ack_timer:stop();
end
if origin.delayed_ack_timer then
origin.delayed_ack_timer:stop();
origin.delayed_ack_timer = nil;
end
-- Remove handled stanzas from outgoing_stanza_queue
-- origin.log("debug", "ACK: h=%s, last=%s", stanza.attr.h or "", origin.last_acknowledged_stanza or "");
local h = tonumber(stanza.attr.h);
if not h then
origin:close{ condition = "invalid-xml"; text = "Missing or invalid 'h' attribute"; };
return;
end
local handled_stanza_count = h-origin.last_acknowledged_stanza;
local queue = origin.outgoing_stanza_queue;
if handled_stanza_count > #queue then
origin.log("warn", "The client says it handled %d new stanzas, but we only sent %d :)",
handled_stanza_count, #queue);
origin.log("debug", "Client h: %d, our h: %d", tonumber(stanza.attr.h), origin.last_acknowledged_stanza);
for i=1,#queue do
origin.log("debug", "Q item %d: %s", i, tostring(queue[i]));
end
origin:close{ condition = "undefined-condition"; text = "Client acknowledged more stanzas than sent by server"; };
return;
end
for i=1,math_min(handled_stanza_count,#queue) do
local handled_stanza = t_remove(origin.outgoing_stanza_queue, 1);
module:fire_event("delivery/success", { session = origin, stanza = handled_stanza });
end
origin.log("debug", "#queue = %d", #queue);
origin.last_acknowledged_stanza = origin.last_acknowledged_stanza + handled_stanza_count;
request_ack_if_needed(origin, false, "handle_a", nil)
return true;
end
module:hook_stanza(xmlns_sm2, "a", handle_a);
module:hook_stanza(xmlns_sm3, "a", handle_a);
--TODO: Optimise... incoming stanzas should be handled by a per-session
-- function that has a counter as an upvalue (no table indexing for increments,
-- and won't slow non-198 sessions). We can also then remove the .handled flag
-- on stanzas
local function handle_unacked_stanzas(session)
local queue = session.outgoing_stanza_queue;
local error_attr = { type = "cancel" };
if #queue > 0 then
session.outgoing_stanza_queue = {};
for i=1,#queue do
if not module:fire_event("delivery/failure", { session = session, stanza = queue[i] }) then
if queue[i].attr.type ~= "error" then
local reply = st.reply(queue[i]);
if reply.attr.to ~= session.full_jid then
reply.attr.type = "error";
reply:tag("error", error_attr)
:tag("recipient-unavailable", {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"});
core_process_stanza(session, reply);
end
end
end
end
end
end
-- don't send delivery errors for messages which will be delivered by mam later on
-- check if stanza was archived --> this will allow us to send back errors for stanzas not archived
-- because the user configured the server to do so ("no-archive"-setting for one special contact for example)
local function get_stanza_id(stanza, by_jid)
for tag in stanza:childtags("stanza-id", "urn:xmpp:sid:0") do
if tag.attr.by == by_jid then
return tag.attr.id;
end
end
return nil;
end
module:hook("delivery/failure", function(event)
local session, stanza = event.session, event.stanza;
-- Only deal with authenticated (c2s) sessions
if session.username then
if stanza.name == "message" and stanza.attr.xmlns == nil and
( stanza.attr.type == "chat" or ( stanza.attr.type or "normal" ) == "normal" ) then
-- don't store messages in offline store if they are mam results
local mam_result = stanza:get_child("result", xmlns_mam2);
if mam_result ~= nil then
return true; -- stanza already "handled", don't send an error and don't add it to offline storage
end
-- do nothing here for normal messages and don't send out "message delivery errors",
-- because messages are already in MAM at this point (no need to frighten users)
local stanza_id = get_stanza_id(stanza, jid.bare(session.full_jid));
if session.mam_requested and stanza_id ~= nil then
session.log("debug", "mod_smacks delivery/failure returning true for mam-handled stanza: mam-archive-id=%s", tostring(stanza_id));
return true; -- stanza handled, don't send an error
end
-- store message in offline store, if this client does not use mam *and* was the last client online
local sessions = prosody.hosts[module.host].sessions[session.username] and
prosody.hosts[module.host].sessions[session.username].sessions or nil;
if sessions and next(sessions) == session.resource and next(sessions, session.resource) == nil then
local ok = module:fire_event("message/offline/handle", { origin = session, username = session.username, stanza = stanza });
session.log("debug", "mod_smacks delivery/failuere returning %s for offline-handled stanza", tostring(ok));
return ok; -- if stanza was handled, don't send an error
end
end
end
end);
module:hook("pre-resource-unbind", function (event)
local session, err = event.session, event.error;
if session.smacks then
if not session.resumption_token then
local queue = session.outgoing_stanza_queue;
if #queue > 0 then
session.log("debug", "Destroying session with %d unacked stanzas", #queue);
handle_unacked_stanzas(session);
end
else
session.log("debug", "mod_smacks hibernating session for up to %d seconds", resume_timeout);
local hibernate_time = os_time(); -- Track the time we went into hibernation
session.hibernating = hibernate_time;
local resumption_token = session.resumption_token;
module:fire_event("smacks-hibernation-start", {origin = session, queue = session.outgoing_stanza_queue});
timer.add_task(resume_timeout, function ()
session.log("debug", "mod_smacks hibernation timeout reached...");
-- We need to check the current resumption token for this resource
-- matches the smacks session this timer is for in case it changed
-- (for example, the client may have bound a new resource and
-- started a new smacks session, or not be using smacks)
local curr_session = full_sessions[session.full_jid];
if session.destroyed then
session.log("debug", "The session has already been destroyed");
elseif curr_session and curr_session.resumption_token == resumption_token
-- Check the hibernate time still matches what we think it is,
-- otherwise the session resumed and re-hibernated.
and session.hibernating == hibernate_time then
-- wait longer if the timeout isn't reached because push was enabled for this session
-- session.first_hibernated_push is the starting point for hibernation timeouts of those push enabled clients
-- wait for an additional resume_timeout seconds if no push occurred since hibernation at all
local current_time = os_time();
local timeout_start = math_max(session.hibernating, session.first_hibernated_push or session.hibernating);
if session.push_identifier ~= nil and not session.first_hibernated_push then
session.log("debug", "No push happened since hibernation started, hibernating session for up to %d extra seconds", resume_timeout);
return resume_timeout;
end
if session.push_identifier ~= nil and current_time-timeout_start < resume_timeout then
session.log("debug", "A push happened since hibernation started, hibernating session for up to %d extra seconds", resume_timeout-(current_time-timeout_start));
return resume_timeout-(current_time-timeout_start); -- time left to wait
end
session.log("debug", "Destroying session for hibernating too long");
session_registry.set(session.username, session.resumption_token, nil);
-- save only actual h value and username/host (for security)
old_session_registry.set(session.username, session.resumption_token, {
h = session.handled_stanza_count,
username = session.username,
host = session.host
});
session.resumption_token = nil;
sessionmanager.destroy_session(session);
else
session.log("debug", "Session resumed before hibernation timeout, all is well")
end
end);
return true; -- Postpone destruction for now
end
end
end);
local function handle_s2s_destroyed(event)
local session = event.session;
local queue = session.outgoing_stanza_queue;
if queue and #queue > 0 then
session.log("warn", "Destroying session with %d unacked stanzas", #queue);
if s2s_resend then
for i = 1, #queue do
module:send(queue[i]);
end
session.outgoing_stanza_queue = nil;
else
handle_unacked_stanzas(session);
end
end
end
module:hook("s2sout-destroyed", handle_s2s_destroyed);
module:hook("s2sin-destroyed", handle_s2s_destroyed);
local function get_session_id(session)
return session.id or (tostring(session):match("[a-f0-9]+$"));
end
function handle_resume(session, stanza, xmlns_sm)
if session.full_jid then
session.log("warn", "Tried to resume after resource binding");
session.send(st.stanza("failed", { xmlns = xmlns_sm })
:tag("unexpected-request", { xmlns = xmlns_errors })
);
return true;
end
local id = stanza.attr.previd;
local original_session = session_registry.get(session.username, id);
if not original_session then
session.log("debug", "Tried to resume non-existent session with id %s", id);
local old_session = old_session_registry.get(session.username, id);
if old_session and session.username == old_session.username
and session.host == old_session.host
and old_session.h then
session.send(st.stanza("failed", { xmlns = xmlns_sm, h = string.format("%d", old_session.h) })
:tag("item-not-found", { xmlns = xmlns_errors })
);
else
session.send(st.stanza("failed", { xmlns = xmlns_sm })
:tag("item-not-found", { xmlns = xmlns_errors })
);
end;
elseif session.username == original_session.username
and session.host == original_session.host then
session.log("debug", "mod_smacks resuming existing session %s...", get_session_id(original_session));
original_session.log("debug", "mod_smacks session resumed from %s...", get_session_id(session));
-- TODO: All this should move to sessionmanager (e.g. session:replace(new_session))
if original_session.conn then
original_session.log("debug", "mod_smacks closing an old connection for this session");
local conn = original_session.conn;
c2s_sessions[conn] = nil;
conn:close();
end
local migrated_session_log = session.log;
original_session.ip = session.ip;
original_session.conn = session.conn;
original_session.send = session.send;
original_session.close = session.close;
original_session.filter = session.filter;
original_session.filter.session = original_session;
original_session.filters = session.filters;
original_session.stream = session.stream;
original_session.secure = session.secure;
original_session.hibernating = nil;
session.log = original_session.log;
session.type = original_session.type;
wrap_session(original_session, true);
-- Inform xmppstream of the new session (passed to its callbacks)
original_session.stream:set_session(original_session);
-- Similar for connlisteners
c2s_sessions[session.conn] = original_session;
original_session.send(st.stanza("resumed", { xmlns = xmlns_sm,
h = string.format("%d", original_session.handled_stanza_count), previd = id }));
-- Fake an <a> with the h of the <resume/> from the client
original_session:dispatch_stanza(st.stanza("a", { xmlns = xmlns_sm,
h = stanza.attr.h }));
-- Ok, we need to re-send any stanzas that the client didn't see
-- ...they are what is now left in the outgoing stanza queue
-- We have to use the send of "session" because we don't want to add our resent stanzas
-- to the outgoing queue again
local queue = original_session.outgoing_stanza_queue;
session.log("debug", "resending all unacked stanzas that are still queued after resume, #queue = %d", #queue);
for i=1,#queue do
session.send(queue[i]);
end
session.log("debug", "all stanzas resent, now disabling send() in this migrated session, #queue = %d", #queue);
function session.send(stanza)
migrated_session_log("error", "Tried to send stanza on old session migrated by smacks resume (maybe there is a bug?): %s", tostring(stanza));
return false;
end
module:fire_event("smacks-hibernation-end", {origin = session, resumed = original_session, queue = queue});
request_ack_if_needed(original_session, true, "handle_resume", nil);
else
module:log("warn", "Client %s@%s[%s] tried to resume stream for %s@%s[%s]",
session.username or "?", session.host or "?", session.type,
original_session.username or "?", original_session.host or "?", original_session.type);
session.send(st.stanza("failed", { xmlns = xmlns_sm })
:tag("not-authorized", { xmlns = xmlns_errors }));
end
return true;
end
module:hook_stanza(xmlns_sm2, "resume", function (session, stanza) return handle_resume(session, stanza, xmlns_sm2); end);
module:hook_stanza(xmlns_sm3, "resume", function (session, stanza) return handle_resume(session, stanza, xmlns_sm3); end);
module:hook("csi-client-active", function (event)
if event.origin.smacks then
request_ack_if_needed(event.origin, true, "csi-active", nil);
end
end);
module:hook("csi-flushing", function(event)
local session = event.session;
if session.smacks then
if not session.awaiting_ack and not session.hibernating and not session.destroyed then
session.log("debug", "Sending <r> (csi-flushing)");
session.awaiting_ack = true; -- The send() call may invoke this event again, so set this first
(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }))
end
end
end);
local function handle_read_timeout(event)
local session = event.session;
if session.smacks then
if session.awaiting_ack then
if session.awaiting_ack_timer then
session.awaiting_ack_timer:stop();
end
if session.delayed_ack_timer then
session.delayed_ack_timer:stop();
session.delayed_ack_timer = nil;
end
return false; -- Kick the session
end
session.log("debug", "Sending <r> (read timeout)");
(session.sends2s or session.send)(st.stanza("r", { xmlns = session.smacks }));
session.awaiting_ack = true;
if not session.delayed_ack_timer then
session.delayed_ack_timer = stoppable_timer(delayed_ack_timeout, function()
delayed_ack_function(session, nil);
end);
end
return true;
end
end
module:hook("s2s-read-timeout", handle_read_timeout);
module:hook("c2s-read-timeout", handle_read_timeout);

@ -1,31 +0,0 @@
---
labels:
- 'Stage-Beta'
summary: Limit presence stanzas to save traffic
...
Introduction
============
For most people 'presence' (status changes) of contacts make up most of
the traffic received by their client. However much of the time it is not
essential to have highly accurate presence information.
This module automatically cuts down on presence traffic when clients
indicate they are inactive (using the [CSI protocol](mod_csi.html)).
This is extremely valuable for mobile clients that wish to save battery
power while in the background.
Configuration
=============
Just load the module (e.g. in modules\_enabled). There are no
configuration options.
Compatibility
=============
----- -------
0.9 Works
----- -------

@ -1,53 +0,0 @@
local filters = require "util.filters";
local st = require "util.stanza";
module:depends("csi");
local function presence_filter(stanza, session)
if getmetatable(stanza) ~= st.stanza_mt then
return stanza; -- Things we don't want to touch
end
if stanza._flush then
stanza._flush = nil;
return stanza;
end
local buffer = session.presence_buffer;
local from = stanza.attr.from;
if stanza.name == "presence" and (stanza.attr.type == nil or stanza.attr.type == "unavailable") then
module:log("debug", "Buffering presence stanza from %s to %s", stanza.attr.from, session.full_jid);
buffer[stanza.attr.from] = st.clone(stanza);
return nil; -- Drop this stanza (we've stored it for later)
else
local cached_presence = buffer[stanza.attr.from];
if cached_presence then
module:log("debug", "Important stanza for %s from %s, flushing presence", session.full_jid, from);
stanza._flush = true;
cached_presence._flush = true;
session.send(cached_presence);
buffer[stanza.attr.from] = nil;
end
end
return stanza;
end
local function throttle_session(event)
local session = event.origin;
if session.presence_buffer then return; end
module:log("debug", "Suppressing presence updates to %s", session.full_jid);
session.presence_buffer = {};
filters.add_filter(session, "stanzas/out", presence_filter);
end
local function restore_session(event)
local session = event.origin;
if not session.presence_buffer then return; end
filters.remove_filter(session, "stanzas/out", presence_filter);
module:log("debug", "Flushing buffer for %s", session.full_jid);
for jid, presence in pairs(session.presence_buffer) do
session.send(presence);
end
session.presence_buffer = nil;
end
module:hook("csi-client-inactive", throttle_session);
module:hook("csi-client-active", restore_session);

@ -1,9 +0,0 @@
mod_auth_sql
mod_blocking
mod_carbons_adhoc
mod_carbons_copies
mod_filter_chatstates
mod_limit_auth
mod_log_auth
mod_smacks
mod_throttle_presence

@ -1 +0,0 @@
ecfd7aece33b

@ -1,33 +0,0 @@
# DOCKER_NAME=prosody-bookworm
FROM docker.mgm.sipwise.com/sipwise-bookworm:latest
# Important! Update this no-op ENV variable when this Dockerfile
# is updated with the current date. It will force refresh of all
# of the base images and things like `apt-get update` won't be using
# old cached versions when the Dockerfile is built.
ENV REFRESHED_AT 2023-06-26
RUN apt-get update && apt-get install --assume-yes lua-check
RUN echo './t/testrunner' >>/root/.bash_history
WORKDIR /code/
################################################################################
# Instructions for usage
# ----------------------
# When you want to build the base image from scratch
# jump to the next section if you don't want to build yourself!:
#
# % docker build --tag="prosody-bookworm" -f t/Dockerfile .
# % docker run --rm -i -t -v $(pwd):/code:rw prosody-bookworm:latest bash
#
# Use the existing docker image:
# % docker pull docker.mgm.sipwise.com/prosody-bookworm
# NOTE: run the following command from root folder of git repository:
# % docker run --rm -i -t -v $(pwd):/code:rw docker.mgm.sipwise.com/prosody-bookworm:latest bash
#
# Inside docker (the command is in history, just press UP button):
# ./t/testrunner
#
################################################################################

@ -1,15 +0,0 @@
#!/bin/bash
set -e
set -u
if [ -d /results ] ; then
# Running from Jenkins (RW)
RESULTS="/results"
else
# Running locally in Docker
RESULTS="./results"
mkdir -p "${RESULTS}"
fi
luacheck --formatter JUnit core plugins util > ${RESULTS}/prosody.luacheck

@ -1,44 +0,0 @@
#!/bin/bash
#
set -e
MOD_SRC=$1
usage() {
echo "$0 modules_src_dir"
printf "\tmodules_src_dir:\tpath to prosody-modules mercurial source\n"
}
refresh_sources() {
echo "refresh sources at $MOD_SRC"
( cd "$MOD_SRC" && hg pull && hg update )
}
copy_modules() {
while read -r dir ; do
if [ -d "$MOD_SRC/$dir" ] ; then
cp -vr "$MOD_SRC/$dir" plugins
else
echo "$MOD_SRC/$dir no longer there"
fi
done < prosody-modules.list
}
get_revision_id() {
(cd "$MOD_SRC/$dir" && hg id -i) > 'prosody-modules.revision'
}
if [ $# -ne 1 ] ; then
echo "wrong number of parameters" >&2
usage
exit 1
fi
if ! [ -d "$MOD_SRC" ] ; then
printf "modules_src_dir[%s] not found\n" "$MOD_SRC"
fi
refresh_sources
copy_modules
get_revision_id
exit 0

@ -1,160 +0,0 @@
#!/usr/bin/env lua5.2
local CFG_SOURCEDIR='/usr/lib/prosody';
local CFG_CONFIGDIR='/etc/prosody';
local mam_query =[[
select `id`, `username`,UuidFromBin(`key`),`stanza`,`epoch`,`with`
from sipwise_mam as mam
where UuidFromBin(`key`) not in (select `key` from prosodyarchive)
order by username, id asc;
]]
local mam_del =[[
delete from sipwise_mam where id = ?;
]]
local archive_insert =[[
insert into `prosodyarchive` (`host`,`user`,`store`,`key`,`when`,`with`,`type`,`value`)
values(?,?,'archive',?,?,?,'xml',?);
]]
local offline_query =[[
select `id`, `domain`, `username`, `stanza`
from sipwise_offline
order by username, id asc;
]]
local offline_del = [[
delete from sipwise_offline where id = ?;
]]
local offline_insert =[[
insert into `prosodyarchive` (`host`,`user`,`store`,`key`,`when`,`with`,`type`,`value`)
values(?,?,'offline',?,?,'','xml',?);
]]
local prosodyarchive_count =[[
select count(sort_id) from prosodyarchive;
]]
local function is_relative(path)
local path_sep = package.config:sub(1,1);
return ((path_sep == "/" and path:sub(1,1) ~= "/")
or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\")))
end
-- Tell Lua where to find our libraries
if CFG_SOURCEDIR then
local function filter_relative_paths(path)
if is_relative(path) then return ""; end
end
local function sanitise_paths(paths)
return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
end
package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
end
local configmanager = require "core.configmanager";
local ok, _, err = configmanager.load(CFG_CONFIGDIR.."/prosody.cfg.lua", "lua")
if not ok then
print("Error: can't load the config file."..err);
os.exit(1);
end
local sql_config = configmanager.get("*", "sql");
if not sql_config then
print("Error: sql not found in the config file.");
os.exit(1);
end
local deserialize = require "util.serialization".deserialize;
local st = require "util.stanza";
local sql = require "util.sql";
local jid_split = require "util.jid".split;
local uuid = require "util.uuid".generate;
local timestamp_parse = require "util.datetime".parse;
local engine = sql:create_engine(sql_config);
engine:connect();
-- from mod_storage_sql.lua of prosody 0.11.3
local function create_table()
local Table, Column, Index = sql.Table, sql.Column, sql.Index;
local ProsodyArchiveTable = Table {
name="prosodyarchive";
Column { name="sort_id", type="INTEGER", primary_key=true, auto_increment=true };
Column { name="host", type="TEXT", nullable=false };
Column { name="user", type="TEXT", nullable=false };
Column { name="store", type="TEXT", nullable=false };
Column { name="key", type="TEXT", nullable=false }; -- item id
Column { name="when", type="INTEGER", nullable=false }; -- timestamp
Column { name="with", type="TEXT", nullable=false }; -- related id
Column { name="type", type="TEXT", nullable=false };
Column { name="value", type="MEDIUMTEXT", nullable=false };
Index { name="prosodyarchive_index", unique = engine.params.driver ~= "MySQL", "host", "user", "store", "key" };
Index { name="prosodyarchive_with_when", "host", "user", "store", "with", "when" };
Index { name="prosodyarchive_when", "host", "user", "store", "when" };
};
engine:transaction(function()
ProsodyArchiveTable:create(engine);
end);
end
local function check_prosodyarchive()
for row in engine:select(prosodyarchive_count) do --luacheck: ignore
return row[1]
end
end
local function migrate_mam()
local i = 0
for row in engine:select(mam_query) do
local user, host, _ = jid_split(row[2]);
-- print("id:"..row[1].." username:"..row[2].." uuid:"..row[3]);
local stanza = st.deserialize(deserialize(row[4]));
-- print("stanza:"..tostring(stanza));
engine:insert(archive_insert, host, user, row[3], row[5], row[6], tostring(stanza));
engine:delete(mam_del, row[1]);
i = i + 1;
end
if i > 0 then
engine.conn:commit();
print("migrated "..i.." mam messages");
end
end
local function migrate_offline()
local i = 0
for row in engine:select(offline_query) do
-- print("id:"..row[1].." username:"..row[3].." domain:"..row[2]);
local stanza = st.deserialize(deserialize(row[4]));
local when = timestamp_parse(stanza.attr.stamp)
stanza.attr.stamp, stanza.attr.stamp_legacy = nil, nil;
engine:insert(offline_insert, row[2], row[3], uuid(), when, tostring(stanza));
engine:delete(offline_del, row[1]);
i = i + 1;
end
if i > 0 then
engine.conn:commit();
print("migrated "..i.." offline messages");
end
end
if arg[1] == '--create' then
if pcall(check_prosodyarchive) then
print("prosodyarchive already there");
else
create_table();
if check_prosodyarchive() then
print("prosodyarchive created");
end
end
else
migrate_mam();
migrate_offline();
end
Loading…
Cancel
Save