mirror of https://github.com/sipwise/prosody.git
We are removing the prosody support from NGCP, so there is no need anymore for this component. Change-Id: I80f0bf4be31930c0de69e20b96beefbbec451c35master
parent
eb84520aec
commit
f8ac2a875c
@ -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>
|
||||
```
|
||||
@ -1,7 +0,0 @@
|
||||
*.debhelper
|
||||
*.log
|
||||
*.substvars
|
||||
/.debhelper/
|
||||
/debhelper-build-stamp
|
||||
/files
|
||||
/ngcp-prosody-modules/
|
||||
@ -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 +0,0 @@
|
||||
debian/README_mod_*.*
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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…
Reference in new issue