Compare commits

...

178 Commits

Author SHA1 Message Date
Christophe Romain d576902bd4 Prepare mix for 17.09 2017-09-28 15:21:11 +02:00
Christophe Romain a3da27e917 Fix race introduced by ffdaff374 on ejabberd_mnesia init 2017-09-28 15:14:11 +02:00
Evgeniy Khramtsov 3f015c829c Make sure Riak gets compiled on OTP20 2017-09-28 12:25:06 +03:00
Evgeniy Khramtsov 2732c8f6fc Fix function clause introduced by c17ec50e3a 2017-09-28 12:24:24 +03:00
Evgeniy Khramtsov 3192687334 Don't forget to route presence-unavailable
When a user has several resources joined to a room using the same
nick attempts to leave the room from a single resource, route
presence-unavailable back to this (and only this) resource.

Fixes #2007
2017-09-28 11:58:36 +03:00
Christophe Romain 126653e01b Pubsub: implment '6.5.7 Requesting the Most Recent Items' 2017-09-27 20:54:50 +02:00
Christophe Romain ce7acafe37 Remove useless condition 2017-09-27 20:40:07 +02:00
Christophe Romain 216a0c97b9 PubSub: add RSM support for mnesia backend 2017-09-27 20:39:54 +02:00
Christophe Romain 3c8308bb8d PubSub: enforce controls on publish and delete 2017-09-27 17:37:38 +02:00
Holger Weiss 1c6aa5e84e mysql.sql: Use multi-column index on username/ID 2017-09-27 15:30:23 +02:00
Christophe Romain 609a1d07cf harden ejabberdctl (#1977) 2017-09-27 15:04:57 +02:00
Christophe Romain 8c026582ab Fix xref error 2017-09-27 12:34:40 +02:00
Christophe Romain 81df1ae3af Unbind unused variable 2017-09-27 12:13:45 +02:00
Christophe Romain 22435ca562 Remove calls to deprecated functions 2017-09-27 11:43:59 +02:00
Paweł Chmielowski f464189819 Update dependences 2017-09-27 11:39:16 +02:00
Christophe Romain 89e504c55f PubSub: fix get_items behaviour with mnesia 2017-09-27 11:18:12 +02:00
Christophe Romain c1d3d1318e Merge branch 'master' of github.com:processone/ejabberd 2017-09-27 11:12:09 +02:00
Christophe Romain d120e0ad91 PubSub: add correct order when requesting all items 2017-09-27 11:12:01 +02:00
Evgeniy Khramtsov c17ec50e3a Add support for XEP-0368: SRV records for XMPP over TLS
Currently this is only supported for outgoing s2s connections.
For such connections ejabberd is now able to resolve SRV records
of type "_xmpps-server._tcp". Also, SNI and ALPN fields are set
during TLS handshake. No additional configuration is required.
2017-09-27 12:03:05 +03:00
Badlop 368ba3fc55 When running "make translations", use the new tools/prepare-tr.sh 2017-09-27 10:57:11 +02:00
Badlop cd098c5adc Remove remaining files of old contrib/ dir 2017-09-27 10:57:07 +02:00
Badlop 221e58fff0 Simplify prepare-tr.sh to work with new extract-tr.sh 2017-09-27 10:57:03 +02:00
Badlop 9b043ae276 Move prepare-translations.sh from contrib to tools/prepare-tr.sh 2017-09-27 10:56:58 +02:00
Evgeniy Khramtsov abc09054e5 Remove forgotten export_all 2017-09-27 11:56:22 +03:00
Christophe Romain 07a193d4dc PubSub: fix RSM support (#1994)(#2Â014) 2017-09-27 10:51:37 +02:00
Christophe Romain cf09ed2df2 PubSub: fix node_options, default options only apply on first plugin 2017-09-27 09:29:04 +02:00
Holger Weiss 0cc1ae0a6a mod_http_upload: Don't ignore 'custom_headers'
Don't ignore the 'custom_headers' option if the domain part of the
'put_url' doesn't match the XMPP domain.

Closes #1482.
2017-09-26 21:40:56 +02:00
Evgeniy Khramtsov e1efd29156 Improve presence-error processing
When a presence-error is received from a participant in a MUC, kick
only this particular participant's full JID, leaving other resources
untouched. This will prevent from erroneous kicking all user's resources
in the presence of "multi-session nicks".
2017-09-26 19:01:54 +03:00
Paweł Chmielowski 05feab35c4 Call earlier deps configure scripts durring compilation 2017-09-26 17:32:37 +02:00
Badlop 2198fbce97 Fix command set_last that always returned code 1, error (#2010) 2017-09-25 18:43:24 +02:00
Christophe Romain b8ab80d1f3 Sync containers from rroemhild and add instructions in README (#1655) 2017-09-25 17:48:57 +02:00
Evgeniy Khramtsov 30e5c9bd3e Add mod_avatar to the example configuration 2017-09-25 16:23:02 +03:00
Evgeniy Khramtsov 251756de00 Catch all p1_fsm errors
Fixes #2012
2017-09-25 13:01:00 +03:00
Evgeniy Khramtsov 3e987d3bae Use eimp instead of ImageMagick calls for thumbnails creation 2017-09-25 12:41:12 +03:00
Evgeniy Khramtsov 138cc25355 Pre-install libgd and libwebp 2017-09-25 11:19:49 +03:00
Evgeniy Khramtsov a15f186253 Remove OTP20 from Travis tests for now 2017-09-25 11:17:46 +03:00
Holger Weiss 59ec3d36f4 mod_mam: Simplify check for anon MUC JID filtering
Refuse filtering anon MUC MAM queries by JID even if it's the client's
own JID.  Clients probably won't perform such queries in practice, so
the additional complexity is unnecessary.
2017-09-24 15:04:09 +02:00
Evgeniy Khramtsov d7250111ce Reuse some translation strings 2017-09-24 14:32:37 +03:00
Evgeniy Khramtsov adfb924808 Replace translate:mark/1 with ?T() macro 2017-09-24 12:42:35 +03:00
Holger Weiss 48f2adde98 mod_mam: Refuse filtering anon MUC queries by JID
Return an empty result set if a non-moderator attempts to filter by JID
while querying the archive of an anonymous MUC room.
2017-09-24 02:05:50 +02:00
Evgeniy Khramtsov c378ea403e Add script to extract translation strings 2017-09-24 00:08:01 +03:00
Holger Weiss 692ccd2e20 mod_push_mnesia: Fix typo in error message 2017-09-22 23:36:51 +02:00
Badlop 5882c9b456 Add some recommended TLSOPTS as comments in the default config (#2004) 2017-09-21 16:28:17 +02:00
Evgeniy Khramtsov db41643bea Also replace vcard-x-update in direct presences 2017-09-18 14:17:34 +03:00
Christophe Romain cb076924cc Merge pull request #1996 from nosnilmot/ejabberdctl-quote-peer
Quote $PEER in ping command to avoid hostnames containing "-" being interpreted as arithmetic
2017-09-18 13:09:16 +02:00
Christophe Romain cce4056040 Fix iexdebug and iexlive commands (#1981) 2017-09-18 13:01:10 +02:00
Christophe Romain 7ad525b542 PubSub: broadcast updated configuration (#1945) 2017-09-18 12:48:20 +02:00
Evgeniy Khramtsov 5bf64381cb Add 'width' and 'height' to avatar info 2017-09-18 09:33:57 +03:00
Evgeniy Khramtsov f435d0a103 Add GIF support 2017-09-17 17:34:31 +03:00
Evgeniy Khramtsov e4d21c1941 Introduce mod_avatar
The purpose of the module is to cope with legacy and modern
XMPP clients posting avatars. It automatically converts vCard based
avatars (XEP-0153) to PEP based avatars (XEP-0084) and vice versa.
Also, the module supports convertation between avatar image formats on
the fly: this is controlled by `convert` option. For example, to
convert all avatars into PNG format, configure the module as:

mod_avatar:
  convert:
    default: png

In order to convert only `webp` format to `jpeg`, set the following:

mod_avatar:
  convert:
    webp: jpeg

Note: the module depends on mod_vcard, mod_vcard_xupdate and mod_pubsub.
Also, ejabberd should be built with --enable-graphics option.
2017-09-17 10:26:48 +03:00
Christophe Romain 5414cbe821 Add riakc dependency version 2017-09-13 09:49:43 +02:00
Holger Weiss e6b1521b29 mod_push_keepalive: Remove unused 'db_type' option 2017-09-11 23:48:25 +02:00
Stu Tomlinson 2a7d9d93c8 Quote $PEER in ping command to avoid hostnames containing "-" being interpreted as arithmetic 2017-09-11 15:00:00 +01:00
Evgeniy Khramtsov dfd2045523 Introduce option 'allow_transports'
This is a boolean option. If set to `true` and some server's JID
is in user's roster, then messages from any user of this server are
accepted even if no subscription present.

The option is enabled by default.
2017-09-08 23:10:01 +03:00
Badlop 9e35af54e0 The redis_reconnect_timeout was no longer used (#1983) 2017-09-07 20:25:20 +02:00
Badlop d87151aee6 Simple optimization in get_room_occupants_number command (#1964) 2017-09-07 18:32:26 +02:00
Evgeniy Khramtsov 4ecd8a0780 Really delete cache on set_data()
Fixes #1991
2017-09-07 19:27:48 +03:00
Evgeniy Khramtsov 803c31f760 Always accept messages from local (sub)domains 2017-09-07 13:53:42 +03:00
Evgeniy Khramtsov 978c92f5e1 Fix a typo and improve logging message 2017-09-07 13:41:51 +03:00
Badlop 32397aa0c3 Replace example config of drop_chat_states with queue_* in mod_client_state (#1985) 2017-09-06 13:48:33 +02:00
Evgeniy Khramtsov 1a58a201f8 Block messages from strangers before mod_mam/mod_offline processing
Fixes #1713
2017-09-02 22:54:46 +03:00
Evgeniy Khramtsov 2acbf4625b Deprecate s2s_use_starttls: required_trusted
The functionality can be now obtained by using
`s2s_use_starttls: required` with `mod_s2s_dialback`
being unloaded.
2017-09-02 11:45:06 +03:00
Evgeniy Khramtsov 7566d254e4 Introduce 'redirect_url' option for mod_register
The option enables registration redirection as described in
https://xmpp.org/extensions/xep-0077.html#redirect

Fixes #1976
2017-09-01 12:14:01 +03:00
Badlop 68dee8cbb3 Fix deletion of multiple offline messages using WebAdmin (#1962) 2017-08-25 12:44:53 +02:00
Evgeniy Khramtsov f7e8d287d5 Fix a typo 2017-08-25 12:47:33 +03:00
Evgeniy Khramtsov 30bca124f4 Add forgotten caching options to the validator (thanks to Jan Pinkas) 2017-08-24 21:40:54 +03:00
Paweł Chmielowski d7891a5562 Fix 'make install' to work with new output from rebar list-deps 2017-08-24 11:43:47 +02:00
Badlop 1d396b4716 Rewrite muc_register_nick and muc_unregister_nick to be DB independent (#1954)
Notice: The arguments expected have changed, instead of
  muc_register_nick Tim tim@example.org muc.example.org
  muc_unregister_nick Tim
it expects now:
  muc_register_nick Tim tim@example.org example.org
  muc_unregister_nick tim@example.org example.org
2017-08-24 10:40:54 +02:00
Evgeniy Khramtsov 7f3fceb432 Recompile rebar script with OTP17.5 2017-08-23 11:45:10 +03:00
Evgeniy Khramtsov 81581f7794 Use forked repo of Riak Erlang client 2017-08-23 10:35:39 +03:00
Evgeniy Khramtsov 47175adc74 Explicitly match against <domain/resource>
Fixes #1958
2017-08-23 09:00:13 +03:00
Evgeniy Khramtsov 350827f8f4 Ignore some options from rebar.config
Namely, the following options are ignored in dependency packets:
require_erts_vsn, require_otp_vsn and require_min_otp_vsn
2017-08-22 15:26:35 +03:00
Badlop 05c2995c7a When creating room, set option to ensure config is set in muc_room table (#1954) 2017-08-21 15:58:10 +02:00
Badlop 2fa6e2fd90 Fix crash when destroying room without providing reason (#1954) 2017-08-21 15:57:48 +02:00
Holger Weiss ba9a79c89c Apply cosmetic changes to previous commit 2017-08-18 16:50:08 +02:00
Holger Weiss 13ad754ecc Suppress push notifications for online clients
When a client enabled push notifications during the current session,
notifications should be suppressed as long as the client is online.
Suppressing the notification didn't work for the case where the
notification was triggered by MAM, but this is now fixed.
2017-08-18 16:44:32 +02:00
Badlop 0b02d42836 Fix mod_multicast start and reading of configured limits (#1949) 2017-08-18 14:09:49 +02:00
Evgeniy Khramtsov ee0a8d2966 Preserve correct order of deserialized XML elements
Fixes #1939
2017-08-18 10:20:27 +03:00
Evgeniy Khramtsov 793ca45dda Add OTP 20.0 to Travis testsing platforms 2017-08-17 22:14:54 +03:00
Evgeniy Khramtsov 6e20e9bcf9 Get rid of deprecated crypto functions 2017-08-17 19:32:15 +03:00
Holger Weiss b8d2a72333 mod_stream_mgmt: Delete 'c2s_init' hook
Delete the 'c2s_init' hook when the last 'mod_stream_mgmt' instance is
stopped.
2017-08-17 18:25:06 +02:00
Holger Weiss 0760c7273c mod_stream_mgmt: Remove outdated TODO comment
The CSI queue is now flushed from mod_client_state.
2017-08-17 18:19:04 +02:00
Evgeniy Khramtsov 9bd099013f Don't attempt to access(2) a certificate file
Fixes #1375
2017-08-17 14:33:41 +03:00
Christophe Romain 68fb12153e Revert "Temporary remove recent last_item refactor"
This reverts commit 1820b4f63b.
2017-08-14 15:25:45 +02:00
Holger Weiss e19d1e9571 Merge remote-tracking branch 'processone/pr/1938'
* processone/pr/1938:
  Let 'domain_certfile' take higher precedence
2017-08-14 14:43:03 +02:00
Christophe Romain 1820b4f63b Temporary remove recent last_item refactor 2017-08-14 09:43:02 +02:00
Holger Weiss 64150cc7c5 Let 'domain_certfile' take higher precedence
If a 'domain_certfile' is specified, use that instead of the
's2s_certfile' (or 'c2s_certfile').
2017-08-13 20:31:03 +02:00
Evgeniy Khramtsov 63aabed320 Apply URL decoding wherever possible
Fixes #1936
2017-08-13 19:18:19 +03:00
Christophe Romain fd7bf7fed3 Fix typo from 9c5427e0c 2017-08-11 17:54:53 +02:00
Christophe Romain 7a90cda8ff Process on_user_offline only from valid sessions 2017-08-11 12:05:14 +02:00
Evgeniy Khramtsov 35eeaa5869 Fix regression introduced by b82b93f8f0
Fixes #1928
2017-08-11 11:43:16 +03:00
Christophe Romain 024713a441 Remove temporary debug 2017-08-11 10:53:19 +02:00
Christophe Romain 32fbfe1981 Use correct c2s process sending PEP with multi devices 2017-08-11 10:32:36 +02:00
Christophe Romain 9c5427e0c2 PubSub: refactor send_last_items remove send_loop 2017-08-11 10:20:33 +02:00
Christophe Romain 8f5a1c4a2a Merge pull request #1926 from weiss/import-metronome-pep
prosody2ejabberd: Support PEP import
2017-08-11 10:19:17 +02:00
Holger Weiss 7d3609d954 prosody2ejabberd: Support PEP import 2017-08-10 19:49:20 +02:00
Holger Weiss fc7ba53c37 prosody2ejabberd: Remove superfluous 'catch' 2017-08-10 18:54:00 +02:00
Evgeniy Khramtsov a96d72330d Rename remove_room hook back 2017-08-10 14:49:05 +03:00
Christophe Romain 7d626b4f5c Add support of section 4.9.3.16 on rfc6120 2017-08-10 12:17:31 +02:00
Christophe Romain e903348dd3 Fix pubsub send_loop after 3fec7824 2017-08-09 11:34:36 +02:00
badlop bee251d928 Merge pull request #1912 from mathiasertl/master
fix FIREWALL_WINDOW option
2017-08-09 09:48:18 +02:00
Mathias Ertl c658907331 fix typo 2017-08-08 19:09:55 +02:00
Evgeniy Khramtsov 3fec782494 Introduce 'hosts' option
The option can be used as a replacement of 'host' option
when several (sub)domains are needed to be registered for the module.
Note that you cannot combine both 'host' and 'hosts' in the config
because 'host' option is of a higher priority. Example:

mod_pubsub:
   ...
   hosts:
     - "pubsub1.@HOST@"
     - "pubsub2.@HOST@"

Fixes #1883
2017-08-08 17:46:26 +03:00
Christophe Romain 52525eb76d Fix tests for 8679cfd2f 2017-08-07 15:38:17 +02:00
Christophe Romain 8679cfd2f3 Rename stop_all_connections to stop_s2s_connections for consistency 2017-08-07 15:06:07 +02:00
Paweł Chmielowski 25af3fb029 Compile mod_push early as it defines behaviour 2017-08-07 09:32:58 +02:00
Evgeniy Khramtsov 2bceebc39d Get rid of export_all 2017-08-05 21:01:29 +03:00
Evgeniy Khramtsov 92532a0d66 Replace gen_fsm with p1_fsm to avoid warnings in OTP20+ 2017-08-05 20:58:21 +03:00
Holger Weiss b673539a2a Merge remote-tracking branch 'processone/pr/1914'
* processone/pr/1914:
  ejabberd_c2s: Fix priority of 'certfile' option
2017-08-05 19:27:43 +02:00
Holger Weiss e1aaa1c99d ejabberd_c2s: Fix priority of 'certfile' option
Use the 'certfile' listener option rather than a 'domain_certfile' for
ejabberd_c2s listeners that have "tls: true" configured.  A
'domain_certfile' should only be preferred for STARTTLS connections.

Closes #1911.
2017-08-05 18:59:32 +02:00
Mathias Ertl aa9eb001d0 fix FIREWALL_WINDOW option 2017-08-04 18:49:33 +02:00
Badlop 101e808124 Fix warning in previous commit 2017-08-04 13:09:17 +02:00
Christophe Romain 766b7c65a6 Merge pull request #1881 from weiss/push
Support XEP-0357: Push Notifications
2017-08-04 12:58:06 +02:00
Badlop 0516a3dc0e Remove unused 'managers' option, related to the deferred XEP-0321 (#1443) 2017-08-04 12:29:15 +02:00
Christophe Romain 3d185c0fb8 Fix missing validation from 633b68db1 (#1900) 2017-08-04 11:53:32 +02:00
Christophe Romain 7815164216 Add minor details (#1353) 2017-08-04 10:34:13 +02:00
Christophe Romain 65f4094804 Prepare packaging for 17.08 2017-08-03 16:55:56 +02:00
Christophe Romain 06450f4a82 Keep disco#info on PEP compatible with XEP-0060 (#1717) 2017-08-03 15:48:23 +02:00
Christophe Romain ce0beb550c Move pubsub sql export to pubsub_db_sql module 2017-08-03 15:34:01 +02:00
Christophe Romain 96c0483533 Fix Xref from 5dcc97c 2017-08-03 14:54:06 +02:00
Paweł Chmielowski 1274bcdba9 Change policy of user_resources command
This fixes issue #1908
2017-08-03 13:49:06 +02:00
Badlop 5dcc97c85a Preliminary export PubSub data from Mnesia tables to SQL file (#1571) 2017-08-03 10:53:01 +02:00
Christophe Romain 6deceeec2e Merge branch 'master' of github.com:processone/ejabberd 2017-08-02 14:36:43 +02:00
Christophe Romain 9edcbadd60 Fix clustering table reg_users_counter (#1889) 2017-08-02 14:36:32 +02:00
Christophe Romain f65492e27f Merge pull request #1837 from marcphilipp/feature/set_room_affiliation_test
Add integration test for set_room_affiliation
2017-08-02 14:34:30 +02:00
Christophe Romain 1df61a82c8 Merge branch 'master' of github.com:processone/ejabberd 2017-08-02 12:25:50 +02:00
Christophe Romain 73509686f1 Fix getting cached last item (#1814) 2017-08-02 12:24:34 +02:00
Evgeny Khramtsov 93e521d65e Merge pull request #1903 from himawri/fix_pubsub_spec
Fix spec for mod_pubsub:subscribe_node
2017-08-02 13:52:21 +04:00
Jing Sun 50511fcff7 Fix spec for mod_pubsub:subscribe_node 2017-08-02 10:31:42 +02:00
Christophe Romain 5e26190b98 Fix PubSub send last published items (#1572) 2017-08-01 17:00:52 +02:00
Christophe Romain f22bd6eb46 Fix PEP node removal (#1839) 2017-08-01 15:40:34 +02:00
Christophe Romain 0ba6c78ed0 Fix disco#items on PEP service 2017-08-01 15:15:01 +02:00
Christophe Romain 636d68e0a9 Fix PEP node identity (#1717) 2017-08-01 14:41:16 +02:00
Christophe Romain 7e6d1c24c2 Update spec from custom and allow modules dependencies (#1740) 2017-08-01 13:33:14 +02:00
Badlop 67918b17d3 Fix extauth.py so support : in passwords (thanks to jmberg)(#1676) 2017-07-31 22:51:31 +02:00
Paweł Chmielowski 51fa438340 Request basic auth dialog from browser 2017-07-28 16:08:05 +02:00
Christophe Romain 35a11526f9 Revert "Fix get_module_opt call in mod_block_strangers"
This reverts commit e5f64bc24a.
2017-07-27 18:23:10 +02:00
Paweł Chmielowski 0cfec92c14 Generate log messages when websocket is closed due timeouts 2017-07-27 17:53:16 +02:00
Alexey Shchepin e5f64bc24a Fix get_module_opt call in mod_block_strangers 2017-07-27 17:48:41 +02:00
Paweł Chmielowski 5c48ba4609 Set high water mark in lager for all backends 2017-07-27 17:14:03 +02:00
Jérôme Sautret 3ca62a797a Fix nick bug with MUC on riak 2017-07-27 17:06:34 +02:00
Christophe Romain b66dab1313 Add muc related hooks 2017-07-27 17:02:06 +02:00
Christophe Romain 58110e4bc1 Update OTP version check by configure 2017-07-27 15:31:59 +02:00
Marco Adkins ea96615460 Ability to filter passwords from the log in mod_http_api (#1888)
* Ability to filter passwords from the log when creating users through the mod_http_api
2017-07-27 15:30:56 +02:00
Paweł Chmielowski b8c26671c4 Update oauth2 dependency 2017-07-25 17:59:32 +02:00
Christophe Romain 2e88d001d6 Fix errors from 1a0db3d 2017-07-21 11:42:03 +02:00
Badlop 1a0db3de3a Describe commands arguments and results in ejabberd_sm, ext_mod, mod_mam 2017-07-21 11:26:53 +02:00
Badlop 250876ea1a Fix indentation of commands lines 2017-07-21 11:26:47 +02:00
Holger Weiss 66510c1d78 Add mod_push_keepalive
This module tries to keep pending stream management sessions of push
clients alive (as long as the disconnected clients are reachable via
push notifications).
2017-07-21 01:07:36 +02:00
Holger Weiss d6f1d3df5b Support XEP-0357: Push Notifications
Closes #1379.
2017-07-20 20:22:50 +02:00
Christophe Romain 72dbb6e7c1 Fix errors when running ejabberdctl as root 2017-07-20 14:40:50 +02:00
Paweł Chmielowski b1082a96c9 Add ability to update changelog in update-deps-releases 2017-07-20 11:43:25 +02:00
Paweł Chmielowski ed17586cf0 Update cache_tab and xmpp dep 2017-07-20 11:43:06 +02:00
Christophe Romain 26b8dd75f7 Merge pull request #1869 from wahjava/master
Fix ERLANG_OPTS when setting INET_DIST_INTERFACE
2017-07-19 11:33:38 +02:00
Paweł Chmielowski c3473c2077 Update fast_tls 2017-07-19 10:58:10 +02:00
Paweł Chmielowski e216654c52 Don't add indentation after single item result in docs 2017-07-18 17:48:53 +02:00
Christophe Romain cc3391cc1c Use string:join instead of lists:join 2017-07-18 15:43:46 +02:00
Christophe Romain b8d56a7c11 Improve formatting of documented API parameters 2017-07-18 15:01:01 +02:00
Evgeny Khramtsov 1bb9e83501 Merge pull request #1871 from rstgroup/master
Fix old route record in mnesia's route table haven't been remove when restarting in some cases (#1184)
2017-07-17 17:56:39 +04:00
Paweł Dorofiejczyk de1a66dfbe Fix old route record in mnesia's route table haven't been remove when restarting in some cases (#1184) 2017-07-17 15:14:30 +02:00
Ashish SHUKLA 95613a11ab Fix ERLANG_OPTS when setting INET_DIST_INTERFACE 2017-07-15 16:42:47 +05:30
Paweł Chmielowski 2cd193f97c Expand catch block used to report errors in doc generator 2017-07-14 17:18:07 +02:00
Paweł Chmielowski 33a9d6a3c3 Fix args_examples from last commit 2017-07-14 17:17:26 +02:00
Badlop fdb863ce70 Describe even more command arguments and results in mod_admin_extra 2017-07-14 16:43:30 +02:00
Paweł Chmielowski 43fc29873e Add refresh repos option in update-deps-releases 2017-07-14 15:08:13 +02:00
Paweł Chmielowski e216a54ead Update fast_tls 2017-07-14 15:08:13 +02:00
Badlop d4cdc3a246 Fix ERLANG_OPTS end lines when setting FIREWALL_WINDOW (#1856) 2017-07-12 14:43:28 +02:00
Holger Weiss f6bdc6fdb2 mod_privacy: Don't crash while copying c2s state
Don't assume 'privacy_active_list' is set when c2s_copy_session/2 is
called.
2017-07-07 14:28:22 +02:00
Holger Weiss 8f25baada6 mod_privacy: Apply cosmetic change to type spec 2017-07-07 14:25:55 +02:00
Badlop aaef1a14b4 Fix set_presence command to work in recent ejabberd 2017-07-07 10:55:08 +02:00
Evgeniy Khramtsov 22e8f5fd51 Add copyright and fix description for some sources 2017-07-06 21:27:04 +03:00
Paweł Chmielowski 88ab787ba6 Start gen_mod from elixir tests 2017-07-06 17:57:46 +02:00
Paweł Chmielowski eb9faffadd Improve elixir tests 2017-07-06 17:19:22 +02:00
Paweł Chmielowski 3b0eee785f Handle new possible result from ejabberd_config.add_option 2017-07-06 17:19:22 +02:00
Paweł Chmielowski 5ef542a638 Remove tests for old commands interface 2017-07-06 17:19:22 +02:00
Evgeniy Khramtsov ffdaff3740 Make ejabberd_cluster modular
For setting the cluster backend new global option 'cluster_backend' is
introduced. The default and only available value at the moment is 'mnesia'
2017-07-06 15:47:35 +03:00
Paweł Chmielowski 56d273477e Remove old command calling interface 2017-07-06 14:24:25 +02:00
Marc Philipp c69720a1ab Add integration test for set_room_affiliation 2017-07-06 13:33:13 +02:00
127 changed files with 5359 additions and 3500 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ before_install:
- pip install --user coveralls-merge
install:
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev
- sudo apt-get -qq install libexpat1-dev libyaml-dev libpam0g-dev libsqlite3-dev libgd-dev libwebp-dev
before_script:
# Ulimit: See Travis-CI issue report: https://github.com/travis-ci/travis-ci/issues/3328
+13 -6
View File
@@ -1,7 +1,7 @@
FROM debian:jessie-slim
MAINTAINER Rafael Römhild <rafael@roemhild.de>
ENV EJABBERD_BRANCH=17.04 \
ENV EJABBERD_BRANCH=17.08 \
EJABBERD_USER=ejabberd \
EJABBERD_HTTPS=true \
EJABBERD_STARTTLS=true \
@@ -9,7 +9,7 @@ ENV EJABBERD_BRANCH=17.04 \
EJABBERD_HOME=/opt/ejabberd \
EJABBERD_DEBUG_MODE=false \
HOME=$EJABBERD_HOME \
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin \
PATH=$EJABBERD_HOME/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/sbin \
DEBIAN_FRONTEND=noninteractive \
XMPP_DOMAIN=localhost \
# Set default locale for the environment
@@ -38,6 +38,7 @@ RUN set -x \
erlang-src erlang-dev \
' \
&& requiredAptPackages=' \
wget \
locales \
ldnsutils \
python2.7 \
@@ -47,7 +48,7 @@ RUN set -x \
erlang-base erlang-snmp erlang-ssl erlang-ssh erlang-webtool \
erlang-tools erlang-xmerl erlang-corba erlang-diameter erlang-eldap \
erlang-eunit erlang-ic erlang-odbc erlang-os-mon \
erlang-parsetools erlang-percept erlang-typer erlang-inets \
erlang-parsetools erlang-percept erlang-typer \
python-mysqldb \
imagemagick \
' \
@@ -68,7 +69,6 @@ RUN set -x \
&& chmod +x ./autogen.sh \
&& ./autogen.sh \
&& ./configure --enable-user=$EJABBERD_USER \
--prefix=/ \
--enable-all \
--disable-tools \
--disable-pam \
@@ -82,12 +82,19 @@ RUN set -x \
&& mkdir $EJABBERD_HOME/module_source \
&& cd $EJABBERD_HOME \
&& rm -rf /tmp/ejabberd \
&& rm -rf /etc/ejabberd \
&& ln -sf $EJABBERD_HOME/conf /etc/ejabberd \
&& rm -rf /usr/local/etc/ejabberd \
&& ln -sf $EJABBERD_HOME/conf /usr/local/etc/ejabberd \
&& chown -R $EJABBERD_USER: $EJABBERD_HOME \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get purge -y --auto-remove $buildDeps
RUN wget -P /usr/local/share/ca-certificates/cacert.org http://www.cacert.org/certs/root.crt http://www.cacert.org/certs/class3.crt; \
update-ca-certificates
# Create logging directories
RUN mkdir -p /var/log/ejabberd
RUN touch /var/log/ejabberd/crash.log /var/log/ejabberd/error.log /var/log/ejabberd/erlang.log
# Wrapper for setting config on disk from environment
# allows setting things like XMPP domain at runtime
ADD ./docker/run.sh /sbin/run
+2 -2
View File
@@ -102,7 +102,7 @@ xref: all
translations:
contrib/extract_translations/prepare-translation.sh -updateall
tools/prepare-tr.sh
edoc:
$(ERL) -noinput +B -eval \
@@ -123,7 +123,7 @@ FILES_WILDCARD=$(call FILTER_DIRS,$(foreach w,$(1),$(wildcard $(w))))
ifeq ($(MAKECMDGOALS),copy-files-sub)
DEPS:=$(sort $(shell $(REBAR) list-deps|$(SED) -e '/^=/d;s/ .*//'))
DEPS:=$(sort $(shell $(REBAR) -q list-deps|$(SED) -e '/[a-z0-9_-]+\s/d;s/ .*//'))
DEPS_FILES=$(call FILES_WILDCARD,$(foreach DEP,$(DEPS),deps/$(DEP)/ebin/*.beam deps/$(DEP)/ebin/*.app deps/$(DEP)/priv/* deps/$(DEP)/priv/lib/* deps/$(DEP)/priv/bin/* deps/$(DEP)/include/*.hrl deps/$(DEP)/COPY* deps/$(DEP)/LICENSE* deps/$(DEP)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
DEPS_FILES_FILTERED=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES))
+4 -1
View File
@@ -116,11 +116,14 @@ To compile ejabberd you need:
needed on systems with GNU Libc.
- ImageMagick's Convert program. Optional. For CAPTCHA challenges.
If your system splits packages in libraries and development headers, you must
install the development packages also.
### 1. Compile and install on *nix systems
To compile ejabberd, execute the following commands. The first one is only
necessary if your source tree didn't come with a `configure` script.
necessary if your source tree didn't come with a `configure` script (In this
case you need autoconf installed).
./autogen.sh
./configure
+1 -1
View File
@@ -84,7 +84,7 @@ defmodule Ejabberd.ConfigFile do
module :mod_client_state do
@opts [
drop_chat_states: true,
queue_chat_states: true,
queue_presence: false]
end
+1 -1
View File
@@ -560,7 +560,7 @@ modules:
mod_caps: {}
mod_carboncopy: {}
mod_client_state:
drop_chat_states: true
queue_chat_states: true
queue_presence: false
mod_configure: {} # requires mod_adhoc
mod_disco: {}
+11 -2
View File
@@ -3,8 +3,8 @@
AC_PREREQ(2.53)
AC_INIT(ejabberd, m4_esyscmd([echo `git describe --tags 2>/dev/null || echo 0.0` | sed 's/-g.*//;s/-/./' | tr -d '\012']), [ejabberd@process-one.net], [ejabberd])
REQUIRE_ERLANG_MIN="6.1 (Erlang/OTP 17.1)"
REQUIRE_ERLANG_MAX="9.0.0 (No Max)"
REQUIRE_ERLANG_MIN="6.4 (Erlang/OTP 17.5)"
REQUIRE_ERLANG_MAX="100.0.0 (No Max)"
AC_CONFIG_MACRO_DIR([m4])
@@ -236,6 +236,14 @@ AC_ARG_ENABLE(sip,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-sip) ;;
esac],[if test "x$sip" = "x"; then sip=false; fi])
AC_ARG_ENABLE(graphics,
[AC_HELP_STRING([--enable-graphics], [enable support for graphic images manipulation (default: yes)])],
[case "${enableval}" in
yes) graphics=true ;;
no) graphics=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-graphics) ;;
esac],[if test "x$graphics" = "x"; then graphics=true; fi])
AC_CONFIG_FILES([Makefile
vars.config
src/ejabberd.app.src])
@@ -280,6 +288,7 @@ AC_SUBST(iconv)
AC_SUBST(stun)
AC_SUBST(sip)
AC_SUBST(debug)
AC_SUBST(graphics)
AC_SUBST(tools)
AC_SUBST(latest_deps)
AC_SUBST(system_deps)
-21
View File
@@ -1,21 +0,0 @@
extract_translations - auxiliary tool that extracts lines to be translated
from ejabberd source tree.
Building:
erlc extract_translations.erl
Invoking 1:
erl -noinput -s extract_translations -extra dirname message_file
where dirname is the directory "src" in ejabberd's source tree root,
message_file is a file with translated messages (src/msgs/*.msg).
Result is a list of messages from source files which aren't contained in
message file.
Invoking 2:
erl -noinput -s extract_translations -extra -unused dirname message_file
Result is a list of messages from message file which aren't in source
files anymore.
@@ -1,307 +0,0 @@
%%%----------------------------------------------------------------------
%%% File : extract_translations.erl
%%% Author : Sergei Golovan <sgolovan@nes.ru>
%%% Purpose : Auxiliary tool for interface/messages translators
%%% Created : 23 Apr 2005 by Sergei Golovan <sgolovan@nes.ru>
%%% Id : $Id$
%%%----------------------------------------------------------------------
-module(extract_translations).
-author('sgolovan@nes.ru').
-export([start/0]).
-define(STATUS_SUCCESS, 0).
-define(STATUS_ERROR, 1).
-define(STATUS_USAGE, 2).
-include_lib("kernel/include/file.hrl").
start() ->
ets:new(translations, [named_table, public]),
ets:new(translations_obsolete, [named_table, public]),
ets:new(files, [named_table, public]),
ets:new(vars, [named_table, public]),
case init:get_plain_arguments() of
["-srcmsg2po", Dir, File] ->
print_po_header(File),
Status = process(Dir, File, srcmsg2po),
halt(Status);
["-unused", Dir, File] ->
Status = process(Dir, File, unused),
halt(Status);
[Dir, File] ->
Status = process(Dir, File, used),
halt(Status);
_ ->
print_usage(),
halt(?STATUS_USAGE)
end.
process(Dir, File, Used) ->
case load_file(File) of
{error, Reason} ->
io:format("~s: ~s~n", [File, file:format_error(Reason)]),
?STATUS_ERROR;
_ ->
FileList = find_src_files(Dir),
lists:foreach(
fun(F) ->
parse_file(Dir, F, Used)
end, FileList),
case Used of
unused ->
ets:foldl(fun({Key, _}, _) ->
io:format("~p~n", [Key])
end, ok, translations);
srcmsg2po ->
ets:foldl(fun({Key, Trans}, _) ->
print_translation_obsolete(Key, Trans)
end, ok, translations_obsolete);
_ ->
ok
end,
?STATUS_SUCCESS
end.
parse_file(Dir, File, Used) ->
ets:delete_all_objects(vars),
case epp:parse_file(File, [Dir, filename:dirname(File) | code:get_path()], []) of
{ok, Forms} ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
end, Forms);
_ ->
ok
end.
parse_form(Dir, File, Form, Used) ->
case Form of
%%{undefined, Something} ->
%% io:format("Undefined: ~p~n", [Something]);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_, {string, Line, Str}]
} ->
process_string(Dir, File, Line, Str, Used);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_,
{bin,_,
[{bin_element,_,
{string,Line,Str},
default,default}]}]
} ->
process_string(Dir, File, Line, Str, Used);
{call,
_,
{remote, _, {atom, _, translate}, {atom, _, translate}},
[_, {var, _, Name}]
} ->
case ets:lookup(vars, Name) of
[{_Name, Value, Line}] ->
process_string(Dir, File, Line, Value, Used);
_ ->
ok
end;
{match,
_,
{var, _, Name},
{string, Line, Value}
} ->
ets:insert(vars, {Name, Value, Line});
{match,
_,
{var, _, Name},
{bin,Line,[{bin_element,_,{string,_,Value},_,_}]}
} ->
ets:insert(vars, {Name, Value, Line});
L when is_list(L) ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
end, L);
T when is_tuple(T) ->
lists:foreach(
fun(F) ->
parse_form(Dir, File, F, Used)
end, tuple_to_list(T));
_ ->
ok
end.
process_string(_Dir, _File, _Line, "", _Used) ->
ok;
process_string(_Dir, File, Line, Str, Used) ->
case {ets:lookup(translations, Str), Used} of
{[{_Key, _Trans}], unused} ->
ets:delete(translations, Str);
{[{_Key, _Trans}], used} ->
ok;
{[{_Key, Trans}], srcmsg2po} ->
ets:delete(translations_obsolete, Str),
print_translation(File, Line, Str, Trans);
{_, used} ->
case ets:lookup(files, File) of
[{_}] ->
ok;
_ ->
io:format("~n% ~s~n", [File]),
ets:insert(files, {File})
end,
case Str of
[] -> ok;
_ -> io:format("{~p, \"\"}.~n", [Str])
end,
ets:insert(translations, {Str, ""});
{_, srcmsg2po} ->
case ets:lookup(files, File) of
[{_}] ->
ok;
_ ->
ets:insert(files, {File})
end,
ets:insert(translations, {Str, ""}),
print_translation(File, Line, Str, "");
_ ->
ok
end.
load_file(File) ->
case file:consult(File) of
{ok, Terms} ->
lists:foreach(
fun({Orig, Trans}) ->
case Trans of
"" ->
ok;
_ ->
ets:insert(translations, {Orig, Trans}),
ets:insert(translations_obsolete, {Orig, Trans})
end
end, Terms);
Err ->
Err
end.
find_src_files(Dir) ->
case file:list_dir(Dir) of
{ok, FileList} ->
recurse_filelist(
lists:map(
fun(F) ->
filename:join(Dir, F)
end, FileList));
_ ->
[]
end.
recurse_filelist(FileList) ->
recurse_filelist(FileList, []).
recurse_filelist([], Acc) ->
lists:reverse(Acc);
recurse_filelist([H | T], Acc) ->
case file:read_file_info(H) of
{ok, #file_info{type = directory}} ->
recurse_filelist(T, lists:reverse(find_src_files(H)) ++ Acc);
{ok, #file_info{type = regular}} ->
case string:substr(H, string:len(H) - 3) of
".erl" ->
recurse_filelist(T, [H | Acc]);
".hrl" ->
recurse_filelist(T, [H | Acc]);
_ ->
recurse_filelist(T, Acc)
end;
_ ->
recurse_filelist(T, Acc)
end.
print_usage() ->
io:format(
"Usage: extract_translations [-unused] dir file~n"
"~n"
"Example:~n"
" extract_translations . ./msgs/ru.msg~n"
).
%%%
%%% Gettext
%%%
print_po_header(File) ->
MsgProps = get_msg_header_props(File),
{Language, [LastT | AddT]} = prepare_props(MsgProps),
print_po_header(Language, LastT, AddT).
get_msg_header_props(File) ->
{ok, F} = file:open(File, [read]),
Lines = get_msg_header_props(F, []),
file:close(F),
Lines.
get_msg_header_props(F, Lines) ->
String = io:get_line(F, ""),
case io_lib:fread("% ", String) of
{ok, [], RemString} ->
case io_lib:fread("~s", RemString) of
{ok, [Key], Value} when Value /= "\n" ->
%% The first character in Value is a blankspace:
%% And the last characters are 'slash n'
ValueClean = string:substr(Value, 2, string:len(Value)-2),
get_msg_header_props(F, Lines ++ [{Key, ValueClean}]);
_ ->
get_msg_header_props(F, Lines)
end;
_ ->
Lines
end.
prepare_props(MsgProps) ->
Language = proplists:get_value("Language:", MsgProps),
Authors = proplists:get_all_values("Author:", MsgProps),
{Language, Authors}.
print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
HeaderString =
"msgid \"\"\n"
"msgstr \"\"\n"
++ "\"X-Language: " ++ Language ++ "\\n\"\n"
"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
++ AdditionalTranslatorsString ++
"\"MIME-Version: 1.0\\n\"\n"
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
"\"Content-Transfer-Encoding: 8bit\\n\"\n",
io:format("~s~n", [HeaderString]).
build_additional_translators(List) ->
lists:foldl(
fun(T, Str) ->
Str ++ "\"X-Additional-Translator: " ++ T ++ "\\n\"\n"
end,
"",
List).
print_translation(File, Line, Str, StrT) ->
StrQ = ejabberd_regexp:greplace(list_to_binary(Str), <<"\\\"">>, <<"\\\\\"">>),
StrTQ = ejabberd_regexp:greplace(list_to_binary(StrT), <<"\\\"">>, <<"\\\\\"">>),
io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
print_translation_obsolete(Str, StrT) ->
File = "unknown.erl",
Line = 1,
StrQ = ejabberd_regexp:greplace(Str, "\\\"", "\\\\\""),
StrTQ = ejabberd_regexp:greplace(StrT, "\\\"", "\\\\\""),
io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).
@@ -1,366 +0,0 @@
#!/bin/bash
# Frontend for ejabberd's extract_translations.erl
# by Badlop
# How to create template files for a new language:
# NEWLANG=zh
# cp msgs/ejabberd.pot msgs/$NEWLANG.po
# echo \{\"\",\"\"\}. > msgs/$NEWLANG.msg
# ../../extract_translations/prepare-translation.sh -updateall
prepare_dirs ()
{
# Where is Erlang binary
ERL=`which erl`
EJA_SRC_DIR=$EJA_DIR/src/
EJA_MSGS_DIR=$EJA_DIR/priv/msgs/
EXTRACT_DIR=$EJA_DIR/contrib/extract_translations/
EXTRACT_ERL=$EXTRACT_DIR/extract_translations.erl
EXTRACT_BEAM=$EXTRACT_DIR/extract_translations.beam
SRC_DIR=$RUN_DIR/src
EBIN_DIR=$RUN_DIR/ebin
MSGS_DIR=$EJA_DIR/priv/msgs
if !([[ -n $EJA_DIR ]])
then
echo "ejabberd dir does not exist: $EJA_DIR"
fi
if !([[ -x $EXTRACT_BEAM ]])
then
sh -c "cd $EXTRACT_DIR; $ERL -compile $EXTRACT_ERL"
fi
}
extract_lang ()
{
MSGS_FILE=$1
MSGS_FILE2=$MSGS_FILE.translate
MSGS_PATH=$MSGS_DIR/$MSGS_FILE
MSGS_PATH2=$MSGS_DIR/$MSGS_FILE2
echo -n "Extracting language strings for '$MSGS_FILE':"
echo -n " new..."
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra . $MSGS_PATH >$MSGS_PATH.new
sed -e 's/^% \.\//% /g;' $MSGS_PATH.new > $MSGS_PATH.new2
mv $MSGS_PATH.new2 $MSGS_PATH.new
echo -n " old..."
$ERL -pa $EXTRACT_DIR -noinput -noshell -s extract_translations -s init stop -extra -unused . $MSGS_PATH >$MSGS_PATH.unused
find_unused_full $MSGS_FILE $MSGS_FILE.unused
echo "" >$MSGS_PATH2
echo " ***** Translation file for ejabberd ***** " >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo " *** New strings: Can you please translate them? *** " >>$MSGS_PATH2
cat $MSGS_PATH.new >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo " *** Unused strings: They will be removed automatically *** " >>$MSGS_PATH2
cat $MSGS_PATH.unused.full >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
echo " *** Already translated strings: you can also modify any of them if you want *** " >>$MSGS_PATH2
echo "" >>$MSGS_PATH2
cat $MSGS_PATH.old_cleaned >>$MSGS_PATH2
echo " ok"
rm $MSGS_PATH.new
rm $MSGS_PATH.old_cleaned
rm $MSGS_PATH.unused.full
}
extract_lang_all ()
{
cd $MSGS_DIR
for i in $( ls *.msg ) ; do
extract_lang $i;
done
echo -e "File\tMissing\tLanguage\t\tLast translator"
echo -e "----\t-------\t--------\t\t---------------"
cd $MSGS_DIR
for i in $( ls *.msg ) ; do
MISSING=`cat $i.translate | grep "\", \"\"}." | wc -l`
LANGUAGE=`grep "X-Language:" $i.translate | sed 's/% Language: //g'`
LASTAUTH=`grep "Author:" $i.translate | head -n 1 | sed 's/% Author: //g'`
echo -e "$i\t$MISSING\t$LANGUAGE\t$LASTAUTH"
done
cd $MSGS_DIR
REVISION=`git describe --always`
zip $HOME/ejabberd-langs-$REVISION.zip *.translate;
rm *.translate
}
find_unused_full ()
{
DATFILE=$1
DATFILEI=$1.old_cleaned
DELFILE=$2
cd msgs
DATFILE1=$DATFILE.t1
DATFILE2=$DATFILE.t2
DELFILE1=$DELFILE.t1
DELFILE2=$DELFILE.t2
DELFILEF=$DATFILE.unused.full
echo "" >$DELFILEF
grep -v "\\\\" $DELFILE >$DELFILE2
echo ENDFILEMARK >>$DELFILE2
cp $DATFILE $DATFILEI
cp $DATFILE $DATFILE2
cp $DELFILE2 $DELFILE1
STRING=`head -1 $DELFILE1`
until [[ $STRING == ENDFILEMARK ]]; do
cp $DELFILE2 $DELFILE1
cp $DATFILE2 $DATFILE1
STRING=`head -1 $DELFILE1`
cat $DATFILE1 | grep "$STRING" >>$DELFILEF
cat $DATFILE1 | grep -v "$STRING" >$DATFILE2
cat $DELFILE1 | grep -v "$STRING" >$DELFILE2
done
mv $DATFILE2 $DATFILEI
rm -f $MSGS_PATH.t1
rm $MSGS_PATH.unused
rm -f $MSGS_PATH.unused.t1
rm $MSGS_PATH.unused.t2
cd ..
}
extract_lang_srcmsg2po ()
{
LANG=$1
LANG_CODE=$LANG.$PROJECT
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
PO_PATH=$MSGS_DIR/$LANG_CODE.po
echo $MSGS_PATH
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$PO_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $PO_PATH.1 > $PO_PATH.2
msguniq --sort-by-file $PO_PATH.2 --output-file=$PO_PATH
rm $PO_PATH.*
}
extract_lang_src2pot ()
{
LANG_CODE=$PROJECT
MSGS_PATH=$MSGS_DIR/$LANG_CODE.msg
POT_PATH=$MSGS_DIR/$LANG_CODE.pot
echo -n "" >$MSGS_PATH
echo "% Language: Language Name" >>$MSGS_PATH
echo "% Author: Translator name and contact method" >>$MSGS_PATH
echo "" >>$MSGS_PATH
cd $SRC_DIR
$ERL -pa $EXTRACT_DIR -pa $EBIN_DIR -pa $EJA_SRC_DIR -pa ../include -noinput -noshell -s extract_translations -s init stop -extra -srcmsg2po . $MSGS_PATH >$POT_PATH.1
sed -e 's/ \[\]$/ \"\"/g;' $POT_PATH.1 > $POT_PATH.2
#msguniq --sort-by-file $POT_PATH.2 $EJA_MSGS_DIR --output-file=$POT_PATH
msguniq --sort-by-file $POT_PATH.2 --output-file=$POT_PATH
rm $POT_PATH.*
rm $MSGS_PATH
# If the project is a specific module, not the main ejabberd
if [[ $PROJECT != ejabberd ]] ; then
# Remove from project.pot the strings that are already present in the general ejabberd
EJABBERD_MSG_FILE=$EJA_MSGS_DIR/es.po # This is just some file with translated strings
POT_PATH_TEMP=$POT_PATH.temp
msgattrib --set-obsolete --only-file=$EJABBERD_MSG_FILE -o $POT_PATH_TEMP $POT_PATH
mv $POT_PATH_TEMP $POT_PATH
fi
}
extract_lang_popot2po ()
{
LANG_CODE=$1
PO_PATH=$MSGS_DIR/$LANG_CODE.po
POT_PATH=$MSGS_DIR/$PROJECT.pot
msgmerge $PO_PATH $POT_PATH >$PO_PATH.translate 2>/dev/null
mv $PO_PATH.translate $PO_PATH
}
extract_lang_po2msg ()
{
LANG_CODE=$1
PO_PATH=$LANG_CODE.po
MS_PATH=$PO_PATH.ms
MSGID_PATH=$PO_PATH.msgid
MSGSTR_PATH=$PO_PATH.msgstr
MSGS_PATH=$LANG_CODE.msg
cd $MSGS_DIR
# Check PO has correct ~
# Let's convert to C format so we can use msgfmt
PO_TEMP=$LANG_CODE.po.temp
cat $PO_PATH | sed 's/%/perc/g' | sed 's/~/%/g' | sed 's/#:.*/#, c-format/g' >$PO_TEMP
msgfmt $PO_TEMP --check-format
result=$?
rm $PO_TEMP
if [ $result -ne 0 ] ; then
exit 1
fi
msgattrib $PO_PATH --translated --no-fuzzy --no-obsolete --no-location --no-wrap | grep "^msg" | tail --lines=+3 >$MS_PATH
grep "^msgid" $PO_PATH.ms | sed 's/^msgid //g' >$MSGID_PATH
grep "^msgstr" $PO_PATH.ms | sed 's/^msgstr //g' >$MSGSTR_PATH
echo "%% -*- coding: latin-1 -*-" >$MSGS_PATH
paste $MSGID_PATH $MSGSTR_PATH --delimiter=, | awk '{print "{" $0 "}."}' | sort -g >>$MSGS_PATH
rm $MS_PATH
rm $MSGID_PATH
rm $MSGSTR_PATH
}
extract_lang_updateall ()
{
echo "Generating POT"
extract_lang_src2pot
cd $MSGS_DIR
echo ""
echo -e "File Missing Language Last translator"
echo -e "---- ------- -------- ---------------"
for i in $( ls *.msg ) ; do
LANG_CODE=${i%.msg}
echo -n $LANG_CODE | awk '{printf "%-6s", $1 }'
# Convert old MSG file to PO
PO=$LANG_CODE.po
[ -f $PO ] || extract_lang_srcmsg2po $LANG_CODE
extract_lang_popot2po $LANG_CODE
extract_lang_po2msg $LANG_CODE
MISSING=`msgfmt --statistics $PO 2>&1 | awk '{printf "%5s", $4 }'`
echo -n " $MISSING"
LANGUAGE=`grep "X-Language:" $PO | sed 's/\"X-Language: //g' | sed 's/\\\\n\"//g' | awk '{printf "%-12s", $1}'`
echo -n " $LANGUAGE"
LASTAUTH=`grep "Last-Translator" $PO | sed 's/\"Last-Translator: //g' | sed 's/\\\\n\"//g'`
echo " $LASTAUTH"
done
echo ""
rm messages.mo
cd ..
}
translation_instructions ()
{
echo ""
echo " A new file has been created for you, with the current, the new and the deprecated strings:"
echo " $MSGS_PATH2"
echo ""
echo " At the end of that file you will find the strings you must update:"
echo " - Untranslated strings are like this: {"March", ""}."
echo " To translate the string, add the text inside the commas. Example:"
echo " {"March", "Marzo"}."
echo " - Old strings that are not used: "Woowoa""
echo " Search the entire file for those strings and remove them"
echo ""
echo " Once you have translated all the strings and removed all the old ones,"
echo " rename the file to overwrite the previous one:"
echo " $MSGS_PATH"
}
EJA_DIR=`pwd`
RUN_DIR=`pwd`
PROJECT=ejabberd
while [ $# -ne 0 ] ; do
PARAM=$1
shift
case $PARAM in
--) break ;;
-project)
PROJECT=$1
shift
;;
-ejadir)
EJA_DIR=$1
shift
;;
-rundir)
RUN_DIR=$1
shift
;;
-lang)
LANGU=$1
prepare_dirs
extract_lang $LANGU
shift
;;
-langall)
prepare_dirs
extract_lang_all
;;
-srcmsg2po)
LANG_CODE=$1
prepare_dirs
extract_lang_srcmsg2po $LANG_CODE
shift
;;
-popot2po)
LANG_CODE=$1
prepare_dirs
extract_lang_popot2po $LANG_CODE
shift
;;
-src2pot)
prepare_dirs
extract_lang_src2pot
;;
-po2msg)
LANG_CODE=$1
prepare_dirs
extract_lang_po2msg $LANG_CODE
shift
;;
-updateall)
prepare_dirs
extract_lang_updateall
;;
*)
echo "Options:"
echo " -langall"
echo " -lang LANGUAGE_FILE"
echo " -srcmsg2po LANGUAGE Construct .msg file using source code to PO file"
echo " -src2pot Generate template POT file from source code"
echo " -popot2po LANGUAGE Update PO file with template POT file"
echo " -po2msg LANGUAGE Export PO file to MSG file"
echo " -updateall Generate POT and update all PO"
echo ""
echo "Example:"
echo " ./prepare-translation.sh -lang es.msg"
exit 0
;;
esac
done
+83 -41
View File
@@ -1,21 +1,23 @@
ejabberd container
- [Introduction](#introduction)
- [Version](#version)
- [Quick Start](#quick-start)
- [Usage](#usage)
- [Persistence](#persistence)
- [SSL Certificates](#ssl-certificates)
- [Base Image](#base-image)
- [Ejabberd Configuration](#ejabberd-configuration)
- [Cluster Example](#cluster-example)
- [Runtime Configuration](#runtime-configuration)
- [Served Hostnames](#served-hostnames)
- [Authentication](#authentication)
- [Admins](#admins)
- [Users](#users)
- [SSL](#ssl)
- [Erlang](#erlang)
- [Modules](#modules)
- [Logging](#logging)
- [Mount Configurations](#mount-configurations)
- [Erlang Configuration](#erlang-configuration)
- [Maintenance](#maintenance)
- [Register Users](#register-users)
- [Creating Backups](#creating-backups)
@@ -28,11 +30,29 @@ ejabberd container
# Introduction
Dockerfile to build an [ejabberd](https://www.ejabberd.im/) container image.
This [ejabberd][] docker container is based on the work done by [rroemhild][]. See more [in this blogpost][].
This container includes the necessary files to build your own containerized ejabberd,
but *IT IS NOT* used to generate official images on the docker [hub][].
This container is not maintained by [ProcessOne][].
Docker Tag Names are based on ejabberd versions in git [tags][]. The image tag `:latest` is based on the master branch.
[ProcessOne][] provides and maintain official containers on the docker [hub][], which targets developers for now and will becomes production ready in a near future.
These [new containers] allow to build and run ejabberd in a simple and lightweight environment.
[tags]: https://github.com/rroemhild/ejabberd/tags
[ejabberd]: https://www.ejabberd.im/
[rroemhild]: https://github.com/rroemhild/docker-ejabberd/
[in this blogpost]: https://blog.process-one.net/ejabberd-16-12/
[hub]: https://hub.docker.com/r/ejabberd/ecs/
[new containers]: https://github.com/processone/docker-ejabberd/
[ProcessOne]: https://www.process-one.net/
## Version
Current Version: `17.08`
Docker Tag Names are based on ejabberd versions in git [branches][] and [tags][]. The image tag `:latest` is based on the master branch.
[tags]: https://github.com/rroemhild/docker-ejabberd/tags
[branches]: https://github.com/rroemhild/docker-ejabberd/branches
# Quick Start
@@ -46,13 +66,20 @@ docker run -d \
-p 5280:5280 \
-h 'xmpp.example.de' \
-e "XMPP_DOMAIN=example.de" \
-e "ERLANG_NODE=nodename" \
-e "ERLANG_NODE=ejabberd" \
-e "EJABBERD_ADMINS=admin@example.de admin2@example.de" \
-e "EJABBERD_USERS=admin@example.de:password1234 admin2@example.de" \
-e "TZ=Europe/Berlin" \
rroemhild/ejabberd
```
or with the [docker-compose](examples/docker-compose/docker-compose.yml) example
```bash
wget https://raw.githubusercontent.com/rroemhild/docker-ejabberd/master/examples/docker-compose/docker-compose.yml
docker-compose up
```
# Usage
## Persistence
@@ -100,7 +127,11 @@ ADD ./example.com.pem /opt/ejabberd/ssl/example.com.pem
If you need root privileges switch to `USER root` and go back to `USER ejabberd` when you're done.
# Ejabberd Configuration
## Cluster Example
The [docker-compose-cluster](examples/docker-compose-cluster) example demonstrates how to extend this container image to setup a multi-master cluster.
# Runtime Configuration
You can additionally provide extra runtime configuration in a downstream image by replacing the config template `ejabberd.yml.tpl` with one based on this image's template and include extra interpolation of environment variables. The template is parsed by Jinja2 with the runtime environment (equivalent to Python's `os.environ` available as `env`).
@@ -138,6 +169,26 @@ EJABBERD_EXTAUTH_CACHE=600
```
**EJABBERD_EXTAUTH_INSTANCES** must be an integer with a minimum value of 1. **EJABBERD_EXTAUTH_CACHE** can be set to "false" or an integer value representing cache time in seconds. Note that caching should not be enabled if internal auth is also enabled.
### Password format
The variable `EJABBERD_AUTH_PASSWORD_FORMAT` controls in which format user passwords are
stored. Possible values are `plain` and `scram`. The default is to store
[SCRAM](https://en.wikipedia.org/wiki/Salted_Challenge_Response_Authentication_Mechanism)bled
passwords, meaning that it is impossible to obtain the original plain password from the
stored information.
NOTE: SCRAM does not work with SIP/TURN foreign authentication methods. In this case, you
may have to disable the option. More details can be found here:
https://docs.ejabberd.im/admin/configuration/#internal
If using SCRAM with an SQL database that has plaintext passwords stored, use the command
```
ejabberdctl convert_to_scram example.org
```
to convert all your existing plaintext passwords to scrambled format.
### MySQL Authentication
Set `EJABBERD_AUTH_METHOD=external` and `EJABBERD_EXTAUTH_PROGRAM=/opt/ejabberd/scripts/lib/auth_mysql.py` to enable MySQL authentication. Use the following environment variables to configure the database connection and the layout of the database. Password changing, registration, and unregistration are optional features and are enabled only if the respective queries are provided.
@@ -215,28 +266,38 @@ EJABBERD_USERS=admin@example.ninja:password1234 user1@test.com user1@xyz.io
```
## SSL
- **EJABBERD_SKIP_MAKE_SSLCERT**: Skip generating ssl certificates. Default: false
- **EJABBERD_SSLCERT_HOST**: SSL Certificate for the hostname.
- **EJABBERD_SSLCERT_EXAMPLE_COM**: SSL Certificates for XMPP domains.
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Default: `true`.
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Default: `true`.
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Default: `true`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Default: `false`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Default: `true`.
- **EJABBERD_CIPHERS**: Cipher suite. Default: `HIGH:!aNULL:!3DES`.
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Default: `false`.
- **EJABBERD_STARTTLS**: Set to `false` to disable StartTLS for client to server connections. Defaults
to `true`.
- **EJABBERD_S2S_SSL**: Set to `false` to disable SSL in server 2 server connections. Defaults to `true`.
- **EJABBERD_HTTPS**: If your proxy terminates SSL you may want to disable HTTPS on port 5280 and 5443. Defaults to `true`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1**: Allow TLSv1 protocol. Defaults to `false`.
- **EJABBERD_PROTOCOL_OPTIONS_TLSV1_1**: Allow TLSv1.1 protocol. Defaults to `true`.
- **EJABBERD_CIPHERS**: Cipher suite. Defaults to `HIGH:!aNULL:!3DES`.
- **EJABBERD_DHPARAM**: Set to `true` to use or generate custom DH parameters. Defaults to `false`.
- **EJABBERD_SKIP_MAKE_DHPARAM**: Skip generating DH params. Default: false
## Erlang
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `ejabberd` lets erlang add the hostname. Defaults to `ejabberd@localhost`.
- **ERLANG_COOKIE**: Set erlang cookie. Defaults to auto-generated cookie.
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd.
## Modules
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Default: `false`.
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Default: `false`.
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Default: `true`.
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Default: `true`.
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Default: `true`.
- **EJABBERD_SKIP_MODULES_UPDATE**: If you do not need to update ejabberd modules specs, skip the update task and speedup start. Defaults to `false`.
- **EJABBERD_MOD_MUC_ADMIN**: Activate the mod_muc_admin module. Defaults to `false`.
- **EJABBERD_MOD_ADMIN_EXTRA**: Activate the mod_muc_admin module. Defaults to `true`.
- **EJABBERD_REGISTER_TRUSTED_NETWORK_ONLY**: Only allow user registration from the trusted_network access rule. Defaults to `true`.
- **EJABBERD_MOD_VERSION**: Activate the mod_version module. Defaults to `true`.
- **EJABBERD_SOURCE_MODULES**: List of modules, which will be installed from sources localized in ${EJABBERD_HOME}/module_source.
- **EJABBERD_CONTRIB_MODULES**: List of modules, which will be installed from contrib repository.
- **EJABBERD_RESTART_AFTER_MODULE_INSTALL**: If any modules were installed, restart the server, if the option is enabled.
- **EJABBERD_CUSTOM_AUTH_MODULE_OVERRIDE**: If a custom module was defined for handling auth, we need to override the pre-defined auth methods in the config.
## Logging
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Default: `4` (Info).
Use the **EJABBERD_LOGLEVEL** environment variable to set verbosity. Defaults to `4` (Info).
```
loglevel: Verbosity of log files generated by ejabberd.
@@ -276,25 +337,6 @@ Example configuration files can be downloaded from the ejabberd [github](https:/
When these files exist in ```/opt/ejabberd/conf```, the run script will ignore the configuration templates.
## Erlang Configuration
With the following environment variables you can configure options that are passed by ejabberdctl to the erlang runtime system when starting ejabberd.
- **POLL**: Set to `false` to disable Kernel polling. Default: `true`.
- **SMP**: SMP support `enable`, `auto`, `disable`. Default: `auto`.
- **ERL_MAX_PORTS**: Maximum number of simultaneously open Erlang ports. Default: `32000`.
- **FIREWALL_WINDOW**: Range of allowed ports to pass through a firewall. Default: `not defined`.
- **INET_DIST_INTERFACE**: IP address where this Erlang node listens other nodes. Default: `0.0.0.0`.
- **ERL_EPMD_ADDRESS**: IP addresses where epmd listens for connections. Default: `0.0.0.0`.
- **ERL_PROCESSES**: Maximum number of Erlang processes. Default: `250000`.
- **ERL_MAX_ETS_TABLES**: Maximum number of Erlang processes. Default: `1400`.
- **ERLANG_OPTIONS**: Overwrite additional options passed to erlang while starting ejabberd. Default: `-noshell`
- **ERLANG_NODE**: Allows to explicitly specify erlang node for ejabberd. Set to `nodename` lets erlang add the hostname. Default: `ejabberd@localhost`.
- **EJABBERD_CONFIG_PATH**: ejabberd configuration file. Default: `/opt/ejabberd/conf/ejabberd.yml`.
- **CONTRIB_MODULES_PATH**: contributed ejabberd modules path. Default: `/opt/ejabberd/modules`.
- **CONTRIB_MODULES_CONF_DIR**: configuration directory for contributed modules. Default: `/opt/ejabberd/modules/conf`.
- **ERLANG_COOKIE**: Set erlang cookie. Default is to auto-generated cookie.
# Maintenance
The `ejabberdctl` command is in the search path and can be run by:
+5 -1
View File
@@ -123,8 +123,10 @@ auth_method:
- {{ auth_method }}
{%- endfor %}
auth_password_format: {{ env.get('EJABBERD_AUTH_PASSWORD_FORMAT', 'scram') }}
{%- if 'anonymous' in env.get('EJABBERD_AUTH_METHOD', 'internal').split() %}
anonymous_protocol: login_anon
anonymous_protocol: both
allow_multiple_connections: true
{%- endif %}
@@ -347,6 +349,8 @@ modules:
- "flat"
- "hometree"
- "pep" # pep requires mod_caps
mod_push: {}
mod_push_keepalive: {}
mod_register:
##
## Protect In-Band account registrations with CAPTCHA.
@@ -0,0 +1,3 @@
FROM rroemhild/ejabberd
ENV EJABBERD_HOME /opt/ejabberd
COPY ./scripts $EJABBERD_HOME/scripts
@@ -0,0 +1,23 @@
# Ejabberd cluster with docker compose
This example uses [dnsdocker](https://github.com/tonistiigi/dnsdock) to discover other nodes and setup a multi-master cluster.
Build the ejabberd cluster image:
```bash
git clone https://github.com/rroemhild/docker-ejabberd.git
cd docker-ejabberd/examples/docker-compose-cluster
docker-compose build
```
Start dnsdocker and the first ejabberd node:
```bash
docker-compose up -d
```
Wait until the first ejabberd node is up and running `docker-compose logs ejabberd`, then add some ejabberd nodes to the cluster:
```bash
docker-compose scale ejabberd=4
```
@@ -0,0 +1,25 @@
dnsdock:
image: tonistiigi/dnsdock
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 172.17.42.1:53:53/udp
ejabberd:
build: .
ports:
- 5222
- 5269
- 5280
environment:
- XMPP_DOMAIN=example.com
- ERLANG_NODE=ejabberd
- EJABBERD_ADMINS=admin@example.com
- EJABBERD_USERS=admin@example.com:test321 user@example.com
- ERLANG_COOKIE=testCluster
- SKIP_MODULES_UPDATE=true
- EJABBERD_CLUSTER=true
- USE_DNS=true
dns: 172.17.42.1
domainname: dockercomposecluster_ejabberd.docker
tty: true
@@ -0,0 +1,37 @@
# overwrite get_nodename to discover hostname from DNS
get_nodename() {
local hostname=${HOSTNAME}
# get hostname from dns
if ( is_true ${USE_DNS} ); then
# wait for dns registration
sleep 1
nodename=$(discover_dns_hostname ${HOSTIP})
is_set ${nodename} \
&& hostname=${nodename}
fi
echo $hostname
return 0
}
# discover hostname from dns with a reverse lookup
discover_dns_hostname() {
local hostip=$1
# try to get the hostname from dns
local dnsname=$(drill -x ${hostip} \
| grep PTR \
| awk '{print $5}' \
| grep -E "^[a-zA-Z0-9]+([-._]?[a-zA-Z0-9]+)*.[a-zA-Z]+\.$" \
| cut -d '.' -f1 \
| tail -1)
is_set ${dnsname} \
&& echo ${dnsname}
return 0
}
@@ -0,0 +1,28 @@
#!/bin/bash
set -e
source "${EJABBERD_HOME}/scripts/lib/base_config.sh"
source "${EJABBERD_HOME}/scripts/lib/config.sh"
source "${EJABBERD_HOME}/scripts/lib/base_functions.sh"
source "${EJABBERD_HOME}/scripts/lib/functions.sh"
get_cluster_node_from_dns() {
local cluster_host=$(drill ${DOMAINNAME} \
| grep ${DOMAINNAME} \
| grep -v ${HOSTIP} \
| awk '{print $5}' \
| grep -v "^$" \
| head -1)
echo $(discover_dns_hostname ${cluster_host})
}
file_exist ${FIRST_START_DONE_FILE} \
&& exit 0
join_cluster $(get_cluster_node_from_dns)
exit 0
+1
View File
@@ -0,0 +1 @@
# simple docker-compose example
@@ -0,0 +1,11 @@
ejabberd:
image: rroemhild/ejabberd
ports:
- 5222:5222
- 5269:5269
- 5280:5280
environment:
- ERLANG_NODE=ejabberd
- XMPP_DOMAIN=example.com xyz.io
- EJABBERD_ADMINS=admin@example.com
- EJABBERD_USERS=admin@example.com:password4321 user1@xyz.io
+2 -2
View File
@@ -3,7 +3,7 @@ readonly HOSTNAME=$(hostname -f)
readonly DOMAINNAME=$(hostname -d)
readonly ERLANGCOOKIEFILE="${EJABBERD_HOME}/.erlang.cookie"
readonly EJABBERDCTL="/sbin/ejabberdctl"
readonly EJABBERDCTL="/usr/local/sbin/ejabberdctl"
readonly CONFIGFILE="${EJABBERD_HOME}/conf/ejabberd.yml"
readonly CONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberd.yml.tpl"
readonly CTLCONFIGFILE="${EJABBERD_HOME}/conf/ejabberdctl.cfg"
@@ -11,7 +11,7 @@ readonly CTLCONFIGTEMPLATE="${EJABBERD_HOME}/conf/ejabberdctl.cfg.tpl"
readonly SSLCERTDIR="${EJABBERD_HOME}/ssl"
readonly SSLCERTHOST="${SSLCERTDIR}/host.pem"
readonly SSLDHPARAM="${SSLCERTDIR}/dh.pem"
readonly LOGDIR="/var/log/ejabberd"
readonly LOGDIR="/usr/local/var/log/ejabberd"
readonly FIRST_START_DONE_FILE="/${EJABBERD_HOME}/first-start-done"
readonly CLUSTER_NODE_FILE="/${EJABBERD_HOME}/cluster-done"
+10 -1
View File
@@ -111,8 +111,10 @@ hosts:
## 'CERTFILE': "/path/to/xmpp.pem"
## 'CIPHERS': "ECDH:DH:!3DES:!aNULL:!eNULL:!MEDIUM@STRENGTH"
## 'TLSOPTS':
## - "no_sslv2"
## - "no_sslv3"
## - "no_tlsv1"
## - "no_tlsv1_1"
## - "cipher_server_preference"
## - "no_compression"
## 'DHFILE': "/path/to/dhparams.pem" # generated with: openssl dhparam -out dhparams.pem 2048
@@ -231,7 +233,7 @@ listen:
##
## s2s_use_starttls: Enable STARTTLS for S2S connections.
## Allowed values are: false optional required required_trusted
## Allowed values are: false, optional or required
## You must specify a certificate file.
##
## s2s_use_starttls: required
@@ -725,6 +727,8 @@ modules:
- "flat"
- "hometree"
- "pep" # pep requires mod_caps
mod_push: {}
mod_push_keepalive: {}
## mod_register:
##
## Protect In-Band account registrations with CAPTCHA.
@@ -764,6 +768,11 @@ modules:
mod_time: {}
mod_vcard:
search: false
mod_vcard_xupdate: {}
## Convert all avatars posted by Android clients from WebP to JPEG
mod_avatar:
convert:
webp: jpeg
mod_version: {}
mod_stream_mgmt: {}
## Non-SASL Authentication (XEP-0078) is now disabled by default
+80 -75
View File
@@ -7,33 +7,41 @@ ERL_MAX_PORTS=32000
ERL_PROCESSES=250000
ERL_MAX_ETS_TABLES=1400
FIREWALL_WINDOW=""
INET_DIST_INTERFACE=""
ERLANG_NODE=ejabberd@localhost
# define default environment variables
ERL="{{erl}}"
IEX="{{bindir}}/iex"
EPMD="{{epmd}}"
INSTALLUSER={{installuser}}
INSTALLUSER="{{installuser}}"
# check the proper system user is used if defined
EXEC_CMD="false"
if [ -n "$INSTALLUSER" ] ; then
if [ $(id -g) -eq $(id -g $INSTALLUSER || echo -1) ] ; then
# check the proper system user is used
case $(id -un) in
"$INSTALLUSER")
EXEC_CMD="as_current_user"
else
id -Gn | grep -q wheel && EXEC_CMD="as_install_user"
fi
else
EXEC_CMD="as_current_user"
fi
if [ "$EXEC_CMD" = "false" ] ; then
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
exit 7
fi
;;
root)
if [ -n "$INSTALLUSER" ] ; then
EXEC_CMD="as_install_user"
else
EXEC_CMD="as_current_user"
echo "WARNING: This is not recommended to run ejabberd as root" >&2
fi
;;
*)
if [ -n "$INSTALLUSER" ] ; then
echo "ERROR: This command can only be run by root or the user $INSTALLUSER" >&2
exit 7
else
EXEC_CMD="as_current_user"
fi
;;
esac
# parse command line parameters
for arg; do
case $1 in
case $arg in
-n|--node) ERLANG_NODE_ARG=$2; shift;;
-s|--spool) SPOOL_DIR=$2; shift;;
-l|--logs) LOGS_DIR=$2; shift;;
@@ -48,28 +56,26 @@ for arg; do
done
# define ejabberd variables if not already defined from the command line
: ${ETC_DIR:={{sysconfdir}}/ejabberd}
: ${LOGS_DIR:={{localstatedir}}/log/ejabberd}
: ${SPOOL_DIR:={{localstatedir}}/lib/ejabberd}
: ${EJABBERD_CONFIG_PATH:="$ETC_DIR"/ejabberd.yml}
: ${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR"/ejabberdctl.cfg}
: "${ETC_DIR:="{{sysconfdir}}/ejabberd"}"
: "${LOGS_DIR:="{{localstatedir}}/log/ejabberd"}"
: "${SPOOL_DIR:="{{localstatedir}}/lib/ejabberd"}"
: "${EJABBERD_CONFIG_PATH:="$ETC_DIR/ejabberd.yml"}"
: "${EJABBERDCTL_CONFIG_PATH:="$ETC_DIR/ejabberdctl.cfg"}"
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
[ "$ERLANG_NODE_ARG" != "" ] && ERLANG_NODE=$ERLANG_NODE_ARG
[ -n "$ERLANG_NODE_ARG" ] && ERLANG_NODE="$ERLANG_NODE_ARG"
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && S="-s"
: ${EJABBERD_DOC_PATH:={{docdir}}}
: ${EJABBERD_LOG_PATH:="$LOGS_DIR"/ejabberd.log}
: "${EJABBERD_DOC_PATH:="{{docdir}}"}"
: "${EJABBERD_LOG_PATH:="$LOGS_DIR/ejabberd.log"}"
# define erl parameters
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
if [ "$FIREWALL_WINDOW" != "" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel " \
"inet_dist_listen_min ${FIREWALL_WINDOW%-*} " \
"inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
if [ -n "$FIREWALL_WINDOW" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_listen_min ${FIREWALL_WINDOW%-*} inet_dist_listen_max ${FIREWALL_WINDOW#*-}"
fi
if [ "$INET_DIST_INTERFACE" != "" ] ; then
if [ -n "$INET_DIST_INTERFACE" ] ; then
INET_DIST_INTERFACE2=$("$ERL" -noshell -eval 'case inet:parse_address("'$INET_DIST_INTERFACE'") of {ok,IP} -> io:format("~p",[IP]); _ -> ok end.' -s erlang halt)
if [ "$INET_DIST_INTERFACE2" != "" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface \"$INET_DIST_INTERFACE2\""
if [ -n "$INET_DIST_INTERFACE2" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
fi
fi
ERL_LIBS={{libdir}}
@@ -103,19 +109,19 @@ export ERL_LIBS
exec_cmd()
{
case $EXEC_CMD in
as_install_user) su -c '"$0" $@"' "$INSTALLUSER" -- "$@" ;;
as_install_user) su -c '"$0" "$@"' "$INSTALLUSER" -- "$@" ;;
as_current_user) "$@" ;;
esac
}
exec_erl()
{
NODE=$1; shift
exec_cmd "$ERL" ${S:--}name $NODE $ERLANG_OPTS "$@"
exec_cmd "$ERL" ${S:--}name "$NODE" $ERLANG_OPTS "$@"
}
exec_iex()
{
NODE=$1; shift
exec_cmd "$IEX" ${S:---}name $NODE --erl "$ERLANG_OPTS" "$@"
exec_cmd "$IEX" -${S:--}name "$NODE" --erl "$ERLANG_OPTS" "$@"
}
# usage
@@ -138,7 +144,7 @@ debugwarning()
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press return to continue"
read foo
read -r
echo ""
fi
}
@@ -161,7 +167,7 @@ livewarning()
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press return to continue"
read foo
read -r
echo ""
fi
}
@@ -191,12 +197,12 @@ help()
uid()
{
uuid=$(uuidgen 2>/dev/null)
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
[ -z "$uuid" ] && uuid=$(printf "%X" $RANDOM$(date +%M%S)$$)
[ -z "$uuid" ] && [ -f /proc/sys/kernel/random/uuid ] && uuid=$(cat /proc/sys/kernel/random/uuid)
[ -z "$uuid" ] && uuid=$(printf "%X" "${RANDOM:-$$}$(date +%M%S)")
uuid=${uuid%%-*}
[ $# -eq 0 ] && echo ${uuid}-${ERLANG_NODE}
[ $# -eq 1 ] && echo ${uuid}-${1}-${ERLANG_NODE}
[ $# -eq 2 ] && echo ${uuid}-${1}@${2}
[ $# -eq 0 ] && echo "${uuid}-${ERLANG_NODE}"
[ $# -eq 1 ] && echo "${uuid}-${1}-${ERLANG_NODE}"
[ $# -eq 2 ] && echo "${uuid}-${1}@${2}"
}
# stop epmd if there is no other running node
@@ -210,25 +216,17 @@ stop_epmd()
check_start()
{
"$EPMD" -names 2>/dev/null | grep -q " ${ERLANG_NODE%@*} " && {
ps ux | grep -v grep | grep -q " $ERLANG_NODE " && {
pgrep -f "$ERLANG_NODE" >/dev/null && {
echo "ERROR: The ejabberd node '$ERLANG_NODE' is already running."
exit 4
} || {
ps ux | grep -v grep | grep -q beam && {
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
echo " but no related beam process has been found."
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
exit 5
} || {
"$EPMD" -kill >/dev/null
}
}
} || {
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
cd "$SPOOL_DIR" || {
echo "ERROR: ejabberd can not access directory $SPOOL_DIR"
exit 6
pgrep beam >/dev/null && {
echo "ERROR: The ejabberd node '$ERLANG_NODE' is registered,"
echo " but no related beam process has been found."
echo "Shutdown all other erlang nodes, and call 'epmd -kill'."
exit 5
}
"$EPMD" -kill >/dev/null
}
}
@@ -237,58 +235,65 @@ wait_status()
{
# args: status try delay
# return: 0 OK, 1 KO
timeout=$2
timeout="$2"
status=4
while [ $status -ne $1 ] ; do
sleep $3
timeout=`expr $timeout - 1`
while [ "$status" -ne "$1" ] ; do
sleep "$3"
timeout=$((timeout - 1))
if [ $timeout -eq 0 ] ; then
status=$1
status="$1"
else
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
-extra $ERLANG_NODE $NO_TIMEOUT status > /dev/null
status=$?
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
status="$?"
fi
done
[ $timeout -gt 0 ]
}
# ensure we can change current directory to SPOOL_DIR
[ -d "$SPOOL_DIR" ] || exec_cmd mkdir -p "$SPOOL_DIR"
cd "$SPOOL_DIR" || {
echo "ERROR: can not access directory $SPOOL_DIR"
exit 6
}
# main
case $1 in
start)
check_start
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput -detached
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput -detached
;;
foreground)
check_start
exec_erl $ERLANG_NODE $EJABBERD_OPTS -noinput
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput
;;
live)
livewarning
check_start
exec_erl $ERLANG_NODE $EJABBERD_OPTS
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS
;;
debug)
debugwarning
exec_erl $(uid debug) -hidden -remsh $ERLANG_NODE
exec_erl "$(uid debug)" -hidden -remsh "$ERLANG_NODE"
;;
etop)
exec_erl $(uid top) -hidden -node $ERLANG_NODE -s etop \
exec_erl "$(uid top)" -hidden -node "$ERLANG_NODE" -s etop \
-s erlang halt -output text
;;
iexdebug)
debugwarning
exec_iex $(uid debug) --remsh "$ERLANG_NODE"
exec_iex "$(uid debug)" --remsh "$ERLANG_NODE"
;;
iexlive)
livewarning
exec_iex $ERLANG_NODE --erl "$EJABBERD_OPTS" --app ejabberd
exec_iex "$ERLANG_NODE" --erl "$EJABBERD_OPTS" --app ejabberd
;;
ping)
PEER=${2:-$ERLANG_NODE}
[ "$PEER" = "${PEER%.*}" ] && PS="-s"
exec_cmd "$ERL" ${PS:--}name $(uid ping $(hostname $PS)) $ERLANG_OPTS \
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"$PEER"')])' \
exec_cmd "$ERL" ${PS:--}name "$(uid ping "$(hostname $PS)")" $ERLANG_OPTS \
-noinput -hidden -eval 'io:format("~p~n",[net_adm:ping('"'$PEER'"')])' \
-s erlang halt -output text
;;
started)
@@ -298,8 +303,8 @@ case $1 in
wait_status 3 30 2 && stop_epmd # wait 30x2s before timeout
;;
*)
exec_erl $(uid ctl) -hidden -noinput -s ejabberd_ctl \
-extra $ERLANG_NODE $NO_TIMEOUT "$@"
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT "$@"
result=$?
case $result in
2|3) help;;
+1
View File
@@ -0,0 +1 @@
-define(T(S), <<S>>).
+1 -1
View File
@@ -3,7 +3,7 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "17.6.0",
version: "17.9.0",
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
+12 -8
View File
@@ -1,18 +1,22 @@
%{"cache_tab": {:hex, :cache_tab, "1.0.8", "eac8923f0f20c35e630317790c4d4c2629c5bc792753fa48eb5391bd39c80245", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"distillery": {:hex, :distillery, "1.4.0", "d633cd322c8efa0428082b00b7f902daf8caa166d45f9022bbc19a896d2e1e56", [:mix], []},
"earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], []},
"esip": {:hex, :esip, "1.0.12", "e0505afe74bb362b0ea486e2a64b3c1934b1eb541a7b3e990b23045e4bdc07d4", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.11", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
%{"cache_tab": {:hex, :cache_tab, "1.0.10", "dd6aba8951ba15cab4ad483d997f8eefdb0cb00225971d0629c730d107a2bed6", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"distillery": {:hex, :distillery, "1.4.1", "546d851bf27ae8fe0727e10e4fc4e146ad836eecee138263a60431e688044ed3", [:mix], []},
"earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], []},
"eredis": {:hex, :eredis, "1.0.8", "ab4fda1c4ba7fbe6c19c26c249dc13da916d762502c4b4fa2df401a8d51c5364", [:rebar], []},
"esip": {:hex, :esip, "1.0.15", "82c8b0178618c10b1ac9690841d94025c982d63f8cd6c8f8bf920cf33e301658", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stun, "1.0.14", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.16.2", "3b3e210ebcd85a7c76b4e73f85c5640c011d2a0b2f06dcdf5acdb2ae904e5084", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"ezlib": {:hex, :ezlib, "1.0.2", "22004ecf553a7d831404394d5642712e2aede90522e22bd6ccc089ca410ee098", [:rebar3], []},
"fast_tls": {:hex, :fast_tls, "1.0.12", "861b591f23103142782c5b72de8898673a37acd78646c50dbda978e1e1c5b463", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"fast_tls": {:hex, :fast_tls, "1.0.15", "96546e6a8b8384fbbcddf435c4c42cf2c0a3dc1858c3c9c2e62a74ae1ddd526a", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.23", "1e7b311d3353806ee832d7630fef57713987cea40a7020669cf057d537de4721", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.10", "ce5d52b77cb21968c8b73aa29b39f56a4ffd7e1e11f853d5597e7277858f155e", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], []},
"iconv": {:hex, :iconv, "1.0.5", "ae871aa11c854695db37e48fd5e5583b02e106126fbdf21bb53448f5a47c092b", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [:rebar3], []},
"lager": {:hex, :lager, "3.4.2", "150b9a17b23ae6d3265cc10dc360747621cf217b7a22b8cddf03b2909dbf7aa5", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
"p1_mysql": {:hex, :p1_mysql, "1.0.3", "e2cc26f2e8d17c3885a9c2fee3ff64fcac5915896f50ab6f6aa9b0da1eed341c", [:rebar3], []},
"p1_oauth2": {:hex, :p1_oauth2, "0.6.1", "4e021250cc198c538b097393671a41e7cebf463c248980320e038fe0316eb56b", [:rebar3], []},
"p1_pgsql": {:hex, :p1_pgsql, "1.1.3", "ce94c83e9605c88d5f541b8f4b49edff3dc2bbacd1b6409c4cad0fbf7bef2ac4", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.9", "c33c230efbeb4dcc02911161e3cb1a93231a92df15e3fc97de655a9271a26d9f", [:rebar3], []},
"sqlite3": {:hex, :sqlite3, "1.1.5", "794738b6d07b6d36ec6d42492cb9d629bad9cf3761617b8b8d728e765db19840", [:rebar3], []},
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.11", "386cb3e3543e17a6351028a43e047c2172225d035c826a72fcb67672da9874e5", [:rebar3], [{:fast_tls, "1.0.12", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"xmpp": {:hex, :xmpp, "1.1.11", "8c49964d0d48b81080d2c5700fcf6cc19950ae9dc60a71bd3ff3d4620336d052", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
"stun": {:hex, :stun, "1.0.14", "6dc2080c25a72f7087301dc7333c1ea7d27ea4d88efaa379fc2b5924f3b17006", [:rebar3], [{:fast_tls, "1.0.15", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}]},
"xmpp": {:hex, :xmpp, "1.1.14", "e186f5208e7a448a4af784a8d2cb87cefe99dd49b24623e25d38115b23a50e12", [:rebar3], [{:fast_xml, "1.1.23", [hex: :fast_xml, optional: false]}, {:p1_utils, "1.0.9", [hex: :p1_utils, optional: false]}, {:stringprep, "1.0.9", [hex: :stringprep, optional: false]}]}}
+32
View File
@@ -0,0 +1,32 @@
-module(override_opts).
-export([preprocess/2]).
override_opts(override, Config, Opts) ->
lists:foldl(fun({Opt, Value}, Conf) ->
rebar_config:set(Conf, Opt, Value)
end, Config, Opts);
override_opts(add, Config, Opts) ->
lists:foldl(fun({Opt, Value}, Conf) ->
V = rebar_config:get_local(Conf, Opt, []),
rebar_config:set(Conf, Opt, [Value | V])
end, Config, Opts).
preprocess(Config, _Dirs) ->
Overrides = rebar_config:get_local(Config, overrides, []),
TopOverrides = case rebar_config:get_xconf(Config, top_overrides, []) of
[] -> Overrides;
Val -> Val
end,
Config2 = rebar_config:set_xconf(Config, top_overrides, TopOverrides),
Config3 = case rebar_app_utils:load_app_file(Config2, _Dirs) of
{ok, C, AppName, _AppData} ->
lists:foldl(fun({Type, AppName2, Opts}, Conf1) when
AppName2 == AppName ->
override_opts(Type, Conf1, Opts);
(_, Conf2) ->
Conf2
end, C, TopOverrides);
_ ->
Config2
end,
{ok, Config3, []}.
Vendored
BIN
View File
Binary file not shown.
+23 -18
View File
@@ -20,30 +20,31 @@
{deps, [{lager, ".*", {git, "https://github.com/erlang-lager/lager",
{tag, {if_version_above, "17", "3.4.2", "3.2.1"}}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.9"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.9"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.13"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.9"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.23"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.13"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.10"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.10"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.11"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.16"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.10"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.24"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.15"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.11"}}},
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.1"}}},
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.2"}}},
{luerl, ".*", {git, "https://github.com/rvirding/luerl", {tag, "v0.2"}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.12"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.13"}}}},
{if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.15"}}}},
{if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.16"}}}},
{if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql",
{tag, "1.0.3"}}}},
{tag, "1.0.4"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
{tag, "1.1.3"}}}},
{tag, "1.1.4"}}}},
{if_var_true, sqlite, {sqlite3, ".*", {git, "https://github.com/processone/erlang-sqlite3",
{tag, "1.1.5"}}}},
{if_var_true, pam, {epam, ".*", {git, "https://github.com/processone/epam",
{tag, "1.0.3"}}}},
{if_var_true, zlib, {ezlib, ".*", {git, "https://github.com/processone/ezlib",
{tag, "1.0.2"}}}},
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/basho/riak-erlang-client",
{tag, "2.4.1"}}}},
{if_var_true, riak, {riakc, ".*", {git, "https://github.com/processone/riak-erlang-client.git",
{tag, {if_version_above, "19", "develop", "2.5.3"}}}}},
{if_var_true, graphics, {eimp, ".*", {git, "https://github.com/processone/eimp.git", {tag, "1.0.1"}}}},
%% Elixir support, needed to run tests
{if_var_true, elixir, {elixir, ".*", {git, "https://github.com/elixir-lang/elixir",
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
@@ -51,7 +52,7 @@
{if_not_rebar3, {if_var_true, elixir, {rebar_elixir_plugin, ".*",
{git, "https://github.com/processone/rebar_elixir_plugin", "0.1.0"}}}},
{if_var_true, iconv, {iconv, ".*", {git, "https://github.com/processone/iconv",
{tag, "1.0.5"}}}},
{tag, "1.0.6"}}}},
{if_var_true, tools, {meck, "0.8.*", {git, "https://github.com/eproxus/meck",
{tag, "0.8.4"}}}},
{if_var_true, tools, {moka, ".*", {git, "https://github.com/processone/moka.git",
@@ -71,11 +72,13 @@
p1_utils,
p1_mysql,
p1_pgsql,
p1_oauth2,
epam,
ezlib,
eimp,
iconv]}}.
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl"]}.
{erl_first_files, ["src/ejabberd_config.erl", "src/gen_mod.erl", "src/mod_muc_room.erl", "src/mod_push.erl"]}.
{erl_opts, [nowarn_deprecated_function,
{i, "include"},
@@ -86,11 +89,13 @@
{if_var_true, debug, debug_info},
{if_var_true, sip, {d, 'SIP'}},
{if_var_true, stun, {d, 'STUN'}},
{if_var_true, graphics, {d, 'GRAPHICS'}},
{if_var_true, roster_gateway_workaround, {d, 'ROSTER_GATWAY_WORKAROUND'}},
{if_var_match, db_type, mssql, {d, 'mssql'}},
{if_var_true, elixir, {d, 'ELIXIR_ENABLED'}},
{if_var_true, erlang_deprecated_types, {d, 'ERL_DEPRECATED_TYPES'}},
{if_have_fun, {crypto, strong_rand_bytes, 1}, {d, 'STRONG_RAND_BYTES'}},
{if_have_fun, {rand, uniform, 1}, {d, 'RAND_UNIFORM'}},
{if_have_fun, {gb_sets, iterator_from, 2}, {d, 'GB_SETS_ITERATOR_FROM'}},
{if_have_fun, {public_key, short_name_hash, 1}, {d, 'SHORT_NAME_HASH'}},
{if_var_true, hipe, native},
@@ -102,7 +107,7 @@
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
{if_not_rebar3, {plugins, [
deps_erl_opts, override_deps_versions,
deps_erl_opts, override_deps_versions, override_opts,
{if_var_true, elixir, rebar_elixir_compiler},
{if_var_true, elixir, rebar_exunit}
]}}.
@@ -152,12 +157,12 @@
{"fast_xml", [{if_var_true, full_xml, "--enable-full-xml"}]},
{if_var_true, pam, {"epam", []}},
{if_var_true, zlib, {"ezlib", []}},
{if_var_true, graphics, {"eimp", []}},
{if_var_true, iconv, {"iconv", []}}]}.
{port_env, [{"CFLAGS", "-g -O2 -Wall"}]}.
{port_specs, [{"priv/lib/jid.so", ["c_src/jid.c"]}]}.
%% Local Variables:
%% mode: erlang
%% End:
+6 -6
View File
@@ -255,9 +255,9 @@ CtParams = fun(CompileOpts) ->
GenDepConfigureLine =
fun(DepPath, Flags) ->
["sh -c 'if test ! -f ",DepPath,"config.status -o ",
"config.status -nt ",DepPath,"config.status; ",
"then (cd ", DepPath, " && ",
["sh -c 'if test ! -f config.status -o ",
"../../config.status -nt config.status; ",
"then (",
"CFLAGS=\"", CFlags,"\" ",
"CPPFLAGS=\"", CPPFlags, "\" "
"LDFLAGS=\"", LDFlags, "\"",
@@ -269,8 +269,8 @@ GenDepsConfigure =
fun(Hooks) ->
lists:map(fun({Pkg, Flags}) ->
DepPath = ResolveDepPath("deps/" ++ Pkg ++ "/"),
{'compile',
lists:flatten(GenDepConfigureLine(DepPath, Flags))}
{add, list_to_atom(Pkg), [{pre_hooks, {'compile',
lists:flatten(GenDepConfigureLine(DepPath, Flags))}}]}
end, Hooks)
end,
@@ -326,7 +326,7 @@ Rules = [
AppendList([{coveralls, ".*", {git, "https://github.com/markusn/coveralls-erl.git", "master"}}]), []},
{[post_hooks], [cover_enabled], os:getenv("TRAVIS") == "true",
AppendList2(TravisPostHooks), [], false},
{[pre_hooks], [post_hook_configure], true,
{[overrides], [post_hook_configure], true,
AppendList2(GenDepsConfigure), [], []},
{[ct_extra_params], [eunit_compile_opts], true,
AppendStr2(CtParams), "", []},
+1 -1
View File
@@ -102,7 +102,7 @@ CREATE TABLE archive (
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE FULLTEXT INDEX i_text ON archive(txt);
CREATE INDEX i_username USING BTREE ON archive(username);
CREATE INDEX i_username_timestamp USING BTREE ON archive(username,timestamp);
CREATE INDEX i_timestamp USING BTREE ON archive(timestamp);
CREATE INDEX i_peer USING BTREE ON archive(peer);
CREATE INDEX i_bare_peer USING BTREE ON archive(bare_peer);
+12 -24
View File
@@ -25,12 +25,11 @@
-module(ejabberd_app).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(application).
-export([start/2, prep_stop/1, stop/1, opt_type/1]).
-export([start/2, prep_stop/1, stop/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -49,13 +48,12 @@ start(normal, _Args) ->
setup_if_elixir_conf_used(),
ejabberd_config:start(),
ejabberd_mnesia:start(),
set_settings_from_config(),
file_queue_init(),
maybe_add_nameservers(),
connect_nodes(),
case ejabberd_sup:start_link() of
{ok, SupPid} ->
register_elixir_config_hooks(),
ejabberd_cluster:wait_for_sync(infinity),
{T2, _} = statistics(wall_clock),
?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs",
[?VERSION, node(), (T2-T1)/1000]),
@@ -88,12 +86,6 @@ stop(_State) ->
%%% Internal functions
%%%
connect_nodes() ->
Nodes = ejabberd_config:get_option(cluster_nodes, []),
lists:foreach(fun(Node) ->
net_kernel:connect_node(Node)
end, Nodes).
%% If ejabberd is running on some Windows machine, get nameservers and add to Erlang
maybe_add_nameservers() ->
case os:type() of
@@ -136,10 +128,6 @@ delete_pid_file() ->
file:delete(PidFilename)
end.
set_settings_from_config() ->
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
net_kernel:set_net_ticktime(Ticktime).
file_queue_init() ->
QueueDir = case ejabberd_config:queue_dir() of
undefined ->
@@ -158,16 +146,8 @@ start_apps() ->
ejabberd:start_app(fast_yaml),
ejabberd:start_app(fast_tls),
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab).
-spec opt_type(net_ticktime) -> fun((pos_integer()) -> pos_integer());
(cluster_nodes) -> fun(([node()]) -> [node()]);
(atom()) -> atom().
opt_type(net_ticktime) ->
fun (P) when is_integer(P), P > 0 -> P end;
opt_type(cluster_nodes) ->
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
opt_type(_) -> [cluster_nodes, net_ticktime].
ejabberd:start_app(cache_tab),
start_eimp().
setup_if_elixir_conf_used() ->
case ejabberd_config:is_using_elixir_config() of
@@ -191,3 +171,11 @@ start_elixir_application() ->
_ ->
ok
end.
-ifdef(GRAPHICS).
start_eimp() ->
ejabberd:start_app(eimp).
-else.
start_eimp() ->
ok.
-endif.
+2 -2
View File
@@ -261,12 +261,12 @@ try_register(User, Server, Password) ->
ok;
(Mod, _) ->
db_try_register(
User, Server, Password, Mod)
LUser, LServer, Password, Mod)
end, {error, not_allowed},
auth_modules(LServer)) of
ok ->
ejabberd_hooks:run(
register_user, Server, [User, Server]);
register_user, LServer, [LUser, LServer]);
{error, _} = Err ->
Err
end;
+2
View File
@@ -208,6 +208,8 @@ remove_user(User, Server) ->
{error, db_failure}
end.
need_transform(#reg_users_counter{}) ->
false;
need_transform(#passwd{us = {U, S}, password = Pass}) ->
if is_binary(Pass) ->
case store_type(S) of
+18 -20
View File
@@ -27,9 +27,7 @@
-protocol({xep, 124, '1.11'}).
-protocol({xep, 206, '1.4'}).
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
-behaviour(p1_fsm).
%% API
-export([start/2, start/3, start_link/3]).
@@ -137,18 +135,18 @@ start(#body{attrs = Attrs} = Body, IP, SID) ->
end.
start(StateName, State) ->
(?GEN_FSM):start_link(?MODULE, [StateName, State],
p1_fsm:start_link(?MODULE, [StateName, State],
?FSMOPTS).
start_link(Body, IP, SID) ->
(?GEN_FSM):start_link(?MODULE, [Body, IP, SID],
p1_fsm:start_link(?MODULE, [Body, IP, SID],
?FSMOPTS).
send({http_bind, FsmRef, IP}, Packet) ->
send_xml({http_bind, FsmRef, IP}, Packet).
send_xml({http_bind, FsmRef, _IP}, Packet) ->
case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
case catch p1_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet},
?SEND_TIMEOUT)
of
@@ -160,12 +158,12 @@ send_xml({http_bind, FsmRef, _IP}, Packet) ->
setopts({http_bind, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
true ->
(?GEN_FSM):send_all_state_event(FsmRef,
p1_fsm:send_all_state_event(FsmRef,
{activate, self()});
_ ->
case lists:member({active, false}, Opts) of
true ->
case catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
case catch p1_fsm:sync_send_all_state_event(FsmRef,
deactivate_socket)
of
{'EXIT', _} -> {error, einval};
@@ -181,7 +179,7 @@ custom_receiver({http_bind, FsmRef, _IP}) ->
{receiver, ?MODULE, FsmRef}.
become_controller(FsmRef, C2SPid) ->
(?GEN_FSM):send_all_state_event(FsmRef,
p1_fsm:send_all_state_event(FsmRef,
{become_controller, C2SPid}).
change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
@@ -190,14 +188,14 @@ change_controller({http_bind, FsmRef, _IP}, C2SPid) ->
reset_stream({http_bind, _FsmRef, _IP}) -> ok.
change_shaper({http_bind, FsmRef, _IP}, Shaper) ->
(?GEN_FSM):send_all_state_event(FsmRef,
p1_fsm:send_all_state_event(FsmRef,
{change_shaper, Shaper}).
monitor({http_bind, FsmRef, _IP}) ->
erlang:monitor(process, FsmRef).
close({http_bind, FsmRef, _IP}) ->
catch (?GEN_FSM):sync_send_all_state_event(FsmRef,
catch p1_fsm:sync_send_all_state_event(FsmRef,
close).
sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}.
@@ -269,7 +267,7 @@ process_request(Data, IP, Type) ->
end.
process_request(Pid, Req, _IP, Type) ->
case catch (?GEN_FSM):sync_send_event(Pid, Req,
case catch p1_fsm:sync_send_event(Pid, Req,
infinity)
of
#body{} = Resp -> bosh_response(Resp, Type);
@@ -571,7 +569,7 @@ handle_sync_event({send_xml, El}, _From, StateName,
of
{{value, {TRef, From, Body}}, Q} ->
cancel_timer(TRef),
(?GEN_FSM):send_event(self(), {Body, From}),
p1_fsm:send_event(self(), {Body, From}),
State1#state{shaped_receivers = Q};
_ -> State1
end,
@@ -598,7 +596,7 @@ handle_info({timeout, TRef, shaper_timeout}, StateName,
State) ->
case p1_queue:out(State#state.shaped_receivers) of
{{value, {TRef, From, Req}}, Q} ->
(?GEN_FSM):send_event(self(), {Req, From}),
p1_fsm:send_event(self(), {Req, From}),
{next_state, StateName,
State#state{shaped_receivers = Q}};
{{value, _}, _} ->
@@ -630,7 +628,7 @@ terminate(_Reason, _StateName, State) ->
mod_bosh:close_session(State#state.sid),
case State#state.c2s_pid of
C2SPid when is_pid(C2SPid) ->
(?GEN_FSM):send_event(C2SPid, closed);
p1_fsm:send_event(C2SPid, closed);
_ -> ok
end,
bounce_receivers(State, closed),
@@ -644,7 +642,7 @@ print_state(State) -> State.
route_els(#state{el_ibuf = Buf, c2s_pid = C2SPid} = State) ->
NewBuf = p1_queue:dropwhile(
fun(El) ->
?GEN_FSM:send_event(C2SPid, El),
p1_fsm:send_event(C2SPid, El),
true
end, Buf),
State#state{el_ibuf = NewBuf}.
@@ -653,7 +651,7 @@ route_els(State, Els) ->
case State#state.c2s_pid of
C2SPid when is_pid(C2SPid) ->
lists:foreach(fun (El) ->
(?GEN_FSM):send_event(C2SPid, El)
p1_fsm:send_event(C2SPid, El)
end,
Els),
State;
@@ -676,7 +674,7 @@ reply(State, Body, RID, From) ->
case catch gb_trees:take_smallest(Receivers) of
{NextRID, {From1, Req}, Receivers1}
when NextRID == RID + 1 ->
(?GEN_FSM):send_event(self(), {Req, From1}),
p1_fsm:send_event(self(), {Req, From1}),
State2#state{receivers = Receivers1};
_ -> State2#state{receivers = Receivers}
end.
@@ -715,7 +713,7 @@ do_reply(State, From, Body, RID) ->
?DEBUG("send reply:~n** RequestID: ~p~n** Reply: "
"~p~n** To: ~p~n** State: ~p",
[RID, Body, From, State]),
(?GEN_FSM):reply(From, Body),
p1_fsm:reply(From, Body),
Responses = gb_trees:delete_any(RID,
State#state.responses),
Responses1 = case gb_trees:size(Responses) of
@@ -1053,7 +1051,7 @@ buf_out(Buf, I, Els) ->
end.
cancel_timer(TRef) when is_reference(TRef) ->
(?GEN_FSM):cancel_timer(TRef);
p1_fsm:cancel_timer(TRef);
cancel_timer(_) -> false.
restart_timer(TRef, Timeout, Msg) ->
+28 -12
View File
@@ -46,8 +46,8 @@
reject_unauthenticated_packet/2, process_closed/2,
process_terminated/2, process_info/2]).
%% API
-export([get_presence/1, resend_presence/1, resend_presence/2,
open_session/1, call/3, send/2, close/1, close/2, stop/1,
-export([get_presence/1, set_presence/2, resend_presence/1, resend_presence/2,
open_session/1, call/3, cast/2, send/2, close/1, close/2, stop/1,
reply/2, copy_state/2, set_timeout/2, route/2,
host_up/1, host_down/1]).
@@ -90,6 +90,10 @@ socket_type() ->
call(Ref, Msg, Timeout) ->
xmpp_stream_in:call(Ref, Msg, Timeout).
-spec cast(pid(), term()) -> ok.
cast(Ref, Msg) ->
xmpp_stream_in:cast(Ref, Msg).
reply(Ref, Reply) ->
xmpp_stream_in:reply(Ref, Reply).
@@ -97,6 +101,10 @@ reply(Ref, Reply) ->
get_presence(Ref) ->
call(Ref, get_presence, 1000).
-spec set_presence(pid(), presence()) -> ok.
set_presence(Ref, Pres) ->
call(Ref, {set_presence, Pres}, 1000).
-spec resend_presence(pid()) -> ok.
resend_presence(Pid) ->
resend_presence(Pid, undefined).
@@ -289,14 +297,19 @@ process_terminated(State, _Reason) ->
%%%===================================================================
%%% xmpp_stream_in callbacks
%%%===================================================================
tls_options(#{lserver := LServer, tls_options := DefaultOpts}) ->
TLSOpts1 = case ejabberd_config:get_option(
{c2s_certfile, LServer},
ejabberd_config:get_option(
{domain_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
tls_options(#{lserver := LServer, tls_options := DefaultOpts,
stream_encrypted := Encrypted}) ->
TLSOpts1 = case {Encrypted, proplists:get_value(certfile, DefaultOpts)} of
{true, CertFile} when CertFile /= undefined -> DefaultOpts;
{_, _} ->
case ejabberd_config:get_option(
{domain_certfile, LServer},
ejabberd_config:get_option(
{c2s_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
end
end,
TLSOpts2 = case ejabberd_config:get_option(
{c2s_ciphers, LServer}) of
@@ -398,7 +411,7 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
ejabberd_hooks:run(forbidden_session_hook, LServer, [JID]),
?INFO_MSG("(~s) Forbidden c2s session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]),
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
{error, xmpp:err_not_allowed(Txt, Lang), State}
end
end.
@@ -525,6 +538,9 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
end,
reply(From, Pres),
State;
handle_call({set_presence, Pres}, From, State) ->
reply(From, ok),
process_self_presence(State, Pres);
handle_call(Request, From, #{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(
c2s_handle_call, LServer, State, [Request, From]).
@@ -642,7 +658,7 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
MyBareJID = jid:remove_resource(JID),
case acl:match_rule(LServer, Access, MyBareJID) of
deny ->
ErrText = <<"Denied by ACL">>,
ErrText = <<"Access denied by service policy">>,
Err = xmpp:err_forbidden(ErrText, Lang),
send_error(State, Pres, Err);
allow ->
+11 -16
View File
@@ -388,29 +388,24 @@ get_transfer_protocol(PortString) ->
get_port_listeners(PortNumber) ->
AllListeners = ejabberd_config:get_option(listen, []),
lists:filter(fun (Listener) when is_list(Listener) ->
case proplists:get_value(port, Listener) of
PortNumber -> true;
_ -> false
end;
(_) -> false
end,
AllListeners).
lists:filter(
fun({{Port, _IP, _Transport}, _Module, _Opts}) ->
Port == PortNumber
end, AllListeners).
get_captcha_transfer_protocol([]) ->
throw(<<"The port number mentioned in captcha_host "
"is not a ejabberd_http listener with "
"'captcha' option. Change the port number "
"or specify http:// in that option.">>);
get_captcha_transfer_protocol([Listener | Listeners]) when is_list(Listener) ->
case proplists:get_value(module, Listener) == ejabberd_http andalso
proplists:get_bool(captcha, Listener) of
get_captcha_transfer_protocol([{_, ejabberd_http, Opts} | Listeners]) ->
case proplists:get_bool(captcha, Opts) of
true ->
case proplists:get_bool(tls, Listener) of
true -> https;
false -> http
end;
false -> get_captcha_transfer_protocol(Listeners)
case proplists:get_bool(tls, Opts) of
true -> https;
false -> http
end;
false -> get_captcha_transfer_protocol(Listeners)
end;
get_captcha_transfer_protocol([_ | Listeners]) ->
get_captcha_transfer_protocol(Listeners).
+142 -88
View File
@@ -1,8 +1,6 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_cluster.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Ejabberd clustering management
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 5 Jul 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
@@ -21,132 +19,188 @@
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
%%%-------------------------------------------------------------------
-module(ejabberd_cluster).
-behaviour(ejabberd_config).
-behaviour(gen_server).
%% API
-export([get_nodes/0, call/4, multicall/3, multicall/4,
eval_everywhere/3, eval_everywhere/4]).
-export([join/1, leave/1, get_known_nodes/0]).
-export([node_id/0, get_node_by_id/1]).
-export([start_link/0, call/4, multicall/3, multicall/4, eval_everywhere/3,
eval_everywhere/4]).
%% Backend dependent API
-export([get_nodes/0, get_known_nodes/0, join/1, leave/1, subscribe/0,
subscribe/1, node_id/0, get_node_by_id/1, send/2, wait_for_sync/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-spec get_nodes() -> [node()].
-type dst() :: pid() | atom() | {atom(), node()}.
get_nodes() ->
mnesia:system_info(running_db_nodes).
-callback init() -> ok | {error, any()}.
-callback get_nodes() -> [node()].
-callback get_known_nodes() -> [node()].
-callback join(node()) -> ok | {error, any()}.
-callback leave(node()) -> ok | {error, any()}.
-callback node_id() -> binary().
-callback get_node_by_id(binary()) -> node().
-callback send({atom(), node()}, term()) -> boolean().
-callback wait_for_sync(timeout()) -> ok | {error, any()}.
-callback subscribe(dst()) -> ok.
-spec get_known_nodes() -> [node()].
-record(state, {}).
get_known_nodes() ->
lists:usort(mnesia:system_info(db_nodes)
++ mnesia:system_info(extra_db_nodes)).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec call(node(), module(), atom(), [any()]) -> any().
call(Node, Module, Function, Args) ->
rpc:call(Node, Module, Function, Args, 5000).
rpc:call(Node, Module, Function, Args, rpc_timeout()).
-spec multicall(module(), atom(), [any()]) -> {list(), [node()]}.
multicall(Module, Function, Args) ->
multicall(get_nodes(), Module, Function, Args).
-spec multicall([node()], module(), atom(), list()) -> {list(), [node()]}.
multicall(Nodes, Module, Function, Args) ->
rpc:multicall(Nodes, Module, Function, Args, 5000).
rpc:multicall(Nodes, Module, Function, Args, rpc_timeout()).
-spec eval_everywhere(module(), atom(), [any()]) -> ok.
eval_everywhere(Module, Function, Args) ->
eval_everywhere(get_nodes(), Module, Function, Args),
ok.
-spec eval_everywhere([node()], module(), atom(), [any()]) -> ok.
eval_everywhere(Nodes, Module, Function, Args) ->
rpc:eval_everywhere(Nodes, Module, Function, Args),
ok.
-spec join(node()) -> ok | {error, any()}.
%%%===================================================================
%%% Backend dependent API
%%%===================================================================
-spec get_nodes() -> [node()].
get_nodes() ->
Mod = get_mod(),
Mod:get_nodes().
-spec get_known_nodes() -> [node()].
get_known_nodes() ->
Mod = get_mod(),
Mod:get_known_nodes().
-spec join(node()) -> ok | {error, any()}.
join(Node) ->
case {node(), net_adm:ping(Node)} of
{Node, _} ->
{error, {not_master, Node}};
{_, pong} ->
application:stop(ejabberd),
application:stop(mnesia),
mnesia:delete_schema([node()]),
application:start(mnesia),
mnesia:change_config(extra_db_nodes, [Node]),
mnesia:change_table_copy_type(schema, node(), disc_copies),
spawn(fun() ->
lists:foreach(fun(Table) ->
Type = call(Node, mnesia, table_info, [Table, storage_type]),
mnesia:add_table_copy(Table, node(), Type)
end, mnesia:system_info(tables)--[schema])
end),
application:start(ejabberd);
_ ->
{error, {no_ping, Node}}
end.
Mod = get_mod(),
Mod:join(Node).
-spec leave(node()) -> ok | {error, any()}.
leave(Node) ->
case {node(), net_adm:ping(Node)} of
{Node, _} ->
Cluster = get_nodes()--[Node],
leave(Cluster, Node);
{_, pong} ->
rpc:call(Node, ?MODULE, leave, [Node], 10000);
{_, pang} ->
case mnesia:del_table_copy(schema, Node) of
{atomic, ok} -> ok;
{aborted, Reason} -> {error, Reason}
end
end.
leave([], Node) ->
{error, {no_cluster, Node}};
leave([Master|_], Node) ->
application:stop(ejabberd),
application:stop(mnesia),
call(Master, mnesia, del_table_copy, [schema, Node]),
spawn(fun() ->
mnesia:delete_schema([node()]),
erlang:halt(0)
end),
ok.
Mod = get_mod(),
Mod:leave(Node).
-spec node_id() -> binary().
node_id() ->
integer_to_binary(erlang:phash2(node())).
Mod = get_mod(),
Mod:node_id().
-spec get_node_by_id(binary()) -> node().
get_node_by_id(Hash) ->
try binary_to_integer(Hash) of
I -> match_node_id(I)
catch _:_ ->
node()
get_node_by_id(ID) ->
Mod = get_mod(),
Mod:get_node_by_id(ID).
-spec send(dst(), term()) -> boolean().
send(Dst, Msg) ->
IsLocal = case Dst of
{_, Node} -> Node == node();
Pid when is_pid(Pid) -> node(Pid) == node();
Name when is_atom(Name) -> true;
_ -> false
end,
if IsLocal ->
erlang:send(Dst, Msg),
true;
true ->
Mod = get_mod(),
Mod:send(Dst, Msg)
end.
-spec wait_for_sync(timeout()) -> ok | {error, any()}.
wait_for_sync(Timeout) ->
Mod = get_mod(),
Mod:wait_for_sync(Timeout).
-spec subscribe() -> ok.
subscribe() ->
subscribe(self()).
-spec subscribe(dst()) -> ok.
subscribe(Proc) ->
Mod = get_mod(),
Mod:subscribe(Proc).
%%%===================================================================
%%% gen_server API
%%%===================================================================
init([]) ->
Ticktime = ejabberd_config:get_option(net_ticktime, 60),
Nodes = ejabberd_config:get_option(cluster_nodes, []),
net_kernel:set_net_ticktime(Ticktime),
lists:foreach(fun(Node) ->
net_kernel:connect_node(Node)
end, Nodes),
Mod = get_mod(),
case Mod:init() of
ok ->
Mod:subscribe(?MODULE),
{ok, #state{}};
{error, Reason} ->
{stop, Reason}
end.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({node_up, Node}, State) ->
?INFO_MSG("Node ~s has joined", [Node]),
{noreply, State};
handle_info({node_down, Node}, State) ->
?INFO_MSG("Node ~s has left", [Node]),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec match_node_id(integer()) -> node().
match_node_id(I) ->
match_node_id(I, get_nodes()).
get_mod() ->
Backend = ejabberd_config:get_option(cluster_backend, mnesia),
list_to_atom("ejabberd_cluster_" ++ atom_to_list(Backend)).
-spec match_node_id(integer(), [node()]) -> node().
match_node_id(I, [Node|Nodes]) ->
case erlang:phash2(Node) of
I -> Node;
_ -> match_node_id(I, Nodes)
end;
match_node_id(_I, []) ->
node().
rpc_timeout() ->
timer:seconds(ejabberd_config:get_option(rpc_timeout, 5)).
opt_type(net_ticktime) ->
fun (P) when is_integer(P), P > 0 -> P end;
opt_type(cluster_nodes) ->
fun (Ns) -> true = lists:all(fun is_atom/1, Ns), Ns end;
opt_type(rpc_timeout) ->
fun (T) when is_integer(T), T > 0 -> T end;
opt_type(cluster_backend) ->
fun (T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(_) ->
[rpc_timeout, cluster_backend, cluster_nodes, net_ticktime].
+144
View File
@@ -0,0 +1,144 @@
%%%----------------------------------------------------------------------
%%% File : ejabberd_cluster_mnesia.erl
%%% Author : Christophe Romain <christophe.romain@process-one.net>
%%% Purpose : Ejabberd clustering management via Mnesia
%%% Created : 7 Oct 2015 by Christophe Romain <christophe.romain@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_cluster_mnesia).
-behaviour(ejabberd_cluster).
%% API
-export([init/0, get_nodes/0, join/1, leave/1,
get_known_nodes/0, node_id/0, get_node_by_id/1,
send/2, wait_for_sync/1, subscribe/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-spec init() -> ok.
init() ->
ok.
-spec get_nodes() -> [node()].
get_nodes() ->
mnesia:system_info(running_db_nodes).
-spec get_known_nodes() -> [node()].
get_known_nodes() ->
lists:usort(mnesia:system_info(db_nodes)
++ mnesia:system_info(extra_db_nodes)).
-spec join(node()) -> ok | {error, any()}.
join(Node) ->
case {node(), net_adm:ping(Node)} of
{Node, _} ->
{error, {not_master, Node}};
{_, pong} ->
application:stop(ejabberd),
application:stop(mnesia),
mnesia:delete_schema([node()]),
application:start(mnesia),
mnesia:change_config(extra_db_nodes, [Node]),
mnesia:change_table_copy_type(schema, node(), disc_copies),
spawn(fun() ->
lists:foreach(fun(Table) ->
Type = ejabberd_cluster:call(
Node, mnesia, table_info, [Table, storage_type]),
mnesia:add_table_copy(Table, node(), Type)
end, mnesia:system_info(tables)--[schema])
end),
application:start(ejabberd);
_ ->
{error, {no_ping, Node}}
end.
-spec leave(node()) -> ok | {error, any()}.
leave(Node) ->
case {node(), net_adm:ping(Node)} of
{Node, _} ->
Cluster = get_nodes()--[Node],
leave(Cluster, Node);
{_, pong} ->
rpc:call(Node, ?MODULE, leave, [Node], 10000);
{_, pang} ->
case mnesia:del_table_copy(schema, Node) of
{atomic, ok} -> ok;
{aborted, Reason} -> {error, Reason}
end
end.
leave([], Node) ->
{error, {no_cluster, Node}};
leave([Master|_], Node) ->
application:stop(ejabberd),
application:stop(mnesia),
ejabberd_cluster:call(Master, mnesia, del_table_copy, [schema, Node]),
spawn(fun() ->
mnesia:delete_schema([node()]),
erlang:halt(0)
end),
ok.
-spec node_id() -> binary().
node_id() ->
integer_to_binary(erlang:phash2(node())).
-spec get_node_by_id(binary()) -> node().
get_node_by_id(Hash) ->
try binary_to_integer(Hash) of
I -> match_node_id(I)
catch _:_ ->
node()
end.
-spec send({atom(), node()}, term()) -> boolean().
send(Dst, Msg) ->
erlang:send(Dst, Msg).
-spec wait_for_sync(timeout()) -> ok.
wait_for_sync(Timeout) ->
?INFO_MSG("Waiting for Mnesia synchronization to complete", []),
mnesia:wait_for_tables(mnesia:system_info(local_tables), Timeout),
ok.
-spec subscribe(_) -> ok.
subscribe(_) ->
ok.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec match_node_id(integer()) -> node().
match_node_id(I) ->
match_node_id(I, get_nodes()).
-spec match_node_id(integer(), [node()]) -> node().
match_node_id(I, [Node|Nodes]) ->
case erlang:phash2(Node) of
I -> Node;
_ -> match_node_id(I, Nodes)
end;
match_node_id(_I, []) ->
node().
+2 -273
View File
@@ -221,7 +221,6 @@
get_command_format/1,
get_command_format/2,
get_command_format/3,
get_command_policy_and_scope/1,
get_command_definition/1,
get_command_definition/2,
get_tags_commands/0,
@@ -230,11 +229,6 @@
register_commands/1,
unregister_commands/1,
expose_commands/1,
execute_command/2,
execute_command/3,
execute_command/4,
execute_command/5,
execute_command/6,
opt_type/1,
get_commands_spec/0,
get_commands_definition/0,
@@ -361,6 +355,8 @@ expose_commands(Commands) ->
Commands),
case ejabberd_config:add_option(commands, [{add_commands, Names}]) of
ok ->
ok;
{aborted, Reason} ->
{error, Reason};
{atomic, Result} ->
@@ -427,17 +423,6 @@ get_command_format(Name, Auth, Version) ->
{Args, Result}
end.
-spec get_command_policy_and_scope(atom()) -> {ok, open|user|admin|restricted, [oauth_scope()]} | {error, command_not_found}.
%% @doc return command policy.
get_command_policy_and_scope(Name) ->
case get_command_definition(Name) of
#ejabberd_commands{policy = Policy} = Cmd ->
{ok, Policy, cmd_scope(Cmd)};
command_not_found ->
{error, command_not_found}
end.
%% The oauth scopes for a command are the command name itself,
%% also might include either 'ejabberd:user' or 'ejabberd:admin'
cmd_scope(#ejabberd_commands{policy = Policy, name = Name}) ->
@@ -503,129 +488,6 @@ execute_command2(Name, Arguments, CallerInfo, Version) ->
throw({error, access_rules_unauthorized})
end.
%% @spec (Name::atom(), Arguments) -> ResultTerm
%% where
%% Arguments = [any()]
%% @doc Execute a command.
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data |
%% no_auth_provided | access_rules_unauthorized
execute_command(Name, Arguments) ->
execute_command(Name, Arguments, ?DEFAULT_VERSION).
-spec execute_command(atom(),
[any()],
integer() |
{binary(), binary(), binary(), boolean()} |
noauth | admin
) -> any().
%% @spec (Name::atom(), Arguments, integer() | Auth) -> ResultTerm
%% where
%% Auth = {User::string(), Server::string(), Password::string(),
%% Admin::boolean()}
%% | noauth
%% | admin
%% Arguments = [any()]
%%
%% @doc Execute a command in a given API version
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data |
%% no_auth_provided
execute_command(Name, Arguments, Version) when is_integer(Version) ->
execute_command([], noauth, Name, Arguments, Version);
execute_command(Name, Arguments, Auth) ->
execute_command([], Auth, Name, Arguments, ?DEFAULT_VERSION).
%% @spec (AccessCommands, Auth, Name::atom(), Arguments) ->
%% ResultTerm | {error, Error}
%% where
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
%% | noauth
%% | admin
%% Arguments = [any()]
%%
%% @doc Execute a command
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided
execute_command(AccessCommands, Auth, Name, Arguments) ->
execute_command(AccessCommands, Auth, Name, Arguments, ?DEFAULT_VERSION).
-spec execute_command([{atom(), [atom()], [any()]}] | undefined,
{binary(), binary(), binary(), boolean()} |
noauth | admin,
atom(),
[any()],
integer()
) -> any().
%% @spec (AccessCommands, Auth, Name::atom(), Arguments, integer()) -> ResultTerm
%% where
%% AccessCommands = [{Access, CommandNames, Arguments}] | undefined
%% Auth = {User::string(), Server::string(), Password::string(), Admin::boolean()}
%% | noauth
%% | admin
%% Arguments = [any()]
%%
%% @doc Execute a command in a given API version
%% Can return the following exceptions:
%% command_unknown | account_unprivileged | invalid_account_data | no_auth_provided | access_rules_unauthorized
execute_command(AccessCommands1, Auth1, Name, Arguments, Version) ->
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, #{}).
execute_command(AccessCommands1, Auth1, Name, Arguments, Version, CallerInfo) ->
Auth = case is_admin(Name, Auth1, CallerInfo) of
true -> admin;
false -> Auth1
end,
TokenJID = oauth_token_user(Auth1),
Command = get_command_definition(Name, Version),
AccessCommands = get_all_access_commands(AccessCommands1),
case check_access_commands(AccessCommands, Auth, Name, Command, Arguments, CallerInfo) of
ok -> execute_check_policy(Auth, TokenJID, Command, Arguments)
end.
execute_check_policy(
_Auth, _JID, #ejabberd_commands{policy = open} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
noauth, _JID, Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
_Auth, _JID, #ejabberd_commands{policy = restricted} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_policy(
_Auth, JID, #ejabberd_commands{policy = admin} = Command, Arguments) ->
execute_check_access(JID, Command, Arguments);
execute_check_policy(
admin, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_check_access(JID, Command, Arguments);
execute_check_policy(
{User, Server, _, _}, JID, #ejabberd_commands{policy = user} = Command, Arguments) ->
execute_check_access(JID, Command, [User, Server | Arguments]).
execute_check_access(_FromJID, #ejabberd_commands{access = []} = Command, Arguments) ->
do_execute_command(Command, Arguments);
execute_check_access(undefined, _Command, _Arguments) ->
throw({error, access_rules_unauthorized});
execute_check_access(FromJID, #ejabberd_commands{access = AccessRefs} = Command, Arguments) ->
%% TODO Review: Do we have smarter / better way to check rule on other Host than global ?
Host = global,
Rules = lists:map(
fun({Mod, AccessName, Default}) ->
gen_mod:get_module_opt(Host, Mod, AccessName, Default);
(Default) ->
Default
end, AccessRefs),
case acl:any_rules_allowed(Host, Rules, FromJID) of
true ->
do_execute_command(Command, Arguments);
false ->
throw({error, access_rules_unauthorized})
end.
do_execute_command(Command, Arguments) ->
Module = Command#ejabberd_commands.module,
@@ -672,58 +534,6 @@ get_tags_commands(Version) ->
%% Access verification
%% -----------------------------
%% @spec (AccessCommands, Auth, Method, Command, Arguments) -> ok
%% where
%% AccessCommands = [ {Access, CommandNames, Arguments} ]
%% Auth = {User::string(), Server::string(), Password::string()} | noauth
%% Method = atom()
%% Arguments = [any()]
%% @doc Check access is allowed to that command.
%% At least one AccessCommand must be satisfied.
%% It may throw {error, Error} where:
%% Error = account_unprivileged | invalid_account_data
check_access_commands([], _Auth, _Method, _Command, _Arguments, _CallerInfo) ->
ok;
check_access_commands(AccessCommands, Auth, Method, Command1, Arguments, CallerInfo) ->
Command =
case {Command1#ejabberd_commands.policy, Auth} of
{user, {_, _, _, _}} ->
Command1;
{user, _} ->
Command1#ejabberd_commands{
args = [{user, binary}, {server, binary} |
Command1#ejabberd_commands.args]};
_ ->
Command1
end,
AccessCommandsAllowed =
lists:filter(
fun({Access, Commands, ArgumentRestrictions}) ->
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
end;
({Access, Commands}) ->
ArgumentRestrictions = [],
case check_access(Command, Access, Auth, CallerInfo) of
true ->
check_access_command(Commands, Command,
ArgumentRestrictions,
Method, Arguments);
false ->
false
end
end,
AccessCommands),
case AccessCommandsAllowed of
[] -> throw({error, account_unprivileged});
L when is_list(L) -> ok
end.
-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
(ejabberd_commands(),
{binary(), binary(), binary(), boolean()}) ->
@@ -746,80 +556,6 @@ check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
_ -> throw({error, invalid_account_data})
end.
check_access(Command, ?POLICY_ACCESS, _, _)
when Command#ejabberd_commands.policy == open ->
true;
check_access(_Command, _Access, admin, _) ->
true;
check_access(_Command, _Access, {_User, _Server, _, true}, _) ->
false;
check_access(Command, Access, Auth, CallerInfo)
when Access =/= ?POLICY_ACCESS;
Command#ejabberd_commands.policy == open;
Command#ejabberd_commands.policy == user ->
case check_auth(Command, Auth) of
{ok, User, Server} ->
check_access2(Access, CallerInfo#{usr => jid:split(jid:make(User, Server))}, Server);
no_auth_provided ->
case Command#ejabberd_commands.policy of
user ->
false;
_ ->
check_access2(Access, CallerInfo, global)
end;
_ ->
false
end;
check_access(_Command, _Access, _Auth, _CallerInfo) ->
false.
check_access2(?POLICY_ACCESS, _CallerInfo, _Server) ->
true;
check_access2(Access, AccessInfo, Server) ->
%% Check this user has access permission
case acl:access_matches(Access, AccessInfo, Server) of
allow -> true;
deny -> false
end.
check_access_command(Commands, Command, ArgumentRestrictions,
Method, Arguments) ->
case Commands==all orelse lists:member(Method, Commands) of
true -> check_access_arguments(Command, ArgumentRestrictions,
Arguments);
false -> false
end.
check_access_arguments(Command, ArgumentRestrictions, Arguments) ->
ArgumentsTagged = tag_arguments(Command#ejabberd_commands.args, Arguments),
lists:all(
fun({ArgName, ArgAllowedValue}) ->
%% If the call uses the argument, check the value is acceptable
case lists:keysearch(ArgName, 1, ArgumentsTagged) of
{value, {ArgName, ArgValue}} -> ArgValue == ArgAllowedValue;
false -> true
end
end, ArgumentRestrictions).
tag_arguments(ArgsDefs, Args) ->
lists:zipwith(
fun({ArgName, _ArgType}, ArgValue) ->
{ArgName, ArgValue}
end,
ArgsDefs,
Args).
%% Get commands for all version
get_all_access_commands(AccessCommands) ->
get_access_commands(AccessCommands, ?DEFAULT_VERSION).
get_access_commands(undefined, Version) ->
Cmds = get_exposed_commands(Version),
[{?POLICY_ACCESS, Cmds, []}];
get_access_commands(AccessCommands, _Version) ->
AccessCommands.
get_exposed_commands() ->
get_exposed_commands(?DEFAULT_VERSION).
get_exposed_commands(Version) ->
@@ -854,13 +590,6 @@ expand_commands(L, OpenCmds, UserCmds, AdminCmds, RestrictedCmds) when is_list(L
[Command|Acc]
end, [], L).
oauth_token_user(noauth) ->
undefined;
oauth_token_user(admin) ->
undefined;
oauth_token_user({User, Server, _, _}) ->
jid:make(User, Server).
is_admin(_Name, admin, _Extra) ->
true;
is_admin(_Name, {_User, _Server, _, false}, _Extra) ->
+49 -38
View File
@@ -87,9 +87,6 @@ md_tag(strong, V) ->
md_tag(_, V) ->
V.
unbinarize(binary) -> string;
unbinarize(Other) -> Other.
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
@@ -252,7 +249,7 @@ json_call(Name, ArgsDesc, Values, ResultDesc, Result, HTMLOutput) ->
{200, json_gen(ResultDesc, Result, Indent, HTMLOutput)};
{{Name0, _}, _} ->
{200, [Indent, ?OP_L("{"), ?STR_A(Name0), ?OP_L(": "),
json_gen(ResultDesc, Result, Indent, HTMLOutput), Indent, ?OP_L("}")]}
json_gen(ResultDesc, Result, Indent, HTMLOutput), ?OP_L("}")]}
end,
CodeStr = case Code of
200 -> <<" 200 OK">>;
@@ -340,48 +337,62 @@ gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
end
end.
format_type({list, {_, {tuple, Els}}}) ->
io_lib:format("[~s]", [format_type({tuple, Els})]);
format_type({list, El}) ->
io_lib:format("[~s]", [format_type(El)]);
format_type({tuple, Els}) ->
Args = [format_type(El) || El <- Els],
io_lib:format("{~s}", [string:join(Args, ", ")]);
format_type({Name, Type}) ->
io_lib:format("~s::~s", [Name, format_type(Type)]);
format_type(binary) ->
"string";
format_type(atom) ->
"string";
format_type(Type) ->
io_lib:format("~p", [Type]).
gen_param(Name, Type, undefined, HTMLOutput) ->
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
?RAW(io_lib:format("~p", [unbinarize(Type)]))])];
[?TAG(li, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))])];
gen_param(Name, Type, Desc, HTMLOutput) ->
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>,
?RAW(io_lib:format("~p", [unbinarize(Type)]))]),
[?TAG(dt, [?TAG_R(strong, atom_to_list(Name)), <<" :: ">>, ?RAW(format_type(Type))]),
?TAG(dd, ?RAW(Desc))].
gen_doc(#ejabberd_commands{name=Name, tags=_Tags, desc=Desc, longdesc=LongDesc,
args=Args, args_desc=ArgsDesc,
result=Result, result_desc=ResultDesc}=Cmd, HTMLOutput, Langs) ->
LDesc = case LongDesc of
"" -> Desc;
_ -> LongDesc
end,
ArgsText = case ArgsDesc of
none ->
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|| {AName, Type} <- Args])];
_ ->
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
end,
ResultText = case Result of
{res,rescode} ->
[?TAG(dl, [gen_param(res, integer,
"Status code (0 on success, 1 otherwise)",
HTMLOutput)])];
{res,restuple} ->
[?TAG(dl, [gen_param(res, string,
"Raw result string",
HTMLOutput)])];
{RName, Type} ->
case ResultDesc of
none ->
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
_ ->
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
end
end,
try
LDesc = case LongDesc of
"" -> Desc;
_ -> LongDesc
end,
ArgsText = case ArgsDesc of
none ->
[?TAG(ul, "args-list", [gen_param(AName, Type, undefined, HTMLOutput)
|| {AName, Type} <- Args])];
_ ->
[?TAG(dl, "args-list", [gen_param(AName, Type, ADesc, HTMLOutput)
|| {{AName, Type}, ADesc} <- lists:zip(Args, ArgsDesc)])]
end,
ResultText = case Result of
{res,rescode} ->
[?TAG(dl, [gen_param(res, integer,
"Status code (0 on success, 1 otherwise)",
HTMLOutput)])];
{res,restuple} ->
[?TAG(dl, [gen_param(res, string,
"Raw result string",
HTMLOutput)])];
{RName, Type} ->
case ResultDesc of
none ->
[?TAG(ul, [gen_param(RName, Type, undefined, HTMLOutput)])];
_ ->
[?TAG(dl, [gen_param(RName, Type, ResultDesc, HTMLOutput)])]
end
end,
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
?TAG(p, ?RAW(LDesc)),
?TAG(h2, <<"Arguments:">>), ArgsText,
+3 -2
View File
@@ -28,7 +28,7 @@
-export([start/0, load_file/1, reload_file/0, read_file/1,
get_option/1, get_option/2, add_option/2, has_option/1,
get_vh_by_auth_method/1, is_file_readable/1,
get_vh_by_auth_method/1,
get_version/0, get_myhosts/0, get_mylang/0, get_lang/1,
get_ejabberd_config_path/0, is_using_elixir_config/0,
prepare_opt_val/4, transform_options/1, collect_options/1,
@@ -46,11 +46,12 @@
get_global_option/2, get_local_option/2,
get_global_option/3, get_local_option/3,
get_option/3]).
-export([is_file_readable/1]).
-deprecated([{add_global_option, 2}, {add_local_option, 2},
{get_global_option, 2}, {get_local_option, 2},
{get_global_option, 3}, {get_local_option, 3},
{get_option, 3}]).
{get_option, 3}, {is_file_readable, 1}]).
-include("ejabberd.hrl").
-include("logger.hrl").
+9 -7
View File
@@ -28,7 +28,7 @@
-author('ecestari@process-one.net').
-behaviour(gen_fsm).
-behaviour(p1_fsm).
-export([start/1, start_link/1, init/1, handle_event/3,
handle_sync_event/4, code_change/4, handle_info/3,
@@ -75,13 +75,13 @@
-export_type([ws_socket/0]).
start(WS) ->
gen_fsm:start(?MODULE, [WS], ?FSMOPTS).
p1_fsm:start(?MODULE, [WS], ?FSMOPTS).
start_link(WS) ->
gen_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
p1_fsm:start_link(?MODULE, [WS], ?FSMOPTS).
send_xml({http_ws, FsmRef, _IP}, Packet) ->
case catch gen_fsm:sync_send_all_state_event(FsmRef,
case catch p1_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet},
15000)
of
@@ -93,7 +93,7 @@ send_xml({http_ws, FsmRef, _IP}, Packet) ->
setopts({http_ws, FsmRef, _IP}, Opts) ->
case lists:member({active, once}, Opts) of
true ->
gen_fsm:send_all_state_event(FsmRef,
p1_fsm:send_all_state_event(FsmRef,
{activate, self()});
_ -> ok
end.
@@ -105,11 +105,11 @@ peername({http_ws, _FsmRef, IP}) -> {ok, IP}.
controlling_process(_Socket, _Pid) -> ok.
become_controller(FsmRef, C2SPid) ->
gen_fsm:send_all_state_event(FsmRef,
p1_fsm:send_all_state_event(FsmRef,
{become_controller, C2SPid}).
close({http_ws, FsmRef, _IP}) ->
catch gen_fsm:sync_send_all_state_event(FsmRef, close).
catch p1_fsm:sync_send_all_state_event(FsmRef, close).
socket_handoff(LocalPath, Request, Socket, SockMod, Buf, Opts) ->
ejabberd_websocket:socket_handoff(LocalPath, Request, Socket, SockMod,
@@ -241,6 +241,7 @@ handle_info(PingPong, StateName, StateData) when PingPong == ping orelse
StateData2#state{pong_expected = false}};
handle_info({timeout, Timer, _}, _StateName,
#state{timer = Timer} = StateData) ->
?DEBUG("Closing websocket connection from hitting inactivity timeout", []),
{stop, normal, StateData};
handle_info({timeout, Timer, _}, StateName,
#state{ping_timer = Timer, ws = {_, WsPid}} = StateData) ->
@@ -253,6 +254,7 @@ handle_info({timeout, Timer, _}, StateName,
{next_state, StateName,
StateData#state{ping_timer = PingTimer, pong_expected = true}};
true ->
?DEBUG("Closing websocket connection from missing pongs", []),
{stop, normal, StateData}
end;
handle_info(_, StateName, StateData) ->
+2
View File
@@ -109,6 +109,7 @@ init_udp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
{ok, Socket} ->
%% Inform my parent that this port was opened succesfully
proc_lib:init_ack({ok, self()}),
application:ensure_started(ejabberd),
start_module_sup(Port, Module),
?INFO_MSG("Start accepting UDP connections at ~s for ~p",
[format_portip(PortIP), Module]),
@@ -134,6 +135,7 @@ init_tcp(PortIP, Module, Opts, SockOpts, Port, IPS) ->
ListenSocket = listen_tcp(PortIP, Module, SockOpts, Port, IPS),
%% Inform my parent that this port was opened succesfully
proc_lib:init_ack({ok, self()}),
application:ensure_started(ejabberd),
start_module_sup(Port, Module),
?INFO_MSG("Start accepting TCP connections at ~s for ~p",
[format_portip(PortIP), Module]),
+3
View File
@@ -151,6 +151,9 @@ do_start() ->
application:set_env(lager, crash_log_size, LogRotateSize),
application:set_env(lager, crash_log_count, LogRotateCount),
ejabberd:start_app(lager),
lists:foreach(fun(Handler) ->
lager:set_loghwm(Handler, LogRateLimit)
end, gen_event:which_handlers(lager_event)),
ok.
%% @spec () -> ok
+3 -25
View File
@@ -50,7 +50,7 @@
config_reloaded/0,
opt_type/1]).
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1, oauth_list_scopes/0]).
-export([oauth_issue_token/3, oauth_list_tokens/0, oauth_revoke_token/1]).
-include("xmpp.hrl").
@@ -96,14 +96,6 @@ get_commands_spec() ->
policy = restricted,
result = {tokens, {list, {token, {tuple, [{token, string}, {user, string}, {scope, string}, {expires_in, string}]}}}}
},
#ejabberd_commands{name = oauth_list_scopes, tags = [oauth],
desc = "List scopes that can be granted, and commands",
longdesc = "List scopes that can be granted to tokens generated through the command line, together with the commands they allow",
module = ?MODULE, function = oauth_list_scopes,
args = [],
policy = restricted,
result = {scopes, {list, {scope, {tuple, [{scope, string}, {commands, string}]}}}}
},
#ejabberd_commands{name = oauth_revoke_token, tags = [oauth],
desc = "Revoke authorization for a token (only Mnesia)",
module = ?MODULE, function = oauth_revoke_token,
@@ -143,9 +135,6 @@ oauth_revoke_token(Token) ->
ok = mnesia:dirty_delete(oauth_token, list_to_binary(Token)),
oauth_list_tokens().
oauth_list_scopes() ->
[ {Scope, string:join([atom_to_list(Cmd) || Cmd <- Cmds], ",")} || {Scope, Cmds} <- dict:to_list(get_cmd_scopes())].
config_reloaded() ->
DBMod = get_db_backend(),
case init_cache(DBMod) of
@@ -240,17 +229,6 @@ verify_resowner_scope(_, _, _) ->
{error, badscope}.
get_cmd_scopes() ->
ScopeMap = lists:foldl(fun(Cmd, Accum) ->
case ejabberd_commands:get_command_policy_and_scope(Cmd) of
{ok, Policy, Scopes} when Policy =/= restricted ->
lists:foldl(fun(Scope, Accum2) ->
dict:append(Scope, Cmd, Accum2)
end, Accum, Scopes);
_ -> Accum
end end, dict:new(), ejabberd_commands:get_exposed_commands()),
ScopeMap.
%% This is callback for oauth tokens generated through the command line. Only open and admin commands are
%% made available.
%verify_client_scope({client, ejabberd_ctl}, Scope, Ctx) ->
@@ -458,7 +436,7 @@ process(_Handlers,
?INPUT(<<"hidden">>, <<"scope">>, Scope),
?INPUT(<<"hidden">>, <<"state">>, State),
?BR,
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?CT(<<": ">>)]),
?LABEL(<<"ttl">>, [?CT(<<"Token TTL">>), ?C(<<": ">>)]),
?XAE(<<"select">>, [{<<"name">>, <<"ttl">>}],
[
?XAC(<<"option">>, [{<<"value">>, <<"3600">>}],<<"1 Hour">>),
@@ -755,7 +733,7 @@ css() ->
text-decoration: underline;
}
.container > .section {
.container > .section {
background: #424A55;
}
+2 -2
View File
@@ -234,7 +234,7 @@ terminate(_Reason,
State) ->
close_stream(XMLStreamState),
if C2SPid /= undefined ->
gen_fsm:send_event(C2SPid, closed);
p1_fsm:send_event(C2SPid, closed);
true -> ok
end,
catch (State#state.sock_mod):close(State#state.socket),
@@ -272,7 +272,7 @@ process_data([Element | Els],
element(1, Element) == xmlstreamend ->
if C2SPid == undefined -> State;
true ->
catch gen_fsm:send_event(C2SPid,
catch p1_fsm:send_event(C2SPid,
element_wrapper(Element)),
process_data(Els, State)
end;
+1 -1
View File
@@ -25,7 +25,7 @@
-module(ejabberd_regexp).
-compile([export_all]).
-export([exec/2, run/2, split/2, replace/3, greplace/3, sh_to_awk/1]).
exec({ReM, ReF, ReA}, {RgM, RgF, RgA}) ->
try apply(ReM, ReF, ReA) catch
+1 -1
View File
@@ -149,7 +149,7 @@ init([]) ->
lists:foreach(
fun (Pid) -> erlang:monitor(process, Pid) end,
mnesia:dirty_select(route,
[{{route, '_', '$1', '_'}, [], ['$1']}])),
[{#route{pid = '$1', _ = '_'}, [], ['$1']}])),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
+28 -24
View File
@@ -39,7 +39,7 @@
remove_connection/2, start_connection/2, start_connection/3,
dirty_get_connections/0, allow_host/2,
incoming_s2s_number/0, outgoing_s2s_number/0,
stop_all_connections/0,
stop_s2s_connections/0,
clean_temporarily_blocked_table/0,
list_temporarily_blocked_hosts/0,
external_host_overloaded/1, is_temporarly_blocked/1,
@@ -199,9 +199,9 @@ dirty_get_connections() ->
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case ejabberd_config:get_option(
{s2s_certfile, LServer},
{domain_certfile, LServer},
ejabberd_config:get_option(
{domain_certfile, LServer})) of
{s2s_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
@@ -369,7 +369,7 @@ do_route(Packet) ->
<<"Server connections to local "
"subdomains are forbidden">>, Lang);
forbidden ->
xmpp:err_forbidden(<<"Denied by ACL">>, Lang);
xmpp:err_forbidden(<<"Access denied by service policy">>, Lang);
internal_server_error ->
xmpp:err_internal_server_error()
end,
@@ -546,25 +546,23 @@ parent_domains(Domain) ->
get_commands_spec() ->
[#ejabberd_commands{
name = incoming_s2s_number,
tags = [stats, s2s],
name = incoming_s2s_number, tags = [stats, s2s],
desc = "Number of incoming s2s connections on the node",
policy = admin,
module = ?MODULE, function = incoming_s2s_number,
args = [], result = {s2s_incoming, integer}},
policy = admin,
module = ?MODULE, function = incoming_s2s_number,
args = [], result = {s2s_incoming, integer}},
#ejabberd_commands{
name = outgoing_s2s_number,
tags = [stats, s2s],
name = outgoing_s2s_number, tags = [stats, s2s],
desc = "Number of outgoing s2s connections on the node",
policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}},
#ejabberd_commands{name = stop_all_connections,
tags = [s2s],
desc = "Stop all outgoing and incoming connections",
policy = admin,
module = ?MODULE, function = stop_all_connections,
args = [], result = {res, rescode}}].
policy = admin,
module = ?MODULE, function = outgoing_s2s_number,
args = [], result = {s2s_outgoing, integer}},
#ejabberd_commands{
name = stop_s2s_connections, tags = [s2s],
desc = "Stop all s2s outgoing and incoming connections",
policy = admin,
module = ?MODULE, function = stop_s2s_connections,
args = [], result = {res, rescode}}].
%% TODO Move those stats commands to ejabberd stats command ?
incoming_s2s_number() ->
@@ -580,8 +578,8 @@ supervisor_count(Supervisor) ->
length(Result)
end.
-spec stop_all_connections() -> ok.
stop_all_connections() ->
-spec stop_s2s_connections() -> ok.
stop_s2s_connections() ->
lists:foreach(
fun({_Id, Pid, _Type, _Module}) ->
supervisor:terminate_child(ejabberd_s2s_in_sup, Pid)
@@ -684,7 +682,7 @@ complete_s2s_info([Connection | T], Type, Result) ->
-spec get_s2s_state(pid()) -> [{status, open | closed | error} | {s2s_pid, pid()}].
get_s2s_state(S2sPid) ->
Infos = case gen_fsm:sync_send_all_state_event(S2sPid,
Infos = case p1_fsm:sync_send_all_state_event(S2sPid,
get_state_infos)
of
{state_infos, Is} -> [{status, open} | Is];
@@ -728,7 +726,13 @@ opt_type(s2s_use_starttls) ->
(false) -> false;
(optional) -> optional;
(required) -> required;
(required_trusted) -> required_trusted
(required_trusted) ->
?WARNING_MSG("The value 'required_trusted' of option "
"'s2s_use_starttls' is deprected and will be "
"unsupported in future releases. Instead, "
"set it to 'required' and make sure "
"mod_s2s_dialback is *NOT* loaded", []),
required_trusted
end;
opt_type(s2s_zlib) ->
fun(B) when is_boolean(B) -> B end;
+1 -1
View File
@@ -374,7 +374,7 @@ mk_bounce_error(_Lang, _State) ->
-spec get_delay() -> non_neg_integer().
get_delay() ->
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
crypto:rand_uniform(1, MaxDelay).
randoms:uniform(MaxDelay).
-spec set_idle_timeout(state()) -> state().
set_idle_timeout(#{on_route := send, server := LServer} = State) ->
+1 -1
View File
@@ -199,7 +199,7 @@ handle_info({route, Packet}, #{access := Access} = State) ->
xmpp_stream_in:send(State, Packet);
deny ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_not_allowed(<<"Denied by ACL">>, Lang),
Err = xmpp:err_not_allowed(<<"Access denied by service policy">>, Lang),
ejabberd_router:route_error(Packet, Err),
State
end;
+41 -12
View File
@@ -66,6 +66,8 @@
user_resources/2,
kick_user/2,
get_session_pid/3,
get_session_sid/3,
get_session_sids/2,
get_user_info/2,
get_user_info/3,
get_user_ip/3,
@@ -292,15 +294,32 @@ close_session_unset_presence(SID, User, Server,
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
get_session_pid(User, Server, Resource) ->
case get_session_sid(User, Server, Resource) of
{_, PID} -> PID;
none -> none
end.
-spec get_session_sid(binary(), binary(), binary()) -> none | sid().
get_session_sid(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case online(get_sessions(Mod, LUser, LServer, LResource)) of
[#session{sid = {_, Pid}}] -> Pid;
[#session{sid = SID}] -> SID;
_ -> none
end.
-spec get_session_sids(binary(), binary()) -> [sid()].
get_session_sids(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
Sessions = online(get_sessions(Mod, LUser, LServer)),
[SID || #session{sid = SID} <- Sessions].
-spec set_offline_info(sid(), binary(), binary(), binary(), info()) -> ok.
set_offline_info(SID, User, Server, Resource, Info) ->
@@ -471,9 +490,13 @@ host_up(Host) ->
-spec host_down(binary()) -> ok.
host_down(Host) ->
Mod = get_sm_backend(Host),
Err = case ejabberd_cluster:get_nodes() of
[Node] when Node == node() -> xmpp:serr_system_shutdown();
_ -> xmpp:serr_reset()
end,
lists:foreach(
fun(#session{sid = {_, Pid}}) when node(Pid) == node() ->
ejabberd_c2s:send(Pid, xmpp:serr_system_shutdown()),
ejabberd_c2s:send(Pid, Err),
ejabberd_c2s:stop(Pid);
(_) ->
ok
@@ -957,30 +980,36 @@ cache_nodes(Mod, LServer) ->
%%% ejabberd commands
get_commands_spec() ->
[#ejabberd_commands{name = connected_users,
tags = [session],
[#ejabberd_commands{name = connected_users, tags = [session],
desc = "List all established sessions",
policy = admin,
module = ?MODULE, function = connected_users, args = [],
result_desc = "List of users sessions",
result_example = [<<"user1@example.com">>, <<"user2@example.com">>],
result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number,
tags = [session, stats],
#ejabberd_commands{name = connected_users_number, tags = [session, stats],
desc = "Get the number of established sessions",
policy = admin,
module = ?MODULE, function = connected_users_number,
result_example = 2,
args = [], result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources,
tags = [session],
#ejabberd_commands{name = user_resources, tags = [session],
desc = "List user's connected resources",
policy = user,
policy = admin,
module = ?MODULE, function = user_resources,
args = [],
args = [{user, binary}, {host, binary}],
args_desc = ["User name", "Server name"],
args_example = [<<"user1">>, <<"example.com">>],
result_example = [<<"tka1">>, <<"Gajim">>, <<"mobile-app">>],
result = {resources, {list, {resource, string}}}},
#ejabberd_commands{name = kick_user,
tags = [session],
#ejabberd_commands{name = kick_user, tags = [session],
desc = "Disconnect user's active sessions",
module = ?MODULE, function = kick_user,
args = [{user, binary}, {host, binary}],
args_desc = ["User name", "Server name"],
args_example = [<<"user1">>, <<"example.com">>],
result_desc = "Number of resources that were kicked",
result_example = 3,
result = {num_resources, integer}}].
-spec connected_users() -> [binary()].
+14 -16
View File
@@ -29,9 +29,7 @@
-author('alexey@process-one.net').
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
-behaviour(p1_fsm).
%% External exports
-export([start/1, start_link/2,
@@ -113,11 +111,11 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
(?GEN_FSM):start(ejabberd_sql, [Host],
p1_fsm:start(ejabberd_sql, [Host],
fsm_limit_opts() ++ (?FSMOPTS)).
start_link(Host, StartInterval) ->
(?GEN_FSM):start_link(ejabberd_sql,
p1_fsm:start_link(ejabberd_sql,
[Host, StartInterval],
fsm_limit_opts() ++ (?FSMOPTS)).
@@ -160,7 +158,7 @@ sql_call(Host, Msg) ->
case ejabberd_sql_sup:get_random_pid(Host) of
none -> {error, <<"Unknown Host">>};
Pid ->
(?GEN_FSM):sync_send_event(Pid,{sql_cmd, Msg,
p1_fsm:sync_send_event(Pid,{sql_cmd, Msg,
p1_time_compat:monotonic_time(milli_seconds)},
query_timeout(Host))
end;
@@ -168,7 +166,7 @@ sql_call(Host, Msg) ->
end.
keep_alive(Host, PID) ->
(?GEN_FSM):sync_send_event(PID,
p1_fsm:sync_send_event(PID,
{sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
p1_time_compat:monotonic_time(milli_seconds)},
query_timeout(Host)).
@@ -280,7 +278,7 @@ init([Host, StartInterval]) ->
keep_alive, [Host, self()])
end,
[DBType | _] = db_opts(Host),
(?GEN_FSM):send_event(self(), connect),
p1_fsm:send_event(self(), connect),
ejabberd_sql_sup:add_pid(Host, self()),
QueueType = case ejabberd_config:get_option({sql_queue_type, Host}) of
undefined ->
@@ -313,7 +311,7 @@ connecting(connect, #state{host = Host} = State) ->
PendingRequests =
p1_queue:dropwhile(
fun(Req) ->
?GEN_FSM:send_event(self(), Req),
p1_fsm:send_event(self(), Req),
true
end, State#state.pending_requests),
State1 = State#state{db_ref = Ref,
@@ -325,7 +323,7 @@ connecting(connect, #state{host = Host} = State) ->
"Retry after: ~p seconds",
[State#state.db_type, Reason,
State#state.start_interval div 1000]),
(?GEN_FSM):send_event_after(State#state.start_interval,
p1_fsm:send_event_after(State#state.start_interval,
connect),
{next_state, connecting, State}
end;
@@ -337,7 +335,7 @@ connecting(Event, State) ->
connecting({sql_cmd, {sql_query, ?KEEPALIVE_QUERY},
_Timestamp},
From, State) ->
(?GEN_FSM):reply(From,
p1_fsm:reply(From,
{error, <<"SQL connection failed">>}),
{next_state, connecting, State};
connecting({sql_cmd, Command, Timestamp} = Req, From,
@@ -350,7 +348,7 @@ connecting({sql_cmd, Command, Timestamp} = Req, From,
catch error:full ->
Q = p1_queue:dropwhile(
fun({sql_cmd, _, To, _Timestamp}) ->
(?GEN_FSM):reply(
p1_fsm:reply(
To, {error, <<"SQL connection failed">>}),
true
end, State#state.pending_requests),
@@ -393,7 +391,7 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%% monitoring the connection)
handle_info({'DOWN', _MonitorRef, process, _Pid, _Info},
_StateName, State) ->
(?GEN_FSM):send_event(self(), connect),
p1_fsm:send_event(self(), connect),
{next_state, connecting, State};
handle_info(Info, StateName, State) ->
?WARNING_MSG("unexpected info in ~p: ~p",
@@ -734,16 +732,16 @@ sql_query_to_iolist(SQLQuery) ->
abort_on_driver_error({error, <<"query timed out">>} =
Reply,
From) ->
(?GEN_FSM):reply(From, Reply),
p1_fsm:reply(From, Reply),
{stop, timeout, get(?STATE_KEY)};
abort_on_driver_error({error,
<<"Failed sending data on socket", _/binary>>} =
Reply,
From) ->
(?GEN_FSM):reply(From, Reply),
p1_fsm:reply(From, Reply),
{stop, closed, get(?STATE_KEY)};
abort_on_driver_error(Reply, From) ->
(?GEN_FSM):reply(From, Reply),
p1_fsm:reply(From, Reply),
{next_state, session_established, get(?STATE_KEY)}.
%% == pure ODBC code
+7
View File
@@ -41,6 +41,12 @@ init([]) ->
brutal_kill,
worker,
[ejabberd_hooks]},
Cluster = {ejabberd_cluster,
{ejabberd_cluster, start_link, []},
permanent,
5000,
worker,
[ejabberd_cluster]},
SystemMonitor =
{ejabberd_system_monitor,
{ejabberd_system_monitor, start_link, []},
@@ -152,6 +158,7 @@ init([]) ->
permanent, 5000, worker, [ejabberd_pkix]},
{ok, {{one_for_one, 10, 1},
[Hooks,
Cluster,
CyrSASL,
Translation,
AccessPerms,
+3 -2
View File
@@ -58,6 +58,7 @@ modules() ->
mod_offline,
mod_privacy,
mod_private,
mod_pubsub,
mod_roster,
mod_shared_roster,
mod_vcard].
@@ -80,8 +81,8 @@ export(Server, Output, Module) ->
case export(LServer, Table, IO, ConvertFun) of
{atomic, ok} -> ok;
{aborted, Reason} ->
?ERROR_MSG("Failed export for module ~p: ~p",
[Module, Reason])
?ERROR_MSG("Failed export for module ~p and table ~p: ~p",
[Module, Table, Reason])
end
end, Module:export(Server)),
close_output(Output, IO).
+15 -15
View File
@@ -63,7 +63,7 @@
%%% active_bind - sent bind() request and waiting for response
%%%----------------------------------------------------------------------
-behaviour(gen_fsm).
-behaviour(p1_fsm).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -148,7 +148,7 @@
start_link(Name) ->
Reg_name = misc:binary_to_atom(<<"eldap_",
Name/binary>>),
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
p1_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
-spec start_link(binary(), [binary()], inet:port_number(), binary(),
binary(), tlsopts()) -> any().
@@ -156,7 +156,7 @@ start_link(Name) ->
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
Reg_name = misc:binary_to_atom(<<"eldap_",
Name/binary>>),
gen_fsm:start_link({local, Reg_name}, ?MODULE,
p1_fsm:start_link({local, Reg_name}, ?MODULE,
[Hosts, Port, Rootdn, Passwd, Opts], []).
-spec get_status(handle()) -> any().
@@ -166,7 +166,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
%%% --------------------------------------------------------------------
get_status(Handle) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_all_state_event(Handle1, get_status).
p1_fsm:sync_send_all_state_event(Handle1, get_status).
%%% --------------------------------------------------------------------
%%% Shutdown connection (and process) asynchronous.
@@ -175,7 +175,7 @@ get_status(Handle) ->
close(Handle) ->
Handle1 = get_handle(Handle),
gen_fsm:send_all_state_event(Handle1, close).
p1_fsm:send_all_state_event(Handle1, close).
%%% --------------------------------------------------------------------
%%% Add an entry. The entry field MUST NOT exist for the AddRequest
@@ -192,7 +192,7 @@ close(Handle) ->
%%% --------------------------------------------------------------------
add(Handle, Entry, Attributes) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1,
p1_fsm:sync_send_event(Handle1,
{add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT).
%%% Do sanity check !
@@ -216,7 +216,7 @@ add_attrs(Attrs) ->
%%% --------------------------------------------------------------------
delete(Handle, Entry) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {delete, Entry},
p1_fsm:sync_send_event(Handle1, {delete, Entry},
?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
@@ -234,7 +234,7 @@ delete(Handle, Entry) ->
modify(Handle, Object, Mods) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods},
p1_fsm:sync_send_event(Handle1, {modify, Object, Mods},
?CALL_TIMEOUT).
%%%
@@ -274,7 +274,7 @@ m(Operation, Type, Values) ->
modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1,
p1_fsm:sync_send_event(Handle1,
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN),
optional(NewSup)},
?CALL_TIMEOUT).
@@ -283,7 +283,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) ->
modify_passwd(Handle, DN, Passwd) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1,
p1_fsm:sync_send_event(Handle1,
{modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
%%% --------------------------------------------------------------------
@@ -298,7 +298,7 @@ modify_passwd(Handle, DN, Passwd) ->
bind(Handle, RootDN, Passwd) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd},
?CALL_TIMEOUT).
%%% Sanity checks !
@@ -356,7 +356,7 @@ search(Handle, L) when is_list(L) ->
call_search(Handle, A) ->
Handle1 = get_handle(Handle),
gen_fsm:sync_send_event(Handle1, {search, A},
p1_fsm:sync_send_event(Handle1, {search, A},
?CALL_TIMEOUT).
-spec parse_search_args(search_args()) -> eldap_search().
@@ -637,7 +637,7 @@ active(Event, From, S) ->
%%----------------------------------------------------------------------
%% Func: handle_event/3
%% Called when gen_fsm:send_all_state_event/2 is invoked.
%% Called when p1_fsm:send_all_state_event/2 is invoked.
%% Returns: {next_state, NextStateName, NextStateData} |
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
@@ -680,7 +680,7 @@ handle_info({Tag, _Socket, Data}, StateName, S)
case catch recvd_packet(Data, S) of
{response, Response, RequestType} ->
NewS = case Response of
{reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1;
{reply, Reply, To, S1} -> p1_fsm:reply(To, Reply), S1;
{ok, S1} -> S1
end,
if StateName == active_bind andalso
@@ -709,7 +709,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}},
StateName, S) ->
case cmd_timeout(Timer, Id, S) of
{reply, To, Reason, NewS} ->
gen_fsm:reply(To, Reason),
p1_fsm:reply(To, Reason),
{next_state, StateName, NewS};
{error, _Reason} -> {next_state, StateName, S}
end;
+48 -20
View File
@@ -80,16 +80,18 @@ code_change(_OldVsn, State, _Extra) ->
get_commands_spec() ->
[#ejabberd_commands{name = modules_update_specs,
tags = [admin,modules],
desc = "",
longdesc = "",
desc = "Update the module source code from Git",
longdesc = "A connection to Internet is required",
module = ?MODULE, function = update,
args = [],
result = {res, rescode}},
#ejabberd_commands{name = modules_available,
tags = [admin,modules],
desc = "",
longdesc = "",
desc = "List the contributed modules available to install",
module = ?MODULE, function = available_command,
result_desc = "List of tuples with module name and description",
result_example = [{mod_cron, "Execute scheduled commands"},
{mod_rest, "ReST frontend"}],
args = [],
result = {modules, {list,
{module, {tuple,
@@ -97,9 +99,11 @@ get_commands_spec() ->
{summary, string}]}}}}},
#ejabberd_commands{name = modules_installed,
tags = [admin,modules],
desc = "",
longdesc = "",
desc = "List the contributed modules already installed",
module = ?MODULE, function = installed_command,
result_desc = "List of tuples with module name and description",
result_example = [{mod_cron, "Execute scheduled commands"},
{mod_rest, "ReST frontend"}],
args = [],
result = {modules, {list,
{module, {tuple,
@@ -107,46 +111,64 @@ get_commands_spec() ->
{summary, string}]}}}}},
#ejabberd_commands{name = module_install,
tags = [admin,modules],
desc = "",
longdesc = "",
desc = "Compile, install and start an available contributed module",
module = ?MODULE, function = install,
args_desc = ["Module name"],
args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_uninstall,
tags = [admin,modules],
desc = "",
longdesc = "",
desc = "Uninstall a contributed module",
module = ?MODULE, function = uninstall,
args_desc = ["Module name"],
args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_upgrade,
tags = [admin,modules],
desc = "",
longdesc = "",
desc = "Upgrade the running code of an installed module",
longdesc = "In practice, this uninstalls and installs the module",
module = ?MODULE, function = upgrade,
args_desc = ["Module name"],
args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}},
#ejabberd_commands{name = module_check,
tags = [admin,modules],
desc = "",
longdesc = "",
desc = "Check the contributed module repository compliance",
module = ?MODULE, function = check,
args_desc = ["Module name"],
args_example = [<<"mod_rest">>],
args = [{module, binary}],
result = {res, rescode}}
].
%% -- public modules functions
update() ->
add_sources(?REPOS),
Contrib = maps:put(?REPOS, [], maps:new()),
Jungles = lists:foldl(fun({Package, Spec}, Acc) ->
Repo = proplists:get_value(url, Spec, ""),
Mods = maps:get(Repo, Acc, []),
maps:put(Repo, [Package|Mods], Acc)
end, Contrib, modules_spec(sources_dir(), "*/*")),
Repos = maps:fold(fun(Repo, _Mods, Acc) ->
Update = add_sources(Repo),
?INFO_MSG("Update packages from repo ~s: ~p", [Repo, Update]),
case Update of
ok -> Acc;
Error -> [{repository, Repo, Error}|Acc]
end
end, [], Jungles),
Res = lists:foldl(fun({Package, Spec}, Acc) ->
Path = proplists:get_value(url, Spec, ""),
Update = add_sources(Package, Path),
Repo = proplists:get_value(url, Spec, ""),
Update = add_sources(Package, Repo),
?INFO_MSG("Update package ~s: ~p", [Package, Update]),
case Update of
ok -> Acc;
Error -> [Error|Acc]
Error -> [{Package, Repo, Error}|Acc]
end
end, [], modules_spec(sources_dir(), "*")),
end, Repos, modules_spec(sources_dir(), "*")),
case Res of
[] -> ok;
[Error|_] -> Error
@@ -538,7 +560,9 @@ compile_result(Results) ->
compile_options() ->
[verbose, report_errors, report_warnings]
++ [{i, filename:join(app_dir(App), "include")}
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]].
|| App <- [fast_xml, xmpp, p1_utils, ejabberd]]
++ [{i, filename:join(mod_dir(Mod), "include")}
|| Mod <- installed()].
app_dir(App) ->
case code:lib_dir(App) of
@@ -553,6 +577,10 @@ app_dir(App) ->
Dir
end.
mod_dir({Package, Spec}) ->
Default = filename:join(modules_dir(), Package),
proplists:get_value(path, Spec, Default).
compile_erlang_file(Dest, File) ->
compile_erlang_file(Dest, File, compile_options()).
+16 -1
View File
@@ -34,7 +34,8 @@
stop_child/1, stop_child/2, config_reloaded/0]).
-export([start_module/2, start_module/3,
stop_module/2, stop_module_keep_config/2,
get_opt/2, get_opt/3, get_opt_host/3, opt_type/1, is_equal_opt/4,
get_opt/2, get_opt/3, get_opt_host/3,
get_opt_hosts/3, opt_type/1, is_equal_opt/4,
get_module_opt/3, get_module_opt/4, get_module_opt_host/3,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
@@ -441,6 +442,20 @@ get_opt_host(Host, Opts, Default) ->
Val = get_opt(host, Opts, Default),
ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
-spec get_opt_hosts(binary(), opts(), binary()) -> [binary()].
get_opt_hosts(Host, Opts, Default) ->
Vals = case get_opt(host, Opts, undefined) of
undefined ->
case get_opt(hosts, Opts, []) of
[] -> [Default];
L -> L
end;
Val ->
[Val]
end,
[ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals].
-spec get_validators(binary(), module(), opts()) -> dict:dict() | undef.
get_validators(Host, Module, Opts) ->
try Module:mod_opt_type('') of
+25 -17
View File
@@ -32,8 +32,8 @@
hex_to_bin/1, hex_to_base64/1, expand_keyword/3,
atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1,
l2i/1, i2l/1, i2l/2, expr_to_term/1, term_to_expr/1,
encode_pid/1, decode_pid/2, compile_exprs/2, join_atoms/2,
try_read_file/1]).
now_to_usec/1, usec_to_now/1, encode_pid/1, decode_pid/2,
compile_exprs/2, join_atoms/2, try_read_file/1, have_eimp/0]).
%% Deprecated functions
-export([decode_base64/1, encode_base64/1]).
@@ -127,6 +127,18 @@ expr_to_term(Expr) ->
term_to_expr(Term) ->
list_to_binary(io_lib:print(Term)).
-spec now_to_usec(erlang:timestamp()) -> non_neg_integer().
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
-spec usec_to_now(non_neg_integer()) -> erlang:timestamp().
usec_to_now(Int) ->
Secs = Int div 1000000,
USec = Int rem 1000000,
MSec = Secs div 1000000,
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
l2i(I) when is_integer(I) -> I;
l2i(L) when is_binary(L) -> binary_to_integer(L).
@@ -192,25 +204,21 @@ join_atoms(Atoms, Sep) ->
%% in configuration validators only.
-spec try_read_file(file:filename_all()) -> binary().
try_read_file(Path) ->
Res = case file:read_file_info(Path) of
{ok, #file_info{type = Type, access = Access}} ->
case {Type, Access} of
{regular, read} -> ok;
{regular, read_write} -> ok;
{regular, _} -> {error, file:format_error(eaccess)};
_ -> {error, "not a regular file"}
end;
{error, Why} ->
{error, file:format_error(Why)}
end,
case Res of
ok ->
case file:open(Path, [read]) of
{ok, Fd} ->
file:close(Fd),
iolist_to_binary(Path);
{error, Reason} ->
?ERROR_MSG("Failed to read ~s: ~s", [Path, Reason]),
{error, Why} ->
?ERROR_MSG("Failed to read ~s: ~s", [Path, file:format_error(Why)]),
erlang:error(badarg)
end.
-ifdef(GRAPHICS).
have_eimp() -> true.
-else.
have_eimp() -> false.
-endif.
%%%===================================================================
%%% Internal functions
%%%===================================================================
+132 -20
View File
@@ -44,7 +44,7 @@
kick_session/4, status_num/2, status_num/1,
status_list/2, status_list/1, connected_users_info/0,
connected_users_vhost/1, set_presence/7,
get_presence/2, user_sessions_info/2, get_last/2,
get_presence/2, user_sessions_info/2, get_last/2, set_last/4,
% Accounts
set_password/3, check_password_hash/4, delete_old_users/1,
@@ -303,6 +303,9 @@ get_commands_spec() ->
desc = "List of users logged in host with their statuses",
module = ?MODULE, function = status_list,
args = [{host, binary}, {status, binary}],
args_example = [<<"myserver.com">>, <<"dnd">>],
args_desc = ["Server name", "Status type to check"],
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
result = {users, {list,
{userstatus, {tuple, [
{user, string},
@@ -316,6 +319,9 @@ get_commands_spec() ->
desc = "List of logged users with this status",
module = ?MODULE, function = status_list,
args = [{status, binary}],
args_example = [<<"dnd">>],
args_desc = ["Status type to check"],
result_example = [{<<"peter">>,<<"myserver.com">>,<<"tka">>,6,<<"Busy">>}],
result = {users, {list,
{userstatus, {tuple, [
{user, string},
@@ -330,6 +336,8 @@ get_commands_spec() ->
desc = "List all established sessions and their information",
module = ?MODULE, function = connected_users_info,
args = [],
result_example = [{"user1@myserver.com/tka", "c2s", "127.0.0.1",
40092, 8, "ejabberd@localhost", 28}],
result = {connected_users_info,
{list,
{sessions, {tuple,
@@ -346,6 +354,9 @@ get_commands_spec() ->
tags = [session],
desc = "Get the list of established sessions in a vhost",
module = ?MODULE, function = connected_users_vhost,
args_example = [<<"myexample.com">>],
args_desc = ["Server name"],
result_example = [<<"user1@myserver.com/tka">>, <<"user2@localhost/tka">>],
args = [{host, binary}],
result = {connected_users_vhost, {list, {sessions, string}}}},
#ejabberd_commands{name = user_sessions_info,
@@ -353,6 +364,10 @@ get_commands_spec() ->
desc = "Get information about all sessions of a user",
module = ?MODULE, function = user_sessions_info,
args = [{user, binary}, {host, binary}],
args_example = [<<"peter">>, <<"myserver.com">>],
args_desc = ["User name", "Server name"],
result_example = [{"c2s", "127.0.0.1", 42656,8, "ejabberd@localhost",
231, <<"dnd">>, <<"tka">>, <<>>}],
result = {sessions_info,
{list,
{session, {tuple,
@@ -385,6 +400,9 @@ get_commands_spec() ->
"defined by the user client.",
module = ?MODULE, function = get_presence,
args = [{user, binary}, {server, binary}],
args_example = [<<"peter">>, <<"myexample.com">>],
args_desc = ["User name", "Server name"],
result_example = {<<"user1@myserver.com/tka">>, <<"dnd">>, <<"Busy">>},
result =
{presence,
{tuple,
@@ -398,24 +416,40 @@ get_commands_spec() ->
{resource, binary}, {type, binary},
{show, binary}, {status, binary},
{priority, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"tka1">>,
<<"available">>,<<"away">>,<<"BB">>, <<"7">>],
args_desc = ["User name", "Server name", "Resource",
"Type: available, error, probe...",
"Show: away, chat, dnd, xa.", "Status text",
"Priority, provide this value as an integer"],
result = {res, rescode}},
#ejabberd_commands{name = set_nickname, tags = [vcard],
desc = "Set nickname in a user's vCard",
module = ?MODULE, function = set_nickname,
args = [{user, binary}, {host, binary}, {nickname, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"User 1">>],
args_desc = ["User name", "Server name", "Nickname"],
result = {res, rescode}},
#ejabberd_commands{name = get_vcard, tags = [vcard],
desc = "Get content from a vCard field",
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
module = ?MODULE, function = get_vcard,
args = [{user, binary}, {host, binary}, {name, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"NICKNAME">>],
args_desc = ["User name", "Server name", "Field name"],
result_example = "User 1",
result_desc = "Field content",
result = {content, string}},
#ejabberd_commands{name = get_vcard2, tags = [vcard],
desc = "Get content from a vCard field",
desc = "Get content from a vCard subfield",
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
module = ?MODULE, function = get_vcard,
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"N">>, <<"FAMILY">>],
args_desc = ["User name", "Server name", "Field name", "Subfield name"],
result_example = "Schubert",
result_desc = "Field content",
result = {content, string}},
#ejabberd_commands{name = get_vcard2_multi, tags = [vcard],
desc = "Get multiple contents from a vCard field",
@@ -429,12 +463,16 @@ get_commands_spec() ->
longdesc = Vcard1FieldsString ++ "\n" ++ Vcard2FieldsString ++ "\n\n" ++ VcardXEP,
module = ?MODULE, function = set_vcard,
args = [{user, binary}, {host, binary}, {name, binary}, {content, binary}],
args_example = [<<"user1">>,<<"myserver.com">>, <<"URL">>, <<"www.example.com">>],
args_desc = ["User name", "Server name", "Field name", "Value"],
result = {res, rescode}},
#ejabberd_commands{name = set_vcard2, tags = [vcard],
desc = "Set content in a vCard subfield",
longdesc = Vcard2FieldsString ++ "\n\n" ++ Vcard1FieldsString ++ "\n" ++ VcardXEP,
module = ?MODULE, function = set_vcard,
args = [{user, binary}, {host, binary}, {name, binary}, {subname, binary}, {content, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"TEL">>, <<"NUMBER">>, <<"123456">>],
args_desc = ["User name", "Server name", "Field name", "Subfield name", "Value"],
result = {res, rescode}},
#ejabberd_commands{name = set_vcard2_multi, tags = [vcard],
desc = "Set multiple contents in a vCard subfield",
@@ -451,6 +489,10 @@ get_commands_spec() ->
{user, binary}, {server, binary},
{nick, binary}, {group, binary},
{subs, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>,
<<"User 2">>, <<"Friends">>, <<"both">>],
args_desc = ["User name", "Server name", "Contact user name", "Contact server name",
"Nickname", "Group", "Subscription"],
result = {res, rescode}},
%%{"", "subs= none, from, to or both"},
%%{"", "example: add-roster peter localhost mike server.com MiKe Employees both"},
@@ -460,6 +502,8 @@ get_commands_spec() ->
module = ?MODULE, function = delete_rosteritem,
args = [{localuser, binary}, {localserver, binary},
{user, binary}, {server, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"user2">>, <<"myserver.com">>],
args_desc = ["User name", "Server name", "Contact user name", "Contact server name"],
result = {res, rescode}},
#ejabberd_commands{name = process_rosteritems, tags = [roster],
desc = "List/delete rosteritems that match filter (only Mnesia)",
@@ -514,8 +558,14 @@ get_commands_spec() ->
]}}}}},
#ejabberd_commands{name = push_roster, tags = [roster],
desc = "Push template roster from file to a user",
longdesc = "The text file must contain an erlang term: a list "
"of tuples with username, servername, group and nick. Example:\n"
"[{\"user1\", \"localhost\", \"Workers\", \"User 1\"},\n"
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
module = ?MODULE, function = push_roster,
args = [{file, binary}, {user, binary}, {host, binary}],
args_example = [<<"/home/ejabberd/roster.txt">>, <<"user1">>, <<"localhost">>],
args_desc = ["File path", "User name", "Server name"],
result = {res, rescode}},
#ejabberd_commands{name = push_roster_all, tags = [roster],
desc = "Push template roster from file to all those users",
@@ -525,11 +575,15 @@ get_commands_spec() ->
" {\"user2\", \"localhost\", \"Workers\", \"User 2\"}].",
module = ?MODULE, function = push_roster_all,
args = [{file, binary}],
args_example = [<<"/home/ejabberd/roster.txt">>],
args_desc = ["File path"],
result = {res, rescode}},
#ejabberd_commands{name = push_alltoall, tags = [roster],
desc = "Add all the users to all the users of Host in Group",
module = ?MODULE, function = push_alltoall,
args = [{host, binary}, {group, binary}],
args_example = [<<"myserver.com">>,<<"Everybody">>],
args_desc = ["Server name", "Group name"],
result = {res, rescode}},
#ejabberd_commands{name = get_last, tags = [last],
@@ -538,27 +592,38 @@ get_commands_spec() ->
"2017-02-23T22:25:28.063062Z ONLINE",
module = ?MODULE, function = get_last,
args = [{user, binary}, {host, binary}],
args_example = [<<"user1">>,<<"myserver.com">>],
args_desc = ["User name", "Server name"],
result_example = {<<"2017-06-30T14:32:16.060684Z">>, "ONLINE"},
result_desc = "Last activity timestamp and status",
result = {last_activity,
{tuple, [{timestamp, string},
{status, string}
]}}},
#ejabberd_commands{name = set_last, tags = [last],
desc = "Set last activity information",
longdesc = "Timestamp is the seconds since"
longdesc = "Timestamp is the seconds since "
"1970-01-01 00:00:00 UTC, for example: date +%s",
module = mod_last, function = store_last_info,
module = ?MODULE, function = set_last,
args = [{user, binary}, {host, binary}, {timestamp, integer}, {status, binary}],
args_example = [<<"user1">>,<<"myserver.com">>, 1500045311, <<"GoSleeping">>],
args_desc = ["User name", "Server name", "Number of seconds since epoch", "Status message"],
result = {res, rescode}},
#ejabberd_commands{name = private_get, tags = [private],
desc = "Get some information from a user private storage",
module = ?MODULE, function = private_get,
args = [{user, binary}, {host, binary}, {element, binary}, {ns, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,<<"storage">>, <<"storage:rosternotes">>],
args_desc = ["User name", "Server name", "Element name", "Namespace"],
result = {res, string}},
#ejabberd_commands{name = private_set, tags = [private],
desc = "Set to the user private storage",
module = ?MODULE, function = private_set,
args = [{user, binary}, {host, binary}, {element, binary}],
args_example = [<<"user1">>,<<"myserver.com">>,
<<"<query xmlns='jabber:iq:private'> <storage xmlns='storage:rosternotes'/></query>">>],
args_desc = ["User name", "Server name", "XML storage element"],
result = {res, rescode}},
#ejabberd_commands{name = srg_create, tags = [shared_roster_group],
@@ -568,41 +633,63 @@ get_commands_spec() ->
"put \\ \" around the argument and\nseparate the "
"identifiers with \\ \\ n\n"
"For example:\n"
" ejabberdctl srg_create group3 localhost "
" ejabberdctl srg_create group3 myserver.com "
"name desc \\\"group1\\\\ngroup2\\\"",
module = ?MODULE, function = srg_create,
args = [{group, binary}, {host, binary},
{name, binary}, {description, binary}, {display, binary}],
args_example = [<<"group3">>, <<"myserver.com">>, <<"Group3">>,
<<"Third group">>, <<"group1\\\\ngroup2">>],
args_desc = ["Group identifier", "Group server name", "Group name",
"Group description", "Groups to display"],
result = {res, rescode}},
#ejabberd_commands{name = srg_delete, tags = [shared_roster_group],
desc = "Delete a Shared Roster Group",
module = ?MODULE, function = srg_delete,
args = [{group, binary}, {host, binary}],
args_example = [<<"group3">>, <<"myserver.com">>],
args_desc = ["Group identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = srg_list, tags = [shared_roster_group],
desc = "List the Shared Roster Groups in Host",
module = ?MODULE, function = srg_list,
args = [{host, binary}],
args_example = [<<"myserver.com">>],
args_desc = ["Server name"],
result_example = [<<"group1">>, <<"group2">>],
result_desc = "List of group identifiers",
result = {groups, {list, {id, string}}}},
#ejabberd_commands{name = srg_get_info, tags = [shared_roster_group],
desc = "Get info of a Shared Roster Group",
module = ?MODULE, function = srg_get_info,
args = [{group, binary}, {host, binary}],
args_example = [<<"group3">>, <<"myserver.com">>],
args_desc = ["Group identifier", "Group server name"],
result_example = [{<<"name">>, "Group 3"}, {<<"displayed_groups">>, "group1"}],
result_desc = "List of group informations, as key and value",
result = {informations, {list, {information, {tuple, [{key, string}, {value, string}]}}}}},
#ejabberd_commands{name = srg_get_members, tags = [shared_roster_group],
desc = "Get members of a Shared Roster Group",
module = ?MODULE, function = srg_get_members,
args = [{group, binary}, {host, binary}],
args_example = [<<"group3">>, <<"myserver.com">>],
args_desc = ["Group identifier", "Group server name"],
result_example = [<<"user1@localhost">>, <<"user2@localhost">>],
result_desc = "List of group identifiers",
result = {members, {list, {member, string}}}},
#ejabberd_commands{name = srg_user_add, tags = [shared_roster_group],
desc = "Add the JID user@host to the Shared Roster Group",
module = ?MODULE, function = srg_user_add,
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = srg_user_del, tags = [shared_roster_group],
desc = "Delete this JID user@host from the Shared Roster Group",
module = ?MODULE, function = srg_user_del,
args = [{user, binary}, {host, binary}, {group, binary}, {grouphost, binary}],
args_example = [<<"user1">>, <<"myserver.com">>, <<"group3">>, <<"myserver.com">>],
args_desc = ["Username", "User server name", "Group identifier", "Group server name"],
result = {res, rescode}},
#ejabberd_commands{name = get_offline_count,
@@ -611,27 +698,42 @@ get_commands_spec() ->
policy = user,
module = mod_offline, function = count_offline_messages,
args = [],
result_example = 5,
result_desc = "Number",
result = {value, integer}},
#ejabberd_commands{name = send_message, tags = [stanza],
desc = "Send a message to a local or remote bare of full JID",
module = ?MODULE, function = send_message,
args = [{type, binary}, {from, binary}, {to, binary},
{subject, binary}, {body, binary}],
args_example = [<<"headline">>, <<"admin@localhost">>, <<"user1@localhost">>,
<<"Restart">>, <<"In 5 minutes">>],
args_desc = ["Message type: normal, chat, headline", "Sender JID",
"Receiver JID", "Subject, or empty string", "Body"],
result = {res, rescode}},
#ejabberd_commands{name = send_stanza_c2s, tags = [stanza],
desc = "Send a stanza as if sent from a c2s session",
module = ?MODULE, function = send_stanza_c2s,
args = [{user, binary}, {host, binary}, {resource, binary}, {stanza, binary}],
args_example = [<<"admin">>, <<"myserver.com">>, <<"bot">>,
<<"<message to='user1@localhost'><ext attr='value'/></message>">>],
args_desc = ["Username", "Server name", "Resource", "Stanza"],
result = {res, rescode}},
#ejabberd_commands{name = send_stanza, tags = [stanza],
desc = "Send a stanza; provide From JID and valid To JID",
module = ?MODULE, function = send_stanza,
args = [{from, binary}, {to, binary}, {stanza, binary}],
args_example = [<<"admin@localhost">>, <<"user1@localhost">>,
<<"<message><ext attr='value'/></message>">>],
args_desc = ["Sender JID", "Destination JID", "Stanza"],
result = {res, rescode}},
#ejabberd_commands{name = privacy_set, tags = [stanza],
desc = "Send a IQ set privacy stanza for a local account",
module = ?MODULE, function = privacy_set,
args = [{user, binary}, {host, binary}, {xmlquery, binary}],
args_example = [<<"user1">>, <<"myserver.com">>,
<<"<query xmlns='jabber:iq:privacy'>...">>],
args_desc = ["Username", "Server name", "Query XML element"],
result = {res, rescode}},
#ejabberd_commands{name = stats, tags = [stats],
@@ -639,12 +741,20 @@ get_commands_spec() ->
policy = admin,
module = ?MODULE, function = stats,
args = [{name, binary}],
args_example = [<<"registeredusers">>],
args_desc = ["Statistic name"],
result_example = 6,
result_desc = "Integer statistic value",
result = {stat, integer}},
#ejabberd_commands{name = stats_host, tags = [stats],
desc = "Get statistical value for this host: registeredusers onlineusers",
policy = admin,
module = ?MODULE, function = stats,
args = [{name, binary}, {host, binary}],
args_example = [<<"registeredusers">>, <<"example.com">>],
args_desc = ["Statistic name", "Server JID"],
result_example = 6,
result_desc = "Integer statistic value",
result = {stat, integer}}
].
@@ -952,7 +1062,7 @@ connected_users_info() ->
connected_users_vhost(Host) ->
USRs = ejabberd_sm:get_vh_session_list(Host),
[ [U, $@, S, $/, R] || {U, S, R} <- USRs].
[ jid:encode(jid:make(USR)) || USR <- USRs].
%% Code copied from ejabberd_sm.erl and customized
dirty_get_sessions_list2() ->
@@ -1002,20 +1112,16 @@ set_presence(User, Host, Resource, Type, Show, Status, Priority0) ->
Priority = if is_integer(Priority0) -> Priority0;
true -> binary_to_integer(Priority0)
end,
case ejabberd_sm:get_session_pid(User, Host, Resource) of
none ->
error;
Pid ->
From = jid:make(User, Host, Resource),
To = jid:make(User, Host),
Presence = #presence{from = From, to = To,
type = misc:binary_to_atom(Type),
show = misc:binary_to_atom(Show),
status = xmpp:mk_text(Status),
priority = Priority},
Pid ! {route, Presence},
ok
end.
Pres = #presence{
from = jid:make(User, Host, Resource),
to = jid:make(User, Host),
type = misc:binary_to_atom(Type),
status = xmpp:mk_text(Status),
show = misc:binary_to_atom(Show),
priority = Priority,
sub_els = []},
Ref = ejabberd_sm:get_session_pid(User, Host, Resource),
ejabberd_c2s:set_presence(Ref, Pres).
user_sessions_info(User, Host) ->
CurrentSec = calendar:datetime_to_gregorian_seconds({date(), time()}),
@@ -1331,6 +1437,12 @@ get_last(User, Server) ->
end,
{xmpp_util:encode_timestamp(Now), Status}.
set_last(User, Server, Timestamp, Status) ->
case mod_last:store_last_info(User, Server, Timestamp, Status) of
{ok, _} -> ok;
Error -> Error
end.
%%%
%%% Private Storage
%%%
+14 -6
View File
@@ -237,7 +237,7 @@ disco_identity(Acc, _From, _To, Node, Lang) ->
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
{result, Feats}
end).
@@ -252,7 +252,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, <<"announce">>, Lang) -
case {acl:match_rule(LServer, Access1, From),
acl:match_rule(global, Access2, From)} of
{deny, deny} ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
{error, xmpp:err_forbidden(Txt, Lang)};
_ ->
{result, []}
@@ -303,7 +303,7 @@ disco_features(Acc, From, #jid{lserver = LServer} = _To, Node, Lang) ->
-define(ITEMS_RESULT(Allow, Items, Lang),
case Allow of
deny ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
{result, Items}
end).
@@ -417,7 +417,7 @@ commands_result(Allow, From, To, Request) ->
case Allow of
deny ->
Lang = Request#adhoc_command.lang,
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
announce_commands(From, To, Request)
end.
@@ -843,7 +843,7 @@ add_store_hint(El) ->
-spec route_forbidden_error(stanza()) -> ok.
route_forbidden_error(Packet) ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
ejabberd_router:route_error(Packet, Err).
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
@@ -913,4 +913,12 @@ import(LServer, {sql, _}, DBType, Tab, List) ->
mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(_) -> [access, db_type].
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[access, db_type, cache_life_time, cache_size,
use_cache, cache_missed].
+450
View File
@@ -0,0 +1,450 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 13 Sep 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%-------------------------------------------------------------------
-module(mod_avatar).
-behaviour(gen_mod).
%% gen_mod API
-export([start/2, stop/1, reload/3, depends/2, mod_opt_type/1]).
%% Hooks
-export([pubsub_publish_item/6, vcard_iq_convert/1, vcard_iq_publish/1]).
-include("xmpp.hrl").
-include("logger.hrl").
-include("pubsub.hrl").
-type convert_rules() :: {default | eimp:img_type(), eimp:img_type()}.
%%%===================================================================
%%% API
%%%===================================================================
start(Host, _Opts) ->
case misc:have_eimp() of
true ->
ejabberd_hooks:add(pubsub_publish_item, Host, ?MODULE,
pubsub_publish_item, 50),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
vcard_iq_convert, 30),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE,
vcard_iq_publish, 100);
false ->
?CRITICAL_MSG("ejabberd is built without "
"graphics support: reconfigure it with "
"--enable-graphics or disable '~s'",
[?MODULE]),
{error, graphics_not_compiled}
end.
stop(Host) ->
ejabberd_hooks:delete(pubsub_publish_item, Host, ?MODULE,
pubsub_publish_item, 50),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_convert, 30),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_publish, 100).
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[{mod_vcard, hard}, {mod_vcard_xupdate, hard}, {mod_pubsub, hard}].
%%%===================================================================
%%% Hooks
%%%===================================================================
pubsub_publish_item(LServer, ?NS_AVATAR_METADATA,
#jid{luser = LUser, lserver = LServer} = From,
#jid{luser = LUser, lserver = LServer} = Host,
ItemId, [Payload|_]) ->
try xmpp:decode(Payload) of
#avatar_meta{info = []} ->
delete_vcard_avatar(From);
#avatar_meta{info = Info} ->
Rules = get_converting_rules(LServer),
case get_meta_info(Info, Rules) of
#avatar_info{type = MimeType, id = ID, url = <<"">>} = I ->
case get_avatar_data(Host, ID) of
{ok, Data} ->
Meta = #avatar_meta{info = [I]},
Photo = #vcard_photo{type = MimeType,
binval = Data},
set_vcard_avatar(From, Photo,
#{avatar_meta => {ID, Meta}});
{error, _} ->
ok
end;
#avatar_info{type = MimeType, url = URL} ->
Photo = #vcard_photo{type = MimeType,
extval = URL},
set_vcard_avatar(From, Photo, #{})
end;
_ ->
?WARNING_MSG("invalid avatar metadata of ~s@~s published "
"with item id ~s",
[LUser, LServer, ItemId])
catch _:{xmpp_codec, Why} ->
?WARNING_MSG("failed to decode avatar metadata of ~s@~s: ~s",
[LUser, LServer, xmpp:format_error(Why)])
end;
pubsub_publish_item(_, _, _, _, _, _) ->
ok.
-spec vcard_iq_convert(iq()) -> iq() | {stop, stanza_error()}.
vcard_iq_convert(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
#jid{luser = LUser, lserver = LServer} = From,
case convert_avatar(LUser, LServer, VCard) of
{ok, MimeType, Data} ->
VCard1 = VCard#vcard_temp{
photo = #vcard_photo{type = MimeType,
binval = Data}},
IQ#iq{sub_els = [VCard1]};
pass ->
IQ;
{error, Reason} ->
stop_with_error(Lang, Reason)
end;
vcard_iq_convert(Acc) ->
Acc.
-spec vcard_iq_publish(iq()) -> iq() | {stop, stanza_error()}.
vcard_iq_publish(#iq{sub_els = [#vcard_temp{photo = undefined}]} = IQ) ->
publish_avatar(IQ, #avatar_meta{}, <<>>, <<>>, <<>>);
vcard_iq_publish(#iq{sub_els = [#vcard_temp{
photo = #vcard_photo{
type = MimeType,
binval = Data}}]} = IQ)
when is_binary(Data), Data /= <<>> ->
SHA1 = str:sha(Data),
M = get_avatar_meta(IQ),
case M of
{ok, SHA1, _} ->
IQ;
{ok, _ItemID, #avatar_meta{info = Info} = Meta} ->
case lists:keyfind(SHA1, #avatar_info.id, Info) of
#avatar_info{} ->
IQ;
false ->
Info1 = lists:filter(
fun(#avatar_info{url = URL}) -> URL /= <<"">> end,
Info),
Meta1 = Meta#avatar_meta{info = Info1},
publish_avatar(IQ, Meta1, MimeType, Data, SHA1)
end;
{error, _} ->
publish_avatar(IQ, #avatar_meta{}, MimeType, Data, SHA1)
end;
vcard_iq_publish(Acc) ->
Acc.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec get_meta_info([avatar_info()], convert_rules()) -> avatar_info().
get_meta_info(Info, Rules) ->
case lists:foldl(
fun(_, #avatar_info{} = Acc) ->
Acc;
(#avatar_info{url = URL}, Acc) when URL /= <<"">> ->
Acc;
(#avatar_info{} = I, _) when Rules == [] ->
I;
(#avatar_info{type = MimeType} = I, Acc) ->
T = decode_mime_type(MimeType),
case lists:keymember(T, 2, Rules) of
true ->
I;
false ->
case convert_to_type(T, Rules) of
undefined ->
Acc;
_ ->
[I|Acc]
end
end
end, [], Info) of
#avatar_info{} = I -> I;
[] -> hd(Info);
Is -> hd(lists:reverse(Is))
end.
-spec get_avatar_data(jid(), binary()) -> {ok, binary()} |
{error,
notfound | invalid_data | internal_error}.
get_avatar_data(JID, ItemID) ->
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case mod_pubsub:get_item(LBJID, ?NS_AVATAR_DATA, ItemID) of
#pubsub_item{payload = [Payload|_]} ->
try xmpp:decode(Payload) of
#avatar_data{data = Data} ->
{ok, Data};
_ ->
?WARNING_MSG("invalid avatar data detected "
"for ~s@~s with item id ~s",
[LUser, LServer, ItemID]),
{error, invalid_data}
catch _:{xmpp_codec, Why} ->
?WARNING_MSG("failed to decode avatar data for "
"~s@~s with item id ~s: ~s",
[LUser, LServer, ItemID,
xmpp:format_error(Why)]),
{error, invalid_data}
end;
{error, #stanza_error{reason = 'item-not-found'}} ->
{error, notfound};
{error, Reason} ->
?WARNING_MSG("failed to get item for ~s@~s at node ~s "
"with item id ~s: ~p",
[LUser, LServer, ?NS_AVATAR_METADATA, ItemID, Reason]),
{error, internal_error}
end.
-spec get_avatar_meta(iq()) -> {ok, binary(), avatar_meta()} |
{error,
notfound | invalid_metadata | internal_error}.
get_avatar_meta(#iq{meta = #{avatar_meta := {ItemID, Meta}}}) ->
{ok, ItemID, Meta};
get_avatar_meta(#iq{from = JID}) ->
{LUser, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case mod_pubsub:get_items(LBJID, ?NS_AVATAR_METADATA) of
[#pubsub_item{itemid = {ItemID, _}, payload = [Payload|_]}|_] ->
try xmpp:decode(Payload) of
#avatar_meta{} = Meta ->
{ok, ItemID, Meta};
_ ->
?WARNING_MSG("invalid metadata payload detected "
"for ~s@~s with item id ~s",
[LUser, LServer, ItemID]),
{error, invalid_metadata}
catch _:{xmpp_codec, Why} ->
?WARNING_MSG("failed to decode metadata for "
"~s@~s with item id ~s: ~s",
[LUser, LServer, ItemID,
xmpp:format_error(Why)]),
{error, invalid_metadata}
end;
{error, #stanza_error{reason = 'item-not-found'}} ->
{error, notfound};
{error, Reason} ->
?WARNING_MSG("failed to get items for ~s@~s at node ~s: ~p",
[LUser, LServer, ?NS_AVATAR_METADATA, Reason]),
{error, internal_error}
end.
-spec publish_avatar(iq(), avatar_meta(), binary(), binary(), binary()) ->
iq() | {stop, stanza_error()}.
publish_avatar(#iq{from = JID} = IQ, Meta, <<>>, <<>>, <<>>) ->
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
case mod_pubsub:publish_item(
LBJID, LServer, ?NS_AVATAR_METADATA,
JID, <<>>, [xmpp:encode(Meta)]) of
{result, _} ->
IQ;
{error, StanzaErr} ->
{stop, StanzaErr}
end;
publish_avatar(#iq{from = JID} = IQ, Meta, MimeType, Data, ItemID) ->
#avatar_meta{info = Info} = Meta,
{_, LServer, _} = LBJID = jid:remove_resource(jid:tolower(JID)),
Payload = xmpp:encode(#avatar_data{data = Data}),
case mod_pubsub:publish_item(
LBJID, LServer, ?NS_AVATAR_DATA,
JID, ItemID, [Payload]) of
{result, _} ->
{W, H} = case eimp:identify(Data) of
{ok, ImgInfo} ->
{proplists:get_value(width, ImgInfo),
proplists:get_value(height, ImgInfo)};
_ ->
{undefined, undefined}
end,
I = #avatar_info{id = ItemID,
width = W,
height = H,
type = MimeType,
bytes = size(Data)},
Meta1 = Meta#avatar_meta{info = [I|Info]},
case mod_pubsub:publish_item(
LBJID, LServer, ?NS_AVATAR_METADATA,
JID, ItemID, [xmpp:encode(Meta1)]) of
{result, _} ->
IQ;
{error, StanzaErr} ->
?ERROR_MSG("Failed to publish avatar metadata for ~s: ~p",
[jid:encode(JID), StanzaErr]),
{stop, StanzaErr}
end;
{error, StanzaErr} ->
?ERROR_MSG("Failed to publish avatar data for ~s: ~p",
[jid:encode(JID), StanzaErr]),
{stop, StanzaErr}
end.
-spec convert_avatar(binary(), binary(), vcard_temp()) ->
{ok, binary(), binary()} |
{error, eimp:error_reason() | base64_error} |
pass.
convert_avatar(LUser, LServer, VCard) ->
case get_converting_rules(LServer) of
[] ->
pass;
Rules ->
case VCard#vcard_temp.photo of
#vcard_photo{binval = Data} when is_binary(Data) ->
convert_avatar(LUser, LServer, Data, Rules);
_ ->
pass
end
end.
-spec convert_avatar(binary(), binary(), binary(), convert_rules()) ->
{ok, eimp:img_type(), binary()} |
{error, eimp:error_reason()} |
pass.
convert_avatar(LUser, LServer, Data, Rules) ->
Type = get_type(Data),
NewType = convert_to_type(Type, Rules),
if NewType == undefined orelse Type == NewType ->
pass;
true ->
?DEBUG("Converting avatar of ~s@~s: ~s -> ~s",
[LUser, LServer, Type, NewType]),
case eimp:convert(Data, NewType) of
{ok, NewData} ->
{ok, encode_mime_type(NewType), NewData};
{error, Reason} = Err ->
?ERROR_MSG("Failed to convert avatar of "
"~s@~s (~s -> ~s): ~s",
[LUser, LServer, Type, NewType,
eimp:format_error(Reason)]),
Err
end
end.
-spec set_vcard_avatar(jid(), vcard_photo() | undefined, map()) -> ok.
set_vcard_avatar(JID, VCardPhoto, Meta) ->
case get_vcard(JID) of
{ok, #vcard_temp{photo = VCardPhoto}} ->
ok;
{ok, VCard} ->
VCard1 = VCard#vcard_temp{photo = VCardPhoto},
IQ = #iq{from = JID, to = JID, id = randoms:get_string(),
type = set, sub_els = [VCard1], meta = Meta},
LServer = JID#jid.lserver,
ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []),
ok;
{error, _} ->
ok
end.
-spec delete_vcard_avatar(jid()) -> ok.
delete_vcard_avatar(JID) ->
set_vcard_avatar(JID, undefined, #{}).
-spec get_vcard(jid()) -> {ok, vcard_temp()} | {error, invalid_vcard}.
get_vcard(#jid{luser = LUser, lserver = LServer}) ->
VCardEl = case mod_vcard:get_vcard(LUser, LServer) of
[El] -> El;
_ -> #vcard_temp{}
end,
try xmpp:decode(VCardEl, ?NS_VCARD, []) of
#vcard_temp{} = VCard ->
{ok, VCard};
_ ->
?ERROR_MSG("invalid vCard of ~s@~s in the database",
[LUser, LServer]),
{error, invalid_vcard}
catch _:{xmpp_codec, Why} ->
?ERROR_MSG("failed to decode vCard of ~s@~s: ~s",
[LUser, LServer, xmpp:format_error(Why)]),
{error, invalid_vcard}
end.
-spec stop_with_error(binary(), eimp:error_reason()) ->
{stop, stanza_error()}.
stop_with_error(Lang, Reason) ->
Txt = eimp:format_error(Reason),
{stop, xmpp:err_internal_server_error(Txt, Lang)}.
-spec get_converting_rules(binary()) -> convert_rules().
get_converting_rules(LServer) ->
gen_mod:get_module_opt(LServer, ?MODULE, convert, []).
-spec get_type(binary()) -> eimp:img_type() | unknown.
get_type(Data) ->
eimp:get_type(Data).
-spec convert_to_type(eimp:img_type() | unknown, convert_rules()) ->
eimp:img_type() | undefined.
convert_to_type(unknown, _Rules) ->
undefined;
convert_to_type(Type, Rules) ->
case proplists:get_value(Type, Rules) of
undefined ->
proplists:get_value(default, Rules);
T ->
T
end.
-spec decode_mime_type(binary()) -> eimp:img_type() | unknown.
decode_mime_type(MimeType) ->
case str:to_lower(MimeType) of
<<"image/jpeg">> -> jpeg;
<<"image/png">> -> png;
<<"image/webp">> -> webp;
<<"image/gif">> -> gif;
_ -> unknown
end.
-spec encode_mime_type(eimp:img_type()) -> binary().
encode_mime_type(Type) ->
<<"image/", (atom_to_binary(Type, latin1))/binary>>.
mod_opt_type({convert, png}) ->
fun(jpeg) -> jpeg;
(webp) -> webp;
(gif) -> gif
end;
mod_opt_type({convert, webp}) ->
fun(jpeg) -> jpeg;
(png) -> png;
(gif) -> gif
end;
mod_opt_type({convert, jpeg}) ->
fun(png) -> png;
(webp) -> webp;
(gif) -> gif
end;
mod_opt_type({convert, gif}) ->
fun(png) -> png;
(jpeg) -> jpeg;
(webp) -> webp
end;
mod_opt_type({convert, default}) ->
fun(png) -> png;
(webp) -> webp;
(jpeg) -> jpeg;
(gif) -> gif
end;
mod_opt_type(_) ->
[{convert, default},
{convert, webp},
{convert, png},
{convert, gif},
{convert, jpeg}].
+85 -38
View File
@@ -32,7 +32,7 @@
-export([start/2, stop/1, reload/3,
depends/2, mod_opt_type/1]).
-export([filter_packet/1]).
-export([filter_packet/1, filter_offline_msg/1]).
-include("xmpp.hrl").
-include("ejabberd.hrl").
@@ -43,54 +43,29 @@
start(Host, _Opts) ->
ejabberd_hooks:add(user_receive_packet, Host,
?MODULE, filter_packet, 25),
ok.
ejabberd_hooks:add(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
stop(Host) ->
ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, filter_packet, 25),
ok.
ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
reload(_Host, _NewOpts, _OldOpts) ->
ok.
filter_packet({#message{} = Msg, State} = Acc) ->
From = xmpp:get_from(Msg),
filter_packet({#message{from = From} = Msg, State} = Acc) ->
LFrom = jid:tolower(From),
LBFrom = jid:remove_resource(LFrom),
#{pres_a := PresA, jid := JID, lserver := LServer} = State,
AllowLocalUsers =
gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true),
case (Msg#message.body == [] andalso
Msg#message.subject == [])
orelse (AllowLocalUsers andalso
ejabberd_router:is_my_route(From#jid.lserver))
orelse (?SETS):is_element(LFrom, PresA)
orelse (?SETS):is_element(LBFrom, PresA)
#{pres_a := PresA} = State,
case (?SETS):is_element(LFrom, PresA)
orelse (?SETS):is_element(LBFrom, PresA)
orelse sets_bare_member(LBFrom, PresA) of
false ->
{Sub, _} = ejabberd_hooks:run_fold(
roster_get_jid_info, LServer,
{none, []}, [JID#jid.luser, LServer, From]),
case Sub of
none ->
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
if
Log ->
?INFO_MSG("Drop packet: ~s",
[fxml:element_to_binary(
xmpp:encode(Msg, ?NS_CLIENT))]);
true ->
ok
end,
if
Drop ->
{stop, {drop, State}};
true ->
Acc
end;
_ ->
Acc
case check_message(Msg) of
allow -> Acc;
deny -> {stop, {drop, State}}
end;
true ->
Acc
@@ -98,6 +73,76 @@ filter_packet({#message{} = Msg, State} = Acc) ->
filter_packet(Acc) ->
Acc.
filter_offline_msg({_Action, #message{} = Msg} = Acc) ->
case check_message(Msg) of
allow -> Acc;
deny -> {stop, {drop, Msg}}
end.
check_message(#message{from = From, to = To} = Msg) ->
LServer = To#jid.lserver,
AllowLocalUsers =
gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users, true),
case (Msg#message.body == [] andalso
Msg#message.subject == [])
orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>) andalso
ejabberd_router:is_my_host(From#jid.lserver)) of
false ->
case check_subscription(From, To) of
none ->
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop, true),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log, false),
if
Log ->
?INFO_MSG("~s message from stranger ~s to ~s",
[if Drop -> "Dropping";
true -> "Allow"
end,
jid:encode(From), jid:encode(To)]);
true ->
ok
end,
if
Drop ->
deny;
true ->
allow
end;
some ->
allow
end;
true ->
allow
end.
-spec check_subscription(jid(), jid()) -> none | some.
check_subscription(From, To) ->
{LocalUser, LocalServer, _} = jid:tolower(To),
{RemoteUser, RemoteServer, _} = jid:tolower(From),
case ejabberd_hooks:run_fold(
roster_get_jid_info, LocalServer,
{none, []}, [LocalUser, LocalServer, From]) of
{none, _} when RemoteUser == <<"">> ->
none;
{none, _} ->
case gen_mod:get_module_opt(LocalServer, ?MODULE,
allow_transports, true) of
true ->
%% Check if the contact's server is in the roster
case ejabberd_hooks:run_fold(
roster_get_jid_info, LocalServer,
{none, []},
[LocalUser, LocalServer, jid:make(RemoteServer)]) of
{none, _} -> none;
_ -> some
end;
false ->
none
end;
_ ->
some
end.
sets_bare_member({U, S, <<"">>} = LBJID, Set) ->
case ?SETS:next(sets_iterator_from(LBJID, Set)) of
{{U, S, _}, _} -> true;
@@ -133,4 +178,6 @@ mod_opt_type(log) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(allow_local_users) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) -> [drop, log, allow_local_users].
mod_opt_type(allow_transports) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) -> [drop, log, allow_local_users, allow_transports].
+5 -5
View File
@@ -192,7 +192,7 @@ get_local_identity(Acc, _From, _To, Node, Lang) ->
-define(INFO_RESULT(Allow, Feats, Lang),
case Allow of
deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow -> {result, Feats}
end).
@@ -310,7 +310,7 @@ get_sm_items(Acc, From,
Items ++ Nodes ++ get_user_resources(User, Server)};
{allow, <<"config">>} -> {result, []};
{_, <<"config">>} ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
_ -> Acc
end
end.
@@ -432,7 +432,7 @@ get_local_items(Acc, From, #jid{lserver = LServer} = To,
_ ->
LNode = tokenize(Node),
Allow = acl:match_rule(LServer, configure, From),
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
case LNode of
[<<"config">>] ->
?ITEMS_RESULT(Allow, LNode, {error, Err});
@@ -765,7 +765,7 @@ get_stopped_nodes(_Lang) ->
-define(COMMANDS_RESULT(LServerOrGlobal, From, To,
Request, Lang),
case acl:match_rule(LServerOrGlobal, configure, From) of
deny -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
deny -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow -> adhoc_local_commands(From, To, Request)
end).
@@ -1737,7 +1737,7 @@ adhoc_sm_commands(_Acc, From,
action = Action, xdata = XData} = Request) ->
case acl:match_rule(LServer, configure, From) of
deny ->
{error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
allow ->
ActionIsExecute = Action == execute orelse Action == complete,
if Action == cancel ->
+24 -17
View File
@@ -43,7 +43,7 @@
-include("xmpp.hrl").
-record(state, {host = <<"">> :: binary()}).
-record(state, {hosts = [] :: [binary()]}).
%%====================================================================
%% gen_mod API
@@ -62,7 +62,9 @@ depends(_Host, _Opts) ->
[].
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(_) -> [host].
mod_opt_type(hosts) ->
fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(_) -> [host, hosts].
%%====================================================================
%% gen_server callbacks
@@ -77,10 +79,13 @@ mod_opt_type(_) -> [host].
%%--------------------------------------------------------------------
init([Host, Opts]) ->
process_flag(trap_exit, true),
MyHost = gen_mod:get_opt_host(Host, Opts,
Hosts = gen_mod:get_opt_hosts(Host, Opts,
<<"echo.@HOST@">>),
ejabberd_router:register_route(MyHost, Host),
{ok, #state{host = MyHost}}.
lists:foreach(
fun(H) ->
ejabberd_router:register_route(H, Host)
end, Hosts),
{ok, #state{hosts = Hosts}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@@ -101,17 +106,19 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({reload, Host, NewOpts, OldOpts}, State) ->
NewMyHost = gen_mod:get_opt_host(Host, NewOpts,
<<"echo.@HOST@">>),
OldMyHost = gen_mod:get_opt_host(Host, OldOpts,
<<"echo.@HOST@">>),
if NewMyHost /= OldMyHost ->
ejabberd_router:register_route(NewMyHost, Host),
ejabberd_router:unregister_route(OldMyHost);
true ->
ok
end,
{noreply, State#state{host = NewMyHost}};
NewMyHosts = gen_mod:get_opt_hosts(Host, NewOpts,
<<"echo.@HOST@">>),
OldMyHosts = gen_mod:get_opt_hosts(Host, OldOpts,
<<"echo.@HOST@">>),
lists:foreach(
fun(H) ->
ejabberd_router:unregister_route(H)
end, OldMyHosts -- NewMyHosts),
lists:foreach(
fun(H) ->
ejabberd_router:register_route(H, Host)
end, NewMyHosts -- OldMyHosts),
{noreply, State#state{hosts = NewMyHosts}};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
@@ -147,7 +154,7 @@ handle_info(_Info, State) -> {noreply, State}.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
ejabberd_router:unregister_route(State#state.host), ok.
lists:foreach(fun ejabberd_router:unregister_route/1, State#state.hosts).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+10 -2
View File
@@ -538,9 +538,17 @@ json_error(HTTPCode, JSONCode, Message) ->
log(Call, Args, {Addr, Port}) ->
AddrS = misc:ip_to_list({Addr, Port}),
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]);
?INFO_MSG("API call ~s ~p from ~s:~p", [Call, hide_sensitive_args(Args), AddrS, Port]);
log(Call, Args, IP) ->
?INFO_MSG("API call ~s ~p (~p)", [Call, Args, IP]).
?INFO_MSG("API call ~s ~p (~p)", [Call, hide_sensitive_args(Args), IP]).
hide_sensitive_args(Args=[_H|_T]) ->
lists:map( fun({<<"password">>, Password}) -> {<<"password">>, ejabberd_config:may_hide_data(Password)};
({<<"newpass">>,NewPassword}) -> {<<"newpass">>, ejabberd_config:may_hide_data(NewPassword)};
(E) -> E end,
Args);
hide_sensitive_args(NonListArgs) ->
NonListArgs.
permission_addon() ->
Access = gen_mod:get_module_opt(global, ?MODULE, admin_ip_access, none),
+13 -3
View File
@@ -59,8 +59,13 @@
-define(HTTP_ERR_FILE_NOT_FOUND,
{-1, 404, [], <<"Not found">>}).
-define(REQUEST_AUTH_HEADERS,
[{<<"WWW-Authenticate">>, <<"Basic realm=\"ejabberd\"">>}]).
-define(HTTP_ERR_FORBIDDEN,
{-1, 403, [], <<"Forbidden">>}).
-define(HTTP_ERR_REQUEST_AUTH,
{-1, 401, ?REQUEST_AUTH_HEADERS, <<"Unauthorized">>}).
-define(DEFAULT_CONTENT_TYPE,
<<"application/octet-stream">>).
@@ -317,12 +322,17 @@ serve(LocalPath, Auth, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentT
false
end,
case CanProceed of
false ->
?HTTP_ERR_REQUEST_AUTH;
true ->
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
case file:read_file_info(FileName) of
{error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
{error, enotdir} -> ?HTTP_ERR_FILE_NOT_FOUND;
{error, eacces} -> ?HTTP_ERR_FORBIDDEN;
{error, enoent} ->
?HTTP_ERR_FILE_NOT_FOUND;
{error, enotdir} ->
?HTTP_ERR_FILE_NOT_FOUND;
{error, eacces} ->
?HTTP_ERR_FORBIDDEN;
{ok, #file_info{type = directory}} -> serve_index(FileName,
DirectoryIndices,
CustomHeaders,
+131 -99
View File
@@ -94,7 +94,7 @@
-record(state,
{server_host :: binary(),
host :: binary(),
hosts :: [binary()],
name :: binary(),
access :: atom(),
max_size :: pos_integer() | infinity,
@@ -107,10 +107,11 @@
get_url :: binary(),
service_url :: binary() | undefined,
thumbnail :: boolean(),
custom_headers :: [{binary(), binary()}],
slots = #{} :: map()}).
-record(media_info,
{type :: binary(),
{type :: atom(),
height :: integer(),
width :: integer()}).
@@ -151,6 +152,8 @@ stop(ServerHost) ->
mod_opt_type(host) ->
fun iolist_to_binary/1;
mod_opt_type(hosts) ->
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(name) ->
fun iolist_to_binary/1;
mod_opt_type(access) ->
@@ -194,7 +197,7 @@ mod_opt_type(rm_on_unregister) ->
mod_opt_type(thumbnail) ->
fun(B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[host, name, access, max_size, secret_length, jid_in_url, file_mode,
[host, hosts, name, access, max_size, secret_length, jid_in_url, file_mode,
dir_mode, docroot, put_url, get_url, service_url, custom_headers,
rm_on_unregister, thumbnail].
@@ -211,7 +214,7 @@ depends(_Host, _Opts) ->
init([ServerHost, Opts]) ->
process_flag(trap_exit, true),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"upload.@HOST@">>),
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"upload.@HOST@">>),
Name = gen_mod:get_opt(name, Opts, <<"HTTP File Upload">>),
Access = gen_mod:get_opt(access, Opts, local),
MaxSize = gen_mod:get_opt(max_size, Opts, 104857600),
@@ -224,6 +227,7 @@ init([ServerHost, Opts]) ->
GetURL = gen_mod:get_opt(get_url, Opts, PutURL),
ServiceURL = gen_mod:get_opt(service_url, Opts),
Thumbnail = gen_mod:get_opt(thumbnail, Opts, true),
CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []),
DocRoot1 = expand_home(str:strip(DocRoot, right, $/)),
DocRoot2 = expand_host(DocRoot1, ServerHost),
case DirMode of
@@ -234,18 +238,23 @@ init([ServerHost, Opts]) ->
end,
case Thumbnail of
true ->
case string:str(os:cmd("identify"), "Magick") of
0 ->
?ERROR_MSG("Cannot find 'identify' command, please install "
"ImageMagick or disable thumbnail creation", []);
_ ->
ok
case misc:have_eimp() of
false ->
?ERROR_MSG("ejabberd is built without graphics support, "
"please rebuild it with --enable-graphics or "
"set 'thumbnail: false' for module '~s' in "
"ejabberd.yml", [?MODULE]);
_ ->
ok
end;
false ->
ok
end,
ejabberd_router:register_route(Host, ServerHost),
{ok, #state{server_host = ServerHost, host = Host, name = Name,
lists:foreach(
fun(Host) ->
ejabberd_router:register_route(Host, ServerHost)
end, Hosts),
{ok, #state{server_host = ServerHost, hosts = Hosts, name = Name,
access = Access, max_size = MaxSize,
secret_length = SecretLength, jid_in_url = JIDinURL,
file_mode = FileMode, dir_mode = DirMode,
@@ -253,7 +262,8 @@ init([ServerHost, Opts]) ->
docroot = DocRoot2,
put_url = expand_host(str:strip(PutURL, right, $/), ServerHost),
get_url = expand_host(str:strip(GetURL, right, $/), ServerHost),
service_url = ServiceURL}}.
service_url = ServiceURL,
custom_headers = CustomHeaders}}.
-spec handle_call(_, {pid(), _}, state())
-> {reply, {ok, pos_integer(), binary(),
@@ -261,25 +271,30 @@ init([ServerHost, Opts]) ->
pos_integer() | undefined}, state()} |
{reply, {error, atom()}, state()} | {noreply, state()}.
handle_call({use_slot, Slot, Size}, _From, #state{file_mode = FileMode,
dir_mode = DirMode,
get_url = GetPrefix,
thumbnail = Thumbnail,
docroot = DocRoot} = State) ->
handle_call({use_slot, Slot, Size}, _From,
#state{file_mode = FileMode,
dir_mode = DirMode,
get_url = GetPrefix,
thumbnail = Thumbnail,
custom_headers = CustomHeaders,
docroot = DocRoot} = State) ->
case get_slot(Slot, State) of
{ok, {Size, Timer}} ->
timer:cancel(Timer),
NewState = del_slot(Slot, State),
Path = str:join([DocRoot | Slot], <<$/>>),
{reply, {ok, Path, FileMode, DirMode, GetPrefix, Thumbnail},
{reply,
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders},
NewState};
{ok, {_WrongSize, _Timer}} ->
{reply, {error, size_mismatch}, State};
error ->
{reply, {error, invalid_slot}, State}
end;
handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) ->
{reply, {ok, DocRoot}, State};
handle_call(get_conf, _From,
#state{docroot = DocRoot,
custom_headers = CustomHeaders} = State) ->
{reply, {ok, DocRoot, CustomHeaders}, State};
handle_call(Request, From, State) ->
?ERROR_MSG("Got unexpected request from ~p: ~p", [From, Request]),
{noreply, State}.
@@ -324,10 +339,9 @@ handle_info(Info, State) ->
-spec terminate(normal | shutdown | {shutdown, _} | _, state()) -> ok.
terminate(Reason, #state{server_host = ServerHost, host = Host}) ->
terminate(Reason, #state{server_host = ServerHost, hosts = Hosts}) ->
?DEBUG("Stopping HTTP upload process for ~s: ~p", [ServerHost, Reason]),
ejabberd_router:unregister_route(Host),
ok.
lists:foreach(fun ejabberd_router:unregister_route/1, Hosts).
-spec code_change({down, _} | _, state(), _) -> {ok, state()}.
@@ -349,44 +363,44 @@ process(LocalPath, #request{method = Method, host = Host, ip = IP})
Method == 'HEAD' ->
?DEBUG("Rejecting ~s request from ~s for ~s: Too few path components",
[Method, ?ADDR_TO_STR(IP), Host]),
http_response(Host, 404);
http_response(404);
process(_LocalPath, #request{method = 'PUT', host = Host, ip = IP,
data = Data} = Request) ->
{Proc, Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, {use_slot, Slot, byte_size(Data)}) of
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail} ->
{ok, Path, FileMode, DirMode, GetPrefix, Thumbnail, CustomHeaders} ->
?DEBUG("Storing file from ~s for ~s: ~s",
[?ADDR_TO_STR(IP), Host, Path]),
case store_file(Path, Data, FileMode, DirMode,
GetPrefix, Slot, Thumbnail) of
ok ->
http_response(Host, 201);
http_response(201, CustomHeaders);
{ok, Headers, OutData} ->
http_response(Host, 201, Headers, OutData);
http_response(201, Headers ++ CustomHeaders, OutData);
{error, Error} ->
?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~p",
[Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]),
http_response(Host, 500)
http_response(500)
end;
{error, size_mismatch} ->
?INFO_MSG("Rejecting file from ~s for ~s: Unexpected size (~B)",
[?ADDR_TO_STR(IP), Host, byte_size(Data)]),
http_response(Host, 413);
http_response(413);
{error, invalid_slot} ->
?INFO_MSG("Rejecting file from ~s for ~s: Invalid slot",
[?ADDR_TO_STR(IP), Host]),
http_response(Host, 403);
http_response(403);
Error ->
?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p",
[?ADDR_TO_STR(IP), Host, Error]),
http_response(Host, 500)
http_response(500)
end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
when Method == 'GET';
Method == 'HEAD' ->
{Proc, [_UserDir, _RandDir, FileName] = Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, get_docroot) of
{ok, DocRoot} ->
case catch gen_server:call(Proc, get_conf) of
{ok, DocRoot, CustomHeaders} ->
Path = str:join([DocRoot | Slot], <<$/>>),
case file:read_file(Path) of
{ok, Data} ->
@@ -401,37 +415,47 @@ process(_LocalPath, #request{method = Method, host = Host, ip = IP} = Request)
$", FileName/binary, $">>}]
end,
Headers2 = [{<<"Content-Type">>, ContentType} | Headers1],
http_response(Host, 200, Headers2, Data);
Headers3 = Headers2 ++ CustomHeaders,
http_response(200, Headers3, Data);
{error, eacces} ->
?INFO_MSG("Cannot serve ~s to ~s: Permission denied",
[Path, ?ADDR_TO_STR(IP)]),
http_response(Host, 403);
http_response(403);
{error, enoent} ->
?INFO_MSG("Cannot serve ~s to ~s: No such file",
[Path, ?ADDR_TO_STR(IP)]),
http_response(Host, 404);
http_response(404);
{error, eisdir} ->
?INFO_MSG("Cannot serve ~s to ~s: Is a directory",
[Path, ?ADDR_TO_STR(IP)]),
http_response(Host, 404);
http_response(404);
{error, Error} ->
?INFO_MSG("Cannot serve ~s to ~s: ~s",
[Path, ?ADDR_TO_STR(IP), ?FORMAT(Error)]),
http_response(Host, 500)
http_response(500)
end;
Error ->
?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p",
[Method, ?ADDR_TO_STR(IP), Host, Error]),
http_response(Host, 500)
http_response(500)
end;
process(_LocalPath, #request{method = 'OPTIONS', host = Host, ip = IP}) ->
process(_LocalPath, #request{method = 'OPTIONS', host = Host,
ip = IP} = Request) ->
?DEBUG("Responding to OPTIONS request from ~s for ~s",
[?ADDR_TO_STR(IP), Host]),
http_response(Host, 200);
{Proc, _Slot} = parse_http_request(Request),
case catch gen_server:call(Proc, get_conf) of
{ok, _DocRoot, CustomHeaders} ->
http_response(200, CustomHeaders);
Error ->
?ERROR_MSG("Cannot handle OPTIONS request from ~s for ~s: ~p",
[?ADDR_TO_STR(IP), Host, Error]),
http_response(500)
end;
process(_LocalPath, #request{method = Method, host = Host, ip = IP}) ->
?DEBUG("Rejecting ~s request from ~s for ~s",
[Method, ?ADDR_TO_STR(IP), Host]),
http_response(Host, 405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
http_response(405, [{<<"Allow">>, <<"OPTIONS, HEAD, GET, PUT">>}]).
%%--------------------------------------------------------------------
%% Exported utility functions.
@@ -518,7 +542,7 @@ process_slot_request(#iq{lang = Lang, from = From} = IQ,
deny ->
?DEBUG("Denying HTTP upload slot request from ~s",
[jid:encode(From)]),
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end.
@@ -654,7 +678,7 @@ make_rand_string(S, N) -> make_rand_string([make_rand_char() | S], N - 1).
-spec make_rand_char() -> char().
make_rand_char() ->
map_int_to_char(crypto:rand_uniform(0, 62)).
map_int_to_char(randoms:uniform(0, 61)).
-spec map_int_to_char(0..61) -> char().
@@ -722,15 +746,15 @@ parse_http_request(#request{host = Host, path = Path}) ->
store_file(Path, Data, FileMode, DirMode, GetPrefix, Slot, Thumbnail) ->
case do_store_file(Path, Data, FileMode, DirMode) of
ok when Thumbnail ->
case identify(Path) of
case identify(Path, Data) of
{ok, MediaInfo} ->
case convert(Path, MediaInfo) of
{ok, OutPath} ->
case convert(Path, Data, MediaInfo) of
{ok, OutPath, OutMediaInfo} ->
[UserDir, RandDir | _] = Slot,
FileName = filename:basename(OutPath),
URL = str:join([GetPrefix, UserDir,
RandDir, FileName], <<$/>>),
ThumbEl = thumb_el(OutPath, URL),
ThumbEl = thumb_el(OutMediaInfo, URL),
{ok,
[{<<"Content-Type">>,
<<"text/xml; charset=utf-8">>}],
@@ -786,30 +810,29 @@ guess_content_type(FileName) ->
?DEFAULT_CONTENT_TYPE,
?CONTENT_TYPES).
-spec http_response(binary(), 100..599)
-spec http_response(100..599)
-> {pos_integer(), [{binary(), binary()}], binary()}.
http_response(Host, Code) ->
http_response(Host, Code, []).
http_response(Code) ->
http_response(Code, []).
-spec http_response(binary(), 100..599, [{binary(), binary()}])
-spec http_response(100..599, [{binary(), binary()}])
-> {pos_integer(), [{binary(), binary()}], binary()}.
http_response(Host, Code, ExtraHeaders) ->
http_response(Code, ExtraHeaders) ->
Message = <<(code_to_message(Code))/binary, $\n>>,
http_response(Host, Code, ExtraHeaders, Message).
http_response(Code, ExtraHeaders, Message).
-spec http_response(binary(), 100..599, [{binary(), binary()}], binary())
-spec http_response(100..599, [{binary(), binary()}], binary())
-> {pos_integer(), [{binary(), binary()}], binary()}.
http_response(Host, Code, ExtraHeaders, Body) ->
CustomHeaders = gen_mod:get_module_opt(Host, ?MODULE, custom_headers, []),
http_response(Code, ExtraHeaders, Body) ->
Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of
true ->
ExtraHeaders;
false ->
[{<<"Content-Type">>, <<"text/plain">>} | ExtraHeaders]
end ++ CustomHeaders,
end,
{Code, Headers, Body}.
-spec code_to_message(100..599) -> binary().
@@ -826,59 +849,68 @@ code_to_message(_Code) -> <<"">>.
%% Image manipulation stuff.
%%--------------------------------------------------------------------
-spec identify(binary()) -> {ok, media_info()} | pass.
-spec identify(binary(), binary()) -> {ok, media_info()} | pass.
identify(Path) ->
Cmd = io_lib:format("identify -format 'ok %m %h %w' ~s", [Path]),
Res = string:strip(os:cmd(Cmd), right, $\n),
case string:tokens(Res, " ") of
["ok", T, H, W] ->
{ok, #media_info{type = list_to_binary(string:to_lower(T)),
height = list_to_integer(H),
width = list_to_integer(W)}};
_ ->
?DEBUG("Cannot identify type of ~s: ~s", [Path, Res]),
identify(Path, Data) ->
case misc:have_eimp() of
true ->
case eimp:identify(Data) of
{ok, Info} ->
{ok, #media_info{
type = proplists:get_value(type, Info),
width = proplists:get_value(width, Info),
height = proplists:get_value(height, Info)}};
{error, Why} ->
?DEBUG("Cannot identify type of ~s: ~s",
[Path, eimp:format_error(Why)]),
pass
end;
false ->
pass
end.
-spec convert(binary(), media_info()) -> {ok, binary()} | pass.
-spec convert(binary(), binary(), media_info()) -> {ok, binary(), media_info()} | pass.
convert(Path, #media_info{type = T, width = W, height = H}) ->
convert(Path, Data, #media_info{type = T, width = W, height = H} = Info) ->
if W * H >= 25000000 ->
?DEBUG("The image ~s is more than 25 Mpix", [Path]),
pass;
W =< 300, H =< 300 ->
{ok, Path};
T == <<"gif">>; T == <<"jpeg">>; T == <<"png">>; T == <<"webp">> ->
Dir = filename:dirname(Path),
FileName = <<(randoms:get_string())/binary, $., T/binary>>,
OutPath = filename:join(Dir, FileName),
Cmd = io_lib:format("convert -resize 300 ~s ~s", [Path, OutPath]),
case os:cmd(Cmd) of
"" ->
{ok, OutPath};
Err ->
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
[Path, OutPath, string:strip(Err, right, $\n)]),
pass
end;
{ok, Path, Info};
true ->
?DEBUG("Won't call 'convert' for unknown type ~s", [T]),
pass
Dir = filename:dirname(Path),
Ext = atom_to_binary(T, latin1),
FileName = <<(randoms:get_string())/binary, $., Ext/binary>>,
OutPath = filename:join(Dir, FileName),
{W1, H1} = if W > H -> {300, round(H*300/W)};
H > W -> {round(W*300/H), 300};
true -> {300, 300}
end,
OutInfo = #media_info{type = T, width = W1, height = H1},
case eimp:convert(Data, T, [{scale, {W1, H1}}]) of
{ok, OutData} ->
case file:write_file(OutPath, OutData) of
ok ->
{ok, OutPath, OutInfo};
{error, Why} ->
?ERROR_MSG("Failed to write to ~s: ~s",
[OutPath, file:format_error(Why)]),
pass
end;
{error, Why} ->
?ERROR_MSG("Failed to convert ~s to ~s: ~s",
[Path, OutPath, eimp:format_error(Why)]),
pass
end
end.
-spec thumb_el(binary(), binary()) -> xmlel().
-spec thumb_el(media_info(), binary()) -> xmlel().
thumb_el(Path, URI) ->
ContentType = guess_content_type(Path),
xmpp:encode(
case identify(Path) of
{ok, #media_info{height = H, width = W}} ->
#thumbnail{'media-type' = ContentType, uri = URI,
height = H, width = W};
pass ->
#thumbnail{uri = URI, 'media-type' = ContentType}
end).
thumb_el(#media_info{type = T, height = H, width = W}, URI) ->
MimeType = <<"image/", (atom_to_binary(T, latin1))/binary>>,
Thumb = #thumbnail{'media-type' = MimeType, uri = URI,
height = H, width = W},
xmpp:encode(Thumb).
%%--------------------------------------------------------------------
%% Remove user.
+39 -25
View File
@@ -58,7 +58,7 @@
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]).
-record(state, {host = <<"">> :: binary(),
-record(state, {hosts = [] :: [binary()],
server_host = <<"">> :: binary(),
access = all :: atom()}).
@@ -99,8 +99,7 @@ depends(_Host, _Opts) ->
init([Host, Opts]) ->
process_flag(trap_exit, true),
ejabberd:start_app(iconv),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"irc.@HOST@">>),
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"irc.@HOST@">>),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts, all),
@@ -108,10 +107,13 @@ init([Host, Opts]) ->
[named_table, public,
{keypos, #irc_connection.jid_server_host}]),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
register_hooks(MyHost, IQDisc),
ejabberd_router:register_route(MyHost, Host),
lists:foreach(
fun(MyHost) ->
register_hooks(MyHost, IQDisc),
ejabberd_router:register_route(MyHost, Host)
end, MyHosts),
{ok,
#state{host = MyHost, server_host = Host,
#state{hosts = MyHosts, server_host = Host,
access = Access}}.
%%--------------------------------------------------------------------
@@ -133,8 +135,8 @@ handle_call(stop, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"irc.@HOST@">>),
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"irc.@HOST@">>),
NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"irc.@HOST@">>),
OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"irc.@HOST@">>),
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
@@ -145,20 +147,26 @@ handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
true ->
ok
end,
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
register_hooks(NewHost, NewIQDisc);
true ->
ok
end,
if NewHost /= OldHost ->
ejabberd_router:register_route(NewHost, ServerHost),
ejabberd_router:unregister_route(OldHost),
unregister_hooks(OldHost);
if (NewIQDisc /= OldIQDisc) ->
lists:foreach(
fun(NewHost) ->
register_hooks(NewHost, NewIQDisc)
end, NewHosts -- (NewHosts -- OldHosts));
true ->
ok
end,
lists:foreach(
fun(NewHost) ->
ejabberd_router:register_route(NewHost, ServerHost),
register_hooks(NewHost, NewIQDisc)
end, NewHosts -- OldHosts),
lists:foreach(
fun(OldHost) ->
ejabberd_router:unregister_route(OldHost),
unregister_hooks(OldHost)
end, OldHosts -- NewHosts),
Access = gen_mod:get_opt(access, NewOpts, all),
{noreply, State#state{host = NewHost, access = Access}};
{noreply, State#state{hosts = NewHosts, access = Access}};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
@@ -170,9 +178,10 @@ handle_cast(Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, Packet},
#state{host = Host, server_host = ServerHost,
access = Access} =
#state{server_host = ServerHost, access = Access} =
State) ->
To = xmpp:get_to(Packet),
Host = To#jid.lserver,
case catch do_route(Host, ServerHost, Access, Packet) of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]);
_ -> ok
@@ -187,9 +196,12 @@ handle_info(_Info, State) -> {noreply, State}.
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, #state{host = MyHost}) ->
ejabberd_router:unregister_route(MyHost),
unregister_hooks(MyHost).
terminate(_Reason, #state{hosts = MyHosts}) ->
lists:foreach(
fun(MyHost) ->
ejabberd_router:unregister_route(MyHost),
unregister_hooks(MyHost)
end, MyHosts).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -250,7 +262,7 @@ do_route(Host, ServerHost, Access, Packet) ->
end;
deny ->
Lang = xmpp:get_lang(Packet),
Err = xmpp:err_forbidden(<<"Denied by ACL">>, Lang),
Err = xmpp:err_forbidden(<<"Access denied by service policy">>, Lang),
ejabberd_router:route_error(Packet, Err)
end.
@@ -975,8 +987,10 @@ mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(default_encoding) ->
fun iolist_to_binary/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(hosts) ->
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(_) ->
[access, db_type, default_encoding, host].
[access, db_type, default_encoding, host, hosts].
-spec extract_ident(stanza()) -> binary().
extract_ident(Packet) ->
+5 -5
View File
@@ -27,7 +27,7 @@
-author('alexey@process-one.net').
-behaviour(gen_fsm).
-behaviour(p1_fsm).
%% External exports
-export([start_link/12, start/13, route_chan/4,
@@ -91,7 +91,7 @@ start(From, Host, ServerHost, Server, Username,
start_link(From, Host, Server, Username, Encoding, Port,
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod) ->
gen_fsm:start_link(?MODULE,
p1_fsm:start_link(?MODULE,
[From, Host, Server, Username, Encoding, Port, Password,
Ident, RemoteAddr, RealName, WebircPassword, Mod],
?FSMOPTS).
@@ -109,7 +109,7 @@ start_link(From, Host, Server, Username, Encoding, Port,
%%----------------------------------------------------------------------
init([From, Host, Server, Username, Encoding, Port,
Password, Ident, RemoteAddr, RealName, WebircPassword, Mod]) ->
gen_fsm:send_event(self(), init),
p1_fsm:send_event(self(), init),
{ok, open_socket,
#state{mod = Mod,
encoding = Encoding, port = Port, password = Password,
@@ -628,11 +628,11 @@ handle_info({tcp, _Socket, Data}, StateName,
StateData#state{inbuf = NewBuf}};
handle_info({tcp_closed, _Socket}, StateName,
StateData) ->
gen_fsm:send_event(self(), closed),
p1_fsm:send_event(self(), closed),
{next_state, StateName, StateData};
handle_info({tcp_error, _Socket, _Reason}, StateName,
StateData) ->
gen_fsm:send_event(self(), closed),
p1_fsm:send_event(self(), closed),
{next_state, StateName, StateData}.
%%----------------------------------------------------------------------
+1 -1
View File
@@ -133,7 +133,7 @@ authenticate(#{stream_id := StreamID, server := Server,
Err = xmpp:make_error(IQ, xmpp:err_jid_malformed()),
process_auth_failure(State, U, Err, 'jid-malformed');
false ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
Err = xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang)),
process_auth_failure(State, U, Err, 'forbidden')
end.
+21 -15
View File
@@ -434,8 +434,9 @@ message_is_archived(false, #{jid := JID}, Pkt) ->
delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>;
TypeBin == <<"groupchat">>;
TypeBin == <<"all">> ->
CurrentTime = p1_time_compat:system_time(micro_seconds),
Diff = Days * 24 * 60 * 60 * 1000000,
TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff),
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
Type = misc:binary_to_atom(TypeBin),
DBTypes = lists:usort(
lists:map(
@@ -830,7 +831,7 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
Msgs =
lists:flatmap(
fun({Nick, Pkt, _HaveSubject, Now, _Size}) ->
TS = now_to_usec(Now),
TS = misc:now_to_usec(Now),
case match_interval(Now, Start, End) and
match_rsm(Now, RSM) of
true ->
@@ -862,8 +863,13 @@ select(_LServer, JidRequestor, JidArchive, Query, RSM,
{Msgs, true, L}
end;
select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType).
case might_expose_jid(Query, MsgType) of
true ->
{[], true, 0};
false ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:select(LServer, JidRequestor, JidArchive, Query, RSM, MsgType)
end.
msg_to_el(#archive_msg{timestamp = TS, packet = El, nick = Nick,
peer = Peer, id = ID},
@@ -979,23 +985,20 @@ match_interval(Now, Start, End) ->
(Now >= Start) and (Now =< End).
match_rsm(Now, #rsm_set{'after' = ID}) when is_binary(ID), ID /= <<"">> ->
Now1 = (catch usec_to_now(binary_to_integer(ID))),
Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
Now > Now1;
match_rsm(Now, #rsm_set{before = ID}) when is_binary(ID), ID /= <<"">> ->
Now1 = (catch usec_to_now(binary_to_integer(ID))),
Now1 = (catch misc:usec_to_now(binary_to_integer(ID))),
Now < Now1;
match_rsm(_Now, _) ->
true.
now_to_usec({MSec, Sec, USec}) ->
(MSec*1000000 + Sec)*1000000 + USec.
usec_to_now(Int) ->
Secs = Int div 1000000,
USec = Int rem 1000000,
MSec = Secs div 1000000,
Sec = Secs rem 1000000,
{MSec, Sec, USec}.
might_expose_jid(Query,
{groupchat, Role, #state{config = #config{anonymous = true}}})
when Role /= moderator ->
proplists:is_defined(with, Query);
might_expose_jid(_Query, _MsgType) ->
false.
get_jids(undefined) ->
[];
@@ -1008,6 +1011,9 @@ get_commands_spec() ->
longdesc = "Valid message TYPEs: "
"\"chat\", \"groupchat\", \"all\".",
module = ?MODULE, function = delete_old_messages,
args_desc = ["Type of messages to delete (chat, groupchat, all)",
"Days to keep messages"],
args_example = [<<"all">>, 31],
args = [{type, binary}, {days, integer}],
result = {res, rescode}}].
+55 -48
View File
@@ -46,7 +46,7 @@
?NS_MIX_NODES_CONFIG]).
-record(state, {server_host :: binary(),
host :: binary()}).
hosts :: [binary()]}).
%%%===================================================================
%%% API
@@ -124,36 +124,39 @@ process_iq(#iq{lang = Lang} = IQ) ->
%%%===================================================================
init([ServerHost, Opts]) ->
process_flag(trap_exit, true),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"mix.@HOST@">>),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
ConfigTab = gen_mod:get_module_proc(Host, config),
ets:new(ConfigTab, [named_table]),
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
ejabberd_router:register_route(Host, ServerHost),
{ok, #state{server_host = ServerHost, host = Host}}.
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"mix.@HOST@">>),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
lists:foreach(
fun(Host) ->
ConfigTab = gen_mod:get_module_proc(Host, config),
ets:new(ConfigTab, [named_table]),
ets:insert(ConfigTab, {plugins, [<<"mix">>]}),
ejabberd_hooks:add(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:add(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:add(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:add(disco_info, Host, ?MODULE, disco_info, 100),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_PUBSUB, mod_pubsub, iq_sm, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
?NS_MIX_0, ?MODULE, process_iq, IQDisc),
ejabberd_router:register_route(Host, ServerHost)
end, Hosts),
{ok, #state{server_host = ServerHost, hosts = Hosts}}.
handle_call(_Request, _From, State) ->
Reply = ok,
@@ -180,22 +183,24 @@ handle_info({route, Packet}, State) ->
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #state{host = Host}) ->
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
ejabberd_router:unregister_route(Host),
ok.
terminate(_Reason, #state{hosts = Hosts}) ->
lists:foreach(
fun(Host) ->
ejabberd_hooks:delete(disco_local_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_local_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_local_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:delete(disco_sm_items, Host, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_sm_identity, Host, ?MODULE, disco_identity, 100),
ejabberd_hooks:delete(disco_info, Host, ?MODULE, disco_info, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_MIX_0),
ejabberd_router:unregister_route(Host)
end, Hosts).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@@ -316,4 +321,6 @@ depends(_Host, _Opts) ->
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(_) -> [host, iqdisc].
mod_opt_type(hosts) ->
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(_) -> [host, hosts, iqdisc].
+57 -38
View File
@@ -61,6 +61,7 @@
count_online_rooms/1,
register_online_user/4,
unregister_online_user/4,
iq_set_register_info/5,
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
can_use_nick/4]).
@@ -75,7 +76,7 @@
-include("mod_muc.hrl").
-record(state,
{host = <<"">> :: binary(),
{hosts = [] :: [binary()],
server_host = <<"">> :: binary(),
access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
history_size = 20 :: non_neg_integer(),
@@ -151,8 +152,9 @@ room_destroyed(Host, Room, Pid, ServerHost) ->
%% If Opts = default, the default room options are used.
%% Else use the passed options as defined in mod_muc_room.
create_room(Host, Name, From, Nick, Opts) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
ServerHost = ejabberd_router:host_of_route(Host),
Proc = gen_mod:get_module_proc(ServerHost, ?MODULE),
gen_server:call(Proc, {create, Name, Host, From, Nick, Opts}).
store_room(ServerHost, Host, Name, Opts) ->
LServer = jid:nameprep(ServerHost),
@@ -225,22 +227,26 @@ get_online_rooms_by_user(ServerHost, LUser, LServer) ->
init([Host, Opts]) ->
process_flag(trap_exit, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
#state{access = Access, host = MyHost,
#state{access = Access, hosts = MyHosts,
history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State = init_state(Host, Opts),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
RMod = gen_mod:ram_db_mod(Host, Opts, ?MODULE),
Mod:init(Host, [{host, MyHost}|Opts]),
RMod:init(Host, [{host, MyHost}|Opts]),
register_iq_handlers(MyHost, IQDisc),
ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host, Access, HistorySize, RoomShaper, QueueType),
Mod:init(Host, [{hosts, MyHosts}|Opts]),
RMod:init(Host, [{hosts, MyHosts}|Opts]),
lists:foreach(
fun(MyHost) ->
register_iq_handlers(MyHost, IQDisc),
ejabberd_router:register_route(MyHost, Host),
load_permanent_rooms(MyHost, Host, Access, HistorySize,
RoomShaper, QueueType)
end, MyHosts),
{ok, State}.
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call({create, Room, From, Nick, Opts}, _From,
#state{host = Host, server_host = ServerHost,
handle_call({create, Room, Host, From, Nick, Opts}, _From,
#state{server_host = ServerHost,
access = Access, default_room_opts = DefOpts,
history_size = HistorySize, queue_type = QueueType,
room_shaper = RoomShaper} = State) ->
@@ -256,51 +262,59 @@ handle_call({create, Room, From, Nick, Opts}, _From,
Nick, NewOpts, QueueType),
RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
RMod:register_online_room(ServerHost, Room, Host, Pid),
ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
{reply, ok, State}.
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{host = OldHost}) ->
handle_cast({reload, ServerHost, NewOpts, OldOpts}, #state{hosts = OldHosts}) ->
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
NewMod = gen_mod:db_mod(ServerHost, NewOpts, ?MODULE),
NewRMod = gen_mod:ram_db_mod(ServerHost, NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(ServerHost, OldOpts, ?MODULE),
OldRMod = gen_mod:ram_db_mod(ServerHost, OldOpts, ?MODULE),
#state{host = NewHost} = NewState = init_state(ServerHost, NewOpts),
#state{hosts = NewHosts} = NewState = init_state(ServerHost, NewOpts),
if NewMod /= OldMod ->
NewMod:init(ServerHost, [{host, NewHost}|NewOpts]);
NewMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
true ->
ok
end,
if NewRMod /= OldRMod ->
NewRMod:init(ServerHost, [{host, NewHost}|NewOpts]);
NewRMod:init(ServerHost, [{hosts, NewHosts}|NewOpts]);
true ->
ok
end,
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
register_iq_handlers(NewHost, NewIQDisc);
true ->
ok
end,
if NewHost /= OldHost ->
ejabberd_router:register_route(NewHost, ServerHost),
ejabberd_router:unregister_route(OldHost),
unregister_iq_handlers(OldHost);
if (NewIQDisc /= OldIQDisc) ->
lists:foreach(
fun(NewHost) ->
register_iq_handlers(NewHost, NewIQDisc)
end, NewHosts -- (NewHosts -- OldHosts));
true ->
ok
end,
lists:foreach(
fun(NewHost) ->
ejabberd_router:register_route(NewHost, ServerHost),
register_iq_handlers(NewHost, NewIQDisc)
end, NewHosts -- OldHosts),
lists:foreach(
fun(OldHost) ->
ejabberd_router:unregister_route(OldHost),
unregister_iq_handlers(OldHost)
end, OldHosts -- NewHosts),
{noreply, NewState};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info({route, Packet},
#state{host = Host, server_host = ServerHost,
#state{server_host = ServerHost,
access = Access, default_room_opts = DefRoomOpts,
history_size = HistorySize, queue_type = QueueType,
max_rooms_discoitems = MaxRoomsDiscoItems,
room_shaper = RoomShaper} = State) ->
From = xmpp:get_from(Packet),
To = xmpp:get_to(Packet),
Host = To#jid.lserver,
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
From, To, Packet, DefRoomOpts, MaxRoomsDiscoItems,
QueueType) of
@@ -319,9 +333,12 @@ handle_info(Info, State) ->
?ERROR_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{host = MyHost}) ->
ejabberd_router:unregister_route(MyHost),
unregister_iq_handlers(MyHost).
terminate(_Reason, #state{hosts = MyHosts}) ->
lists:foreach(
fun(MyHost) ->
ejabberd_router:unregister_route(MyHost),
unregister_iq_handlers(MyHost)
end, MyHosts).
code_change(_OldVsn, State, _Extra) -> {ok, State}.
@@ -329,8 +346,8 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%% Internal functions
%%--------------------------------------------------------------------
init_state(Host, Opts) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"conference.@HOST@">>),
MyHosts = gen_mod:get_opt_hosts(Host, Opts,
<<"conference.@HOST@">>),
Access = gen_mod:get_opt(access, Opts, all),
AccessCreate = gen_mod:get_opt(access_create, Opts, all),
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none),
@@ -341,7 +358,7 @@ init_state(Host, Opts) ->
QueueType = gen_mod:get_opt(queue_type, Opts,
ejabberd_config:default_queue_type(Host)),
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none),
#state{host = MyHost,
#state{hosts = MyHosts,
server_host = Host,
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
default_room_opts = DefRoomOpts,
@@ -664,17 +681,17 @@ iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM
-spec get_room_disco_item({binary(), binary(), pid()},
term()) -> {ok, disco_item()} |
{error, timeout | notfound}.
{error, timeout | notfound}.
get_room_disco_item({Name, Host, Pid}, Query) ->
RoomJID = jid:make(Name, Host),
try gen_fsm:sync_send_all_state_event(Pid, Query, 100) of
try p1_fsm:sync_send_all_state_event(Pid, Query, 100) of
{item, Desc} ->
{ok, #disco_item{jid = RoomJID, name = Desc}};
false ->
{error, notfound}
catch _:{timeout, _} ->
catch _:{timeout, {p1_fsm, _, _}} ->
{error, timeout};
_:{noproc, _} ->
_:{_, {p1_fsm, _, _}} ->
{error, notfound}
end.
@@ -683,7 +700,7 @@ get_subscribed_rooms(ServerHost, Host, From) ->
BareFrom = jid:remove_resource(From),
lists:flatmap(
fun({Name, _, Pid}) ->
case gen_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
case p1_fsm:sync_send_all_state_event(Pid, {is_subscribed, BareFrom}) of
true -> [jid:make(Name, Host)];
false -> []
end;
@@ -765,7 +782,7 @@ process_iq_register_set(ServerHost, Host, From,
broadcast_service_message(ServerHost, Host, Msg) ->
lists:foreach(
fun({_, _, Pid}) ->
gen_fsm:send_all_state_event(
p1_fsm:send_all_state_event(
Pid, {service_message, Msg})
end, get_online_rooms(ServerHost, Host)).
@@ -850,6 +867,8 @@ mod_opt_type(ram_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(history_size) ->
fun (I) when is_integer(I), I >= 0 -> I end;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(hosts) ->
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(max_room_desc) ->
fun (infinity) -> infinity;
(I) when is_integer(I), I > 0 -> I
@@ -943,7 +962,7 @@ mod_opt_type({default_room_options, presence_broadcast}) ->
end;
mod_opt_type(_) ->
[access, access_admin, access_create, access_persistent,
db_type, ram_db_type, history_size, host,
db_type, ram_db_type, history_size, host, hosts,
max_room_desc, max_room_id, max_room_name,
max_rooms_discoitems, max_user_conferences, max_users,
max_users_admin_threshold, max_users_presence,
+35 -43
View File
@@ -29,7 +29,7 @@
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, depends/2, muc_online_rooms/1,
muc_register_nick/3, muc_unregister_nick/1,
muc_register_nick/3, muc_unregister_nick/2,
create_room_with_opts/4, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
@@ -91,16 +91,18 @@ get_commands_spec() ->
args = [{host, binary}],
result = {rooms, {list, {room, string}}}},
#ejabberd_commands{name = muc_register_nick, tags = [muc],
desc = "Register a nick in the MUC service",
desc = "Register a nick to a User JID in the MUC service of a server",
module = ?MODULE, function = muc_register_nick,
args_desc = ["Nick", "User JID", "MUC service"],
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"muc.example.org">>],
args = [{nick, binary}, {jid, binary}, {domain, binary}],
args_desc = ["Nick", "User JID", "Server Host"],
args_example = [<<"Tim">>, <<"tim@example.org">>, <<"example.org">>],
args = [{nick, binary}, {jid, binary}, {serverhost, binary}],
result = {res, rescode}},
#ejabberd_commands{name = muc_unregister_nick, tags = [muc],
desc = "Unregister the nick in the MUC service",
desc = "Unregister the nick registered by that account in the MUC service",
module = ?MODULE, function = muc_unregister_nick,
args = [{nick, binary}],
args_desc = ["User JID", "MUC service"],
args_example = [<<"tim@example.org">>, <<"example.org">>],
args = [{jid, binary}, {serverhost, binary}],
result = {res, rescode}},
#ejabberd_commands{name = create_room, tags = [muc_room],
@@ -305,31 +307,14 @@ muc_online_rooms(ServerHost) ->
|| {Name, _, _} <- mod_muc:get_online_rooms(Host)]
end, Hosts).
muc_register_nick(Nick, JIDBinary, Domain) ->
try jid:decode(JIDBinary) of
JID ->
F = fun (MHost, MNick) ->
mnesia:write(#muc_registered{us_host=MHost, nick=MNick})
end,
case mnesia:transaction(F, [{{JID#jid.luser, JID#jid.lserver},
Domain}, Nick]) of
{atomic, ok} -> ok;
{aborted, _Error} -> error
end
catch _:{bad_jid, _} -> throw({error, "Malformed JID"})
end.
muc_register_nick(Nick, FromBinary, ServerHost) ->
Host = find_host(ServerHost),
From = jid:decode(FromBinary),
Lang = <<"en">>,
mod_muc:iq_set_register_info(ServerHost, Host, From, Nick, Lang).
muc_unregister_nick(Nick) ->
F2 = fun(N) ->
[{_,Key,_}|_] = mnesia:index_read(muc_registered, N, 3),
mnesia:delete({muc_registered, Key})
end,
case mnesia:transaction(F2, [Nick], 1) of
{atomic, ok} ->
ok;
{aborted, _Error} ->
error
end.
muc_unregister_nick(FromBinary, ServerHost) ->
muc_register_nick(<<"">>, FromBinary, ServerHost).
get_user_rooms(LUser, LServer) ->
lists:flatmap(
@@ -536,7 +521,8 @@ prepare_room_info(Room_info) ->
%% ok | error
%% @doc Create a room immediately with the default options.
create_room(Name1, Host1, ServerHost) ->
create_room_with_opts(Name1, Host1, ServerHost, []).
create_room_with_opts(Name1, Host1, ServerHost, []),
change_room_option(Name1, Host1, <<"persistent">>, <<"true">>).
create_room_with_opts(Name1, Host1, ServerHost, CustomRoomOpts) ->
true = (error /= (Name = jid:nodeprep(Name1))),
@@ -597,7 +583,7 @@ muc_create_room(ServerHost, {Name, Host, _}, DefRoomOpts) ->
destroy_room(Name, Service) ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
gen_fsm:send_all_state_event(Pid, destroy),
p1_fsm:send_all_state_event(Pid, destroy),
ok;
error ->
error
@@ -716,11 +702,11 @@ get_rooms(ServerHost) ->
end, Hosts).
get_room_config(Room_pid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_config),
{ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_config),
R.
get_room_state(Room_pid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(Room_pid, get_state),
{ok, R} = p1_fsm:sync_send_all_state_event(Room_pid, get_state),
R.
%%---------------
@@ -786,7 +772,7 @@ find_serverhost(Host, ServerHosts) ->
ServerHost.
act_on_room(destroy, {N, H, Pid}, SH) ->
gen_fsm:send_all_state_event(
p1_fsm:send_all_state_event(
Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
mod_muc:room_destroyed(H, N, Pid, SH),
mod_muc:forget_room(SH, H, N);
@@ -816,7 +802,13 @@ get_room_occupants(Pid) ->
dict:to_list(S#state.users)).
get_room_occupants_number(Room, Host) ->
length(get_room_occupants(Room, Host)).
case get_room_pid(Room, Host) of
room_not_found ->
throw({error, room_not_found});
Pid ->
S = get_room_state(Pid),
dict:size(S#state.users)
end.
%%----------------------------
%% Send Direct Invitation
@@ -888,7 +880,7 @@ change_room_option(Name, Service, OptionString, ValueString) ->
{Option, Value} = format_room_option(OptionString, ValueString),
Config = get_room_config(Pid),
Config2 = change_option(Option, Value, Config),
{ok, _} = gen_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
{ok, _} = p1_fsm:sync_send_all_state_event(Pid, {change_config, Config2}),
ok
end.
@@ -983,7 +975,7 @@ get_room_affiliations(Name, Service) ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
%% Get the PID of the online room, then request its state
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, get_state),
{ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, get_state),
Affiliations = ?DICT:to_list(StateData#state.affiliations),
lists:map(
fun({{Uname, Domain, _Res}, {Aff, Reason}}) when is_atom(Aff)->
@@ -1012,7 +1004,7 @@ set_room_affiliation(Name, Service, JID, AffiliationString) ->
case mod_muc:find_online_room(Name, Service) of
{ok, Pid} ->
%% Get the PID for the online room so we can get the state of the room
{ok, StateData} = gen_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
{ok, StateData} = p1_fsm:sync_send_all_state_event(Pid, {process_item_change, {jid:decode(JID), affiliation, Affiliation, <<"">>}, undefined}),
mod_muc:store_room(StateData#state.server_host, StateData#state.host, StateData#state.room, make_opts(StateData)),
ok;
error ->
@@ -1035,7 +1027,7 @@ subscribe_room(User, Nick, Room, Nodes) ->
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event(
case p1_fsm:sync_send_all_state_event(
Pid,
{muc_subscribe, UserJID, Nick, NodeList}) of
{ok, SubscribedNodes} ->
@@ -1062,7 +1054,7 @@ unsubscribe_room(User, Room) ->
UserJID ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
case gen_fsm:sync_send_all_state_event(
case p1_fsm:sync_send_all_state_event(
Pid,
{muc_unsubscribe, UserJID}) of
ok ->
@@ -1085,7 +1077,7 @@ unsubscribe_room(User, Room) ->
get_subscribers(Name, Host) ->
case get_room_pid(Name, Host) of
Pid when is_pid(Pid) ->
{ok, JIDList} = gen_fsm:sync_send_all_state_event(Pid, get_subscribers),
{ok, JIDList} = p1_fsm:sync_send_all_state_event(Pid, get_subscribers),
[jid:encode(jid:remove_resource(J)) || J <- JIDList];
_ ->
throw({error, "The room does not exist"})
+46 -47
View File
@@ -974,10 +974,9 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
Os2 = lists:sort(Os1),
Options2 = Title ++ Os2,
lists:foldl(fun ({Opt, Val}, R) ->
case get_roomconfig_text(Opt) of
case get_roomconfig_text(Opt, Lang) of
undefined -> R;
OptT ->
OptText = (?T(OptT)),
OptText ->
R2 = case Val of
false ->
<<"<div class=\"rcod\">",
@@ -1025,49 +1024,49 @@ roomconfig_to_string(Options, Lang, FileFormat) ->
end,
<<"">>, Options2).
get_roomconfig_text(title) -> <<"Room title">>;
get_roomconfig_text(persistent) ->
<<"Make room persistent">>;
get_roomconfig_text(public) ->
<<"Make room public searchable">>;
get_roomconfig_text(public_list) ->
<<"Make participants list public">>;
get_roomconfig_text(password_protected) ->
<<"Make room password protected">>;
get_roomconfig_text(password) -> <<"Password">>;
get_roomconfig_text(anonymous) ->
<<"This room is not anonymous">>;
get_roomconfig_text(members_only) ->
<<"Make room members-only">>;
get_roomconfig_text(moderated) ->
<<"Make room moderated">>;
get_roomconfig_text(members_by_default) ->
<<"Default users as participants">>;
get_roomconfig_text(allow_change_subj) ->
<<"Allow users to change the subject">>;
get_roomconfig_text(allow_private_messages) ->
<<"Allow users to send private messages">>;
get_roomconfig_text(allow_private_messages_from_visitors) ->
<<"Allow visitors to send private messages to">>;
get_roomconfig_text(allow_query_users) ->
<<"Allow users to query other users">>;
get_roomconfig_text(allow_user_invites) ->
<<"Allow users to send invites">>;
get_roomconfig_text(logging) -> <<"Enable logging">>;
get_roomconfig_text(allow_visitor_nickchange) ->
<<"Allow visitors to change nickname">>;
get_roomconfig_text(allow_visitor_status) ->
<<"Allow visitors to send status text in "
"presence updates">>;
get_roomconfig_text(captcha_protected) ->
<<"Make room captcha protected">>;
get_roomconfig_text(description) ->
<<"Room description">>;
%% get_roomconfig_text(subject) -> "Subject";
%% get_roomconfig_text(subject_author) -> "Subject author";
get_roomconfig_text(max_users) ->
<<"Maximum Number of Occupants">>;
get_roomconfig_text(_) -> undefined.
get_roomconfig_text(title, Lang) -> ?T(<<"Room title">>);
get_roomconfig_text(persistent, Lang) ->
?T(<<"Make room persistent">>);
get_roomconfig_text(public, Lang) ->
?T(<<"Make room public searchable">>);
get_roomconfig_text(public_list, Lang) ->
?T(<<"Make participants list public">>);
get_roomconfig_text(password_protected, Lang) ->
?T(<<"Make room password protected">>);
get_roomconfig_text(password, Lang) -> ?T(<<"Password">>);
get_roomconfig_text(anonymous, Lang) ->
?T(<<"This room is not anonymous">>);
get_roomconfig_text(members_only, Lang) ->
?T(<<"Make room members-only">>);
get_roomconfig_text(moderated, Lang) ->
?T(<<"Make room moderated">>);
get_roomconfig_text(members_by_default, Lang) ->
?T(<<"Default users as participants">>);
get_roomconfig_text(allow_change_subj, Lang) ->
?T(<<"Allow users to change the subject">>);
get_roomconfig_text(allow_private_messages, Lang) ->
?T(<<"Allow users to send private messages">>);
get_roomconfig_text(allow_private_messages_from_visitors, Lang) ->
?T(<<"Allow visitors to send private messages to">>);
get_roomconfig_text(allow_query_users, Lang) ->
?T(<<"Allow users to query other users">>);
get_roomconfig_text(allow_user_invites, Lang) ->
?T(<<"Allow users to send invites">>);
get_roomconfig_text(logging, Lang) -> ?T(<<"Enable logging">>);
get_roomconfig_text(allow_visitor_nickchange, Lang) ->
?T(<<"Allow visitors to change nickname">>);
get_roomconfig_text(allow_visitor_status, Lang) ->
?T(<<"Allow visitors to send status text in "
"presence updates">>);
get_roomconfig_text(captcha_protected, Lang) ->
?T(<<"Make room CAPTCHA protected">>);
get_roomconfig_text(description, Lang) ->
?T(<<"Room description">>);
%% get_roomconfig_text(subject, Lang) -> "Subject";
%% get_roomconfig_text(subject_author, Lang) -> "Subject author";
get_roomconfig_text(max_users, Lang) ->
?T(<<"Maximum Number of Occupants">>);
get_roomconfig_text(_, _) -> undefined.
%% Users = [{JID, Nick, Role}]
roomoccupants_to_string(Users, _FileFormat) ->
@@ -1142,7 +1141,7 @@ get_room_state(RoomName, MucService) ->
-spec get_room_state(pid()) -> mod_muc_room:state().
get_room_state(RoomPid) ->
{ok, R} = gen_fsm:sync_send_all_state_event(RoomPid,
{ok, R} = p1_fsm:sync_send_all_state_event(RoomPid,
get_state),
R.
+5 -2
View File
@@ -296,7 +296,7 @@ import(_LServer, <<"muc_registered">>,
%%% gen_server callbacks
%%%===================================================================
init([Host, Opts]) ->
MyHost = proplists:get_value(host, Opts),
MyHosts = proplists:get_value(hosts, Opts),
case gen_mod:db_mod(Host, Opts, mod_muc) of
?MODULE ->
ejabberd_mnesia:create(?MODULE, muc_room,
@@ -318,7 +318,10 @@ init([Host, Opts]) ->
{type, ordered_set},
{attributes, record_info(fields, muc_online_room)}]),
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
clean_table_from_bad_node(node(), MyHost),
lists:foreach(
fun(MyHost) ->
clean_table_from_bad_node(node(), MyHost)
end, MyHosts),
mnesia:subscribe(system);
_ ->
ok
+1 -1
View File
@@ -60,7 +60,7 @@ restore_room(_LServer, Host, Name) ->
forget_room(_LServer, Host, Name) ->
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})}.
can_use_nick(LServer, Host, JID, Nick) ->
can_use_nick(_LServer, Host, JID, Nick) ->
{LUser, LServer, _} = jid:tolower(JID),
LUS = {LUser, LServer},
case ejabberd_riak:get_by_index(muc_registered,
+27 -12
View File
@@ -27,7 +27,7 @@
-author('alexey@process-one.net').
-behaviour(gen_fsm).
-behaviour(p1_fsm).
%% External exports
-export([start_link/10,
@@ -94,23 +94,23 @@
%%%----------------------------------------------------------------------
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) ->
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS).
start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
p1_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts, QueueType],
?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper,
Creator, Nick, DefRoomOpts, QueueType) ->
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Creator, Nick, DefRoomOpts, QueueType],
?FSMOPTS).
start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType) ->
gen_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
p1_fsm:start_link(?MODULE, [Host, ServerHost, Access, Room, HistorySize,
RoomShaper, Opts, QueueType],
?FSMOPTS).
@@ -492,7 +492,7 @@ handle_event({destroy, Reason}, _StateName,
handle_event(destroy, StateName, StateData) ->
?INFO_MSG("Destroyed MUC room ~s",
[jid:encode(StateData#state.jid)]),
handle_event({destroy, undefined}, StateName, StateData);
handle_event({destroy, <<"">>}, StateName, StateData);
handle_event({set_affiliations, Affiliations},
StateName, StateData) ->
{next_state, StateName,
@@ -703,7 +703,7 @@ terminate(Reason, _StateName, StateData) ->
-spec route(pid(), stanza()) -> ok.
route(Pid, Packet) ->
#jid{lresource = Nick} = xmpp:get_to(Packet),
gen_fsm:send_event(Pid, {route, Nick, Packet}).
p1_fsm:send_event(Pid, {route, Nick, Packet}).
-spec process_groupchat_message(message(), state()) -> fsm_next().
process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData) ->
@@ -1028,8 +1028,16 @@ do_process_presence(Nick, #presence{from = From, type = unavailable} = Packet,
end,
NewState = add_user_presence_un(From, NewPacket, StateData),
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [_, _ | _]} -> ok;
_ -> send_new_presence(From, NewState, StateData)
{ok, [_, _ | _]} ->
Aff = get_affiliation(From, StateData),
Item = #muc_item{affiliation = Aff, role = none, jid = From},
Pres = xmpp:set_subtag(
Packet, #muc_user{items = [Item],
status_codes = [110]}),
send_wrapped(jid:replace_resource(StateData#state.jid, Nick),
From, Pres, ?NS_MUCSUB_NODES_PRESENCE, StateData);
_ ->
send_new_presence(From, NewState, StateData)
end,
Reason = xmpp:get_text(NewPacket#presence.status),
remove_online_user(From, NewState, Reason);
@@ -1243,7 +1251,12 @@ expulse_participant(Packet, From, StateData, Reason1) ->
#presence{type = unavailable,
status = xmpp:mk_text(Reason2)},
StateData),
send_new_presence(From, NewState, StateData),
LJID = jid:tolower(From),
{ok, #user{nick = Nick}} = (?DICT):find(LJID, StateData#state.users),
case (?DICT):find(Nick, StateData#state.nicks) of
{ok, [_, _ | _]} -> ok;
_ -> send_new_presence(From, NewState, StateData)
end,
remove_online_user(From, NewState).
-spec set_affiliation(jid(), affiliation(), state()) -> state().
@@ -2697,8 +2710,8 @@ find_changed_items(UJID, UAffiliation, URole,
Nick /= <<"">> ->
case find_jids_by_nick(Nick, StateData) of
[] ->
ErrText = str:format(<<"Nickname ~s does not exist in the room">>,
[Nick]),
ErrText = {<<"Nickname ~s does not exist in the room">>,
[Nick]},
throw({error, xmpp:err_not_acceptable(ErrText, Lang)});
JIDList ->
JIDList
@@ -4004,6 +4017,7 @@ tab_add_online_user(JID, StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
ServerHost = StateData#state.server_host,
ejabberd_hooks:run(join_room, ServerHost, [ServerHost, Room, Host, JID]),
mod_muc:register_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_remove_online_user(jid(), state()) -> any().
@@ -4011,6 +4025,7 @@ tab_remove_online_user(JID, StateData) ->
Room = StateData#state.room,
Host = StateData#state.host,
ServerHost = StateData#state.server_host,
ejabberd_hooks:run(leave_room, ServerHost, [ServerHost, Room, Host, JID]),
mod_muc:unregister_online_user(ServerHost, jid:tolower(JID), Room, Host).
-spec tab_count_user(jid(), state()) -> non_neg_integer().
+8 -6
View File
@@ -998,8 +998,10 @@ build_service_limit_record(LimitOpts) ->
build_limit_record(LimitOptsR, remote)}.
get_from_limitopts(LimitOpts, SenderT) ->
{SenderT, Result} = lists:keyfind(SenderT, 1, LimitOpts),
Result.
case lists:keyfind(SenderT, 1, LimitOpts) of
false -> [];
{SenderT, Result} -> Result
end.
build_remote_limit_record(LimitOpts, SenderT) ->
build_limit_record(LimitOpts, SenderT).
@@ -1120,10 +1122,10 @@ mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type({limits, Type}) when (Type == local) or (Type == remote) ->
fun(L) ->
lists:map(
fun ({message, infinite}) -> infinite;
({presence, infinite}) -> infinite;
({message, I}) when is_integer(I) -> I;
({presence, I}) when is_integer(I) -> I
fun ({message, infinite} = O) -> O;
({presence, infinite} = O) -> O;
({message, I} = O) when is_integer(I) -> O;
({presence, I} = O) when is_integer(I) -> O
end, L)
end;
mod_opt_type(_) -> [access, host, {limits, local}, {limits, remote}].
+15 -12
View File
@@ -706,22 +706,25 @@ user_queue_parse_query(LUser, LServer, Query) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case lists:keysearch(<<"delete">>, 1, Query) of
{value, _} ->
case lists:keyfind(<<"selected">>, 1, Query) of
{_, Seq} ->
case catch binary_to_integer(Seq) of
I when is_integer(I), I>=0 ->
Mod:remove_message(LUser, LServer, I),
ok;
_ ->
nothing
end;
false ->
nothing
end;
user_queue_parse_query(LUser, LServer, Query, Mod);
_ ->
nothing
end.
user_queue_parse_query(LUser, LServer, Query, Mod) ->
case lists:keytake(<<"selected">>, 1, Query) of
{value, {_, Seq}, Query2} ->
case catch binary_to_integer(Seq) of
I when is_integer(I), I>=0 ->
Mod:remove_message(LUser, LServer, I);
_ ->
nothing
end,
user_queue_parse_query(LUser, LServer, Query2, Mod);
false ->
nothing
end.
us_to_list({User, Server}) ->
jid:encode({User, Server, <<"">>}).
+36 -22
View File
@@ -410,9 +410,11 @@ decode_item(#privacy_item{order = Order,
match_presence_out = MatchPresenceOut}
end.
-spec c2s_copy_session(ejabberd_c2s:state(), c2s_state()) -> c2s_state().
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
c2s_copy_session(State, #{privacy_active_list := List}) ->
State#{privacy_active_list => List}.
State#{privacy_active_list => List};
c2s_copy_session(State, _) ->
State.
-spec user_send_packet({stanza(), c2s_state()}) -> {stanza(), c2s_state()}.
user_send_packet({#iq{type = Type,
@@ -636,25 +638,29 @@ is_ptype_match(Item, PType) ->
ljid(), none | both | from | to, [binary()]) -> boolean().
is_type_match(none, _Value, _JID, _Subscription, _Groups) ->
true;
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
jid ->
case Value of
{<<"">>, Server, <<"">>} ->
case JID of
{_, Server, _} -> true;
_ -> false
end;
{User, Server, <<"">>} ->
case JID of
{User, Server, _} -> true;
_ -> false
end;
_ -> Value == JID
end;
subscription -> Value == Subscription;
group -> lists:member(Value, Groups)
end.
is_type_match(jid, Value, JID, _Subscription, _Groups) ->
case Value of
{<<"">>, Server, <<"">>} ->
case JID of
{_, Server, _} -> true;
_ -> false
end;
{User, Server, <<"">>} ->
case JID of
{User, Server, _} -> true;
_ -> false
end;
{<<"">>, Server, Resource} ->
case JID of
{_, Server, Resource} -> true;
_ -> false
end;
_ -> Value == JID
end;
is_type_match(subscription, Value, _JID, Subscription, _Groups) ->
Value == Subscription;
is_type_match(group, Group, _JID, _Subscription, Groups) ->
lists:member(Group, Groups).
-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
@@ -840,4 +846,12 @@ depends(_Host, _Opts) ->
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(_) -> [db_type, iqdisc].
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun (I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[db_type, iqdisc, cache_life_time, cache_size,
use_cache, cache_missed].
+1 -1
View File
@@ -133,7 +133,7 @@ set_data(LUser, LServer, Data) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:set_data(LUser, LServer, Data) of
ok ->
delete_cache(Mod, LServer, LServer, Data);
delete_cache(Mod, LUser, LServer, Data);
{error, _} = Err ->
Err
end.
+3 -1
View File
@@ -112,6 +112,8 @@ depends(_Host, _Opts) ->
mod_opt_type(access) -> fun acl:access_rules_validator/1;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(hosts) ->
fun(L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(hostname) -> fun iolist_to_binary/1;
mod_opt_type(ip) ->
fun (S) ->
@@ -131,7 +133,7 @@ mod_opt_type(ram_db_type) ->
mod_opt_type(Opt) ->
case mod_proxy65_stream:listen_opt_type(Opt) of
Opts when is_list(Opts) ->
[access, host, hostname, ip, name, port,
[access, host, hosts, hostname, ip, name, port,
max_connections, ram_db_type] ++ Opts;
Fun ->
Fun
+57 -42
View File
@@ -43,7 +43,7 @@
-define(PROCNAME, ejabberd_mod_proxy65_service).
-record(state, {myhost = <<"">> :: binary()}).
-record(state, {myhosts = [] :: [binary()]}).
%%%------------------------
%%% gen_server callbacks
@@ -61,24 +61,27 @@ reload(Host, NewOpts, OldOpts) ->
init([Host, Opts]) ->
process_flag(trap_exit, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
MyHost = gen_mod:get_opt_host(Host, Opts, <<"proxy.@HOST@">>),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
?MODULE, process_disco_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
?MODULE, process_bytestreams, IQDisc),
ejabberd_router:register_route(MyHost, Host),
{ok, #state{myhost = MyHost}}.
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"proxy.@HOST@">>),
lists:foreach(
fun(MyHost) ->
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO,
?MODULE, process_disco_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_VCARD,
?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS,
?MODULE, process_bytestreams, IQDisc),
ejabberd_router:register_route(MyHost, Host)
end, MyHosts),
{ok, #state{myhosts = MyHosts}}.
terminate(_Reason, #state{myhost = MyHost}) ->
ejabberd_router:unregister_route(MyHost),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_BYTESTREAMS).
terminate(_Reason, #state{myhosts = MyHosts}) ->
lists:foreach(
fun(MyHost) ->
ejabberd_router:unregister_route(MyHost),
unregister_handlers(MyHost)
end, MyHosts).
handle_info({route, #iq{} = Packet}, State) ->
ejabberd_router:process_iq(Packet),
@@ -89,33 +92,29 @@ handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast({reload, ServerHost, NewOpts, OldOpts}, State) ->
NewHost = gen_mod:get_opt_host(ServerHost, NewOpts, <<"proxy.@HOST@">>),
OldHost = gen_mod:get_opt_host(ServerHost, OldOpts, <<"proxy.@HOST@">>),
NewHosts = gen_mod:get_opt_hosts(ServerHost, NewOpts, <<"proxy.@HOST@">>),
OldHosts = gen_mod:get_opt_hosts(ServerHost, OldOpts, <<"proxy.@HOST@">>),
NewIQDisc = gen_mod:get_opt(iqdisc, NewOpts, gen_iq_handler:iqdisc(ServerHost)),
OldIQDisc = gen_mod:get_opt(iqdisc, OldOpts, gen_iq_handler:iqdisc(ServerHost)),
if (NewIQDisc /= OldIQDisc) or (NewHost /= OldHost) ->
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_INFO,
?MODULE, process_disco_info, NewIQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items, NewIQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_VCARD,
?MODULE, process_vcard, NewIQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, NewHost, ?NS_BYTESTREAMS,
?MODULE, process_bytestreams, NewIQDisc);
if (NewIQDisc /= OldIQDisc) ->
lists:foreach(
fun(NewHost) ->
register_handlers(NewHost, NewIQDisc)
end, NewHosts -- (NewHosts -- OldHosts));
true ->
ok
end,
if NewHost /= OldHost ->
ejabberd_router:register_route(NewHost, ServerHost),
ejabberd_router:unregister_route(OldHost),
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, OldHost, ?NS_BYTESTREAMS);
true ->
ok
end,
{noreply, State#state{myhost = NewHost}};
lists:foreach(
fun(NewHost) ->
ejabberd_router:register_route(NewHost, ServerHost),
register_handlers(NewHost, NewIQDisc)
end, NewHosts -- OldHosts),
lists:foreach(
fun(OldHost) ->
ejabberd_router:unregister_route(OldHost),
unregister_handlers(OldHost)
end, OldHosts -- NewHosts),
{noreply, State#state{myhosts = NewHosts}};
handle_cast(Msg, State) ->
?WARNING_MSG("unexpected cast: ~p", [Msg]),
{noreply, State}.
@@ -184,7 +183,7 @@ process_bytestreams(#iq{type = get, from = JID, to = To, lang = Lang} = IQ) ->
StreamHost = get_streamhost(Host, ServerHost),
xmpp:make_iq_result(IQ, #bytestreams{hosts = [StreamHost]});
deny ->
xmpp:make_error(IQ, xmpp:err_forbidden(<<"Denied by ACL">>, Lang))
xmpp:make_error(IQ, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang))
end;
process_bytestreams(#iq{type = set, lang = Lang,
sub_els = [#bytestreams{sid = SID}]} = IQ)
@@ -233,7 +232,7 @@ process_bytestreams(#iq{type = set, lang = Lang, from = InitiatorJID, to = To,
xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
end;
deny ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end.
@@ -276,3 +275,19 @@ get_my_ip() ->
max_connections(ServerHost) ->
gen_mod:get_module_opt(ServerHost, mod_proxy65, max_connections, infinity).
register_handlers(Host, IQDisc) ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
?MODULE, process_disco_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS,
?MODULE, process_bytestreams, IQDisc).
unregister_handlers(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_BYTESTREAMS).
+5 -5
View File
@@ -26,7 +26,7 @@
-author('xram@jabber.ru').
-behaviour(gen_fsm).
-behaviour(p1_fsm).
%% gen_fsm callbacks.
-export([init/1, handle_event/3, handle_sync_event/4,
@@ -75,7 +75,7 @@ start({gen_tcp, Socket}, Opts1) ->
[Socket, Host, Opts]).
start_link(Socket, Host, Opts) ->
gen_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
p1_fsm:start_link(?MODULE, [Socket, Host, Opts], []).
init([Socket, Host, Opts]) ->
process_flag(trap_exit, true),
@@ -106,9 +106,9 @@ socket_type() -> raw.
stop(StreamPid) -> StreamPid ! stop.
activate({P1, J1}, {P2, J2}) ->
case catch {gen_fsm:sync_send_all_state_event(P1,
case catch {p1_fsm:sync_send_all_state_event(P1,
get_socket),
gen_fsm:sync_send_all_state_event(P2, get_socket)}
p1_fsm:sync_send_all_state_event(P2, get_socket)}
of
{S1, S2} when is_port(S1), is_port(S2) ->
P1 ! {activate, P2, S2, J1, J2},
@@ -197,7 +197,7 @@ handle_info({tcp, _S, Data}, StateName, StateData)
when StateName /= wait_for_activation ->
erlang:cancel_timer(StateData#state.timer),
TRef = erlang:send_after(?WAIT_TIMEOUT, self(), stop),
gen_fsm:send_event(self(), Data),
p1_fsm:send_event(self(), Data),
{next_state, StateName, StateData#state{timer = TRef}};
%% Activation message.
handle_info({activate, PeerPid, PeerSocket, IJid, TJid},
+288 -335
View File
@@ -52,7 +52,7 @@
%% exports for hooks
-export([presence_probe/3, caps_add/3, caps_update/3,
in_subscription/6, out_subscription/4,
on_user_offline/3, remove_user/2,
on_user_online/1, on_user_offline/2, remove_user/2,
disco_local_identity/5, disco_local_features/5,
disco_local_items/5, disco_sm_identity/5,
disco_sm_features/5, disco_sm_items/5,
@@ -89,11 +89,7 @@
%% API and gen_server callbacks
-export([start/2, stop/1, init/1,
handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, depends/2]).
-export([send_loop/1, mod_opt_type/1]).
-define(LOOPNAME, ejabberd_mod_pubsub_loop).
terminate/2, code_change/3, depends/2, export/1, mod_opt_type/1]).
%%====================================================================
%% API
@@ -189,7 +185,7 @@
-record(state,
{
server_host,
host,
hosts,
access,
pep_mapping = [],
ignore_pep_from_offline = true,
@@ -205,7 +201,7 @@
-type(state() ::
#state{
server_host :: binary(),
host :: mod_pubsub:hostPubsub(),
hosts :: [mod_pubsub:hostPubsub()],
access :: atom(),
pep_mapping :: [{binary(), binary()}],
ignore_pep_from_offline :: boolean(),
@@ -243,43 +239,65 @@ stop(Host) ->
init([ServerHost, Opts]) ->
process_flag(trap_exit, true),
?DEBUG("pubsub init ~p ~p", [ServerHost, Opts]),
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
ejabberd_router:register_route(Host, ServerHost),
Hosts = gen_mod:get_opt_hosts(ServerHost, Opts, <<"pubsub.@HOST@">>),
Access = gen_mod:get_opt(access_createnode, Opts, all),
PepOffline = gen_mod:get_opt(ignore_pep_from_offline, Opts, true),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(ServerHost)),
LastItemCache = gen_mod:get_opt(last_item_cache, Opts, false),
MaxItemsNode = gen_mod:get_opt(max_items_node, Opts, ?MAXITEMS),
MaxSubsNode = gen_mod:get_opt(max_subscriptions_node, Opts),
case gen_mod:db_type(ServerHost, ?MODULE) of
mnesia -> pubsub_index:init(Host, ServerHost, Opts);
_ -> ok
end,
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
DefaultModule = plugin(Host, hd(Plugins)),
BaseOptions = DefaultModule:options(),
DefaultNodeCfg = filter_node_options(
gen_mod:get_opt(default_node_config, Opts, []),
BaseOptions),
ejabberd_mnesia:create(?MODULE, pubsub_last_item,
[{ram_copies, [node()]},
{attributes, record_info(fields, pubsub_last_item)}]),
lists:foreach(
fun(H) ->
T = gen_mod:get_module_proc(H, config),
ets:new(T, [set, named_table]),
ets:insert(T, {nodetree, NodeTree}),
ets:insert(T, {plugins, Plugins}),
ets:insert(T, {last_item_cache, LastItemCache}),
ets:insert(T, {max_items_node, MaxItemsNode}),
ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
ets:insert(T, {default_node_config, DefaultNodeCfg}),
ets:insert(T, {pep_mapping, PepMapping}),
ets:insert(T, {ignore_pep_from_offline, PepOffline}),
ets:insert(T, {host, Host}),
ets:insert(T, {access, Access})
end, [Host, ServerHost]),
ejabberd_hooks:add(sm_remove_connection_hook, ServerHost,
[{ram_copies, [node()]},
{attributes, record_info(fields, pubsub_last_item)}]),
AllPlugins =
lists:flatmap(
fun(Host) ->
ejabberd_router:register_route(Host, ServerHost),
case gen_mod:db_type(ServerHost, ?MODULE) of
mnesia -> pubsub_index:init(Host, ServerHost, Opts);
_ -> ok
end,
{Plugins, NodeTree, PepMapping} = init_plugins(Host, ServerHost, Opts),
DefaultModule = plugin(Host, hd(Plugins)),
DefaultNodeCfg = merge_config(
gen_mod:get_opt(default_node_config, Opts, []),
DefaultModule:options()),
lists:foreach(
fun(H) ->
T = gen_mod:get_module_proc(H, config),
try
ets:new(T, [set, named_table]),
ets:insert(T, {nodetree, NodeTree}),
ets:insert(T, {plugins, Plugins}),
ets:insert(T, {last_item_cache, LastItemCache}),
ets:insert(T, {max_items_node, MaxItemsNode}),
ets:insert(T, {max_subscriptions_node, MaxSubsNode}),
ets:insert(T, {default_node_config, DefaultNodeCfg}),
ets:insert(T, {pep_mapping, PepMapping}),
ets:insert(T, {ignore_pep_from_offline, PepOffline}),
ets:insert(T, {host, Host}),
ets:insert(T, {access, Access})
catch error:badarg when H == ServerHost ->
ok
end
end, [Host, ServerHost]),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
?MODULE, process_disco_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
?MODULE, process_pubsub, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
?MODULE, process_pubsub_owner, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
?MODULE, process_commands, IQDisc),
Plugins
end, Hosts),
ejabberd_hooks:add(c2s_session_opened, ServerHost,
?MODULE, on_user_online, 75),
ejabberd_hooks:add(c2s_terminated, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:add(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75),
@@ -297,19 +315,7 @@ init([ServerHost, Opts]) ->
?MODULE, remove_user, 50),
ejabberd_hooks:add(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
?MODULE, process_disco_info, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
?MODULE, process_disco_items, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB,
?MODULE, process_pubsub, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER,
?MODULE, process_pubsub_owner, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_COMMANDS,
?MODULE, process_commands, IQDisc),
case lists:member(?PEPNODE, Plugins) of
case lists:member(?PEPNODE, AllPlugins) of
true ->
ejabberd_hooks:add(caps_add, ServerHost,
?MODULE, caps_add, 80),
@@ -328,35 +334,16 @@ init([ServerHost, Opts]) ->
false ->
ok
end,
{_, State} = init_send_loop(ServerHost),
{ok, State}.
init_send_loop(ServerHost) ->
NodeTree = config(ServerHost, nodetree),
Plugins = config(ServerHost, plugins),
LastItemCache = config(ServerHost, last_item_cache),
MaxItemsNode = config(ServerHost, max_items_node),
PepMapping = config(ServerHost, pep_mapping),
PepOffline = config(ServerHost, ignore_pep_from_offline),
Host = config(ServerHost, host),
Access = config(ServerHost, access),
DBType = gen_mod:db_type(ServerHost, ?MODULE),
State = #state{host = Host, server_host = ServerHost,
access = Access, pep_mapping = PepMapping,
ignore_pep_from_offline = PepOffline,
last_item_cache = LastItemCache,
max_items_node = MaxItemsNode, nodetree = NodeTree,
plugins = Plugins, db_type = DBType},
Proc = gen_mod:get_module_proc(ServerHost, ?LOOPNAME),
Pid = case whereis(Proc) of
undefined ->
SendLoop = spawn(?MODULE, send_loop, [State]),
register(Proc, SendLoop),
SendLoop;
Loop ->
Loop
end,
{Pid, State}.
{ok, #state{hosts = Hosts, server_host = ServerHost,
access = Access, pep_mapping = PepMapping,
ignore_pep_from_offline = PepOffline,
last_item_cache = LastItemCache,
max_items_node = MaxItemsNode, nodetree = NodeTree,
plugins = Plugins, db_type = DBType}}.
depends(ServerHost, Opts) ->
Host = gen_mod:get_opt_host(ServerHost, Opts, <<"pubsub.@HOST@">>),
@@ -406,94 +393,6 @@ terminate_plugins(Host, ServerHost, Plugins, TreePlugin) ->
TreePlugin:terminate(Host, ServerHost),
ok.
get_subscribed(User, Server) ->
Items = ejabberd_hooks:run_fold(roster_get, Server, [], [{User, Server}]),
lists:filtermap(
fun(#roster{jid = LJID, subscription = Sub})
when Sub == both orelse Sub == from ->
{true, LJID};
(_) ->
false
end, Items).
send_loop(State) ->
receive
{presence, JID, _Pid} ->
Host = State#state.host,
ServerHost = State#state.server_host,
DBType = State#state.db_type,
LJID = jid:tolower(JID),
BJID = jid:remove_resource(LJID),
lists:foreach(
fun(PType) ->
Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
lists:foreach(
fun({NodeRec, _, _, SubJID}) ->
{_, Node} = NodeRec#pubsub_node.nodeid,
Nidx = NodeRec#pubsub_node.id,
Options = NodeRec#pubsub_node.options,
[send_items(Host, Node, Nidx, PType, Options, SubJID, last)
|| NodeRec#pubsub_node.type == PType]
end,
lists:usort(Subs))
end,
State#state.plugins),
if not State#state.ignore_pep_from_offline ->
{User, Server, Resource} = LJID,
Contacts = get_subscribed(User, Server),
lists:foreach(
fun({U, S, R}) when S == ServerHost ->
case user_resources(U, S) of
[] -> %% offline
PeerJID = jid:make(U, S, R),
self() ! {presence, User, Server, [Resource], PeerJID};
_ -> %% online
%% this is already handled by presence probe
ok
end;
(_) ->
%% we can not do anything in any cases
ok
end, Contacts);
true ->
ok
end,
send_loop(State);
{presence, User, Server, Resources, JID} ->
spawn(fun() ->
Host = State#state.host,
Owner = jid:remove_resource(jid:tolower(JID)),
lists:foreach(fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
case match_option(Options, send_last_published_item, on_sub_and_presence) of
true ->
lists:foreach(fun(Resource) ->
LJID = {User, Server, Resource},
Subscribed = case get_option(Options, access_model) of
open -> true;
presence -> true;
whitelist -> false; % subscribers are added manually
authorize -> false; % likewise
roster ->
Grps = get_option(Options, roster_groups_allowed, []),
{OU, OS, _} = Owner,
element(2, get_roster_info(OU, OS, LJID, Grps))
end,
if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, LJID, last);
true -> ok
end
end,
Resources);
_ ->
ok
end
end,
tree_action(Host, get_nodes, [Owner, JID]))
end),
send_loop(State);
stop ->
ok
end.
%% -------
%% disco hooks handling functions
%%
@@ -546,13 +445,11 @@ disco_identity(Host, Node, From) ->
case get_allowed_items_call(Host, Nidx, From, Type,
Options, Owners) of
{result, _} ->
{result, [#identity{category = <<"pubsub">>,
type = <<"pep">>},
#identity{category = <<"pubsub">>,
type = <<"leaf">>,
{result, [#identity{category = <<"pubsub">>, type = <<"pep">>},
#identity{category = <<"pubsub">>, type = <<"leaf">>,
name = case get_option(Options, title) of
false -> <<>>;
[Title] -> Title
Title -> Title
end}]};
_ ->
{result, []}
@@ -586,8 +483,7 @@ disco_features(Host, Node, From) ->
Type, Options, Owners) of
{result, _} ->
{result,
[?NS_PUBSUB |
[feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
[?NS_PUBSUB | [feature(F) || F <- plugin_features(Host, <<"pep">>)]]};
_ ->
{result, []}
end
@@ -620,7 +516,7 @@ disco_items(Host, <<>>, From) ->
jid = jid:make(Host),
name = case get_option(Options, title) of
false -> <<>>;
[Title] -> Title
Title -> Title
end} | Acc];
_ ->
Acc
@@ -655,12 +551,12 @@ disco_items(Host, Node, From) ->
end.
%% -------
%% presence hooks handling functions
%% presence and session hooks handling functions
%%
-spec caps_add(jid(), jid(), [binary()]) -> ok.
caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features)
when Host =/= S ->
caps_add(#jid{lserver = S1} = From, #jid{lserver = S2} = To, _Features)
when S1 =/= S2 ->
%% When a remote contact goes online while the local user is offline, the
%% remote contact won't receive last items from the local user even if
%% ignore_pep_from_offline is set to false. To work around this issue a bit,
@@ -670,36 +566,36 @@ caps_add(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID
%% contact becomes available; the former is also executed when the local
%% user goes online (because that triggers the contact to send a presence
%% packet with CAPS).
presence(Host, {presence, U, S, [R], JID});
send_last_pep(To, From);
caps_add(_From, _To, _Feature) ->
ok.
-spec caps_update(jid(), jid(), [binary()]) -> ok.
caps_update(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = Host} = JID, _Features) ->
presence(Host, {presence, U, S, [R], JID}).
caps_update(From, To, _Features) ->
send_last_pep(To, From).
-spec presence_probe(jid(), jid(), pid()) -> ok.
presence_probe(#jid{luser = U, lserver = S, lresource = R} = JID, JID, Pid) ->
presence(S, {presence, JID, Pid}),
presence(S, {presence, U, S, [R], JID});
presence_probe(#jid{luser = U, lserver = S}, #jid{luser = U, lserver = S}, _Pid) ->
%% ignore presence_probe from my other ressources
%% to not get duplicated last items
ok;
presence_probe(#jid{luser = U, lserver = S, lresource = R}, #jid{lserver = S} = JID, _Pid) ->
presence(S, {presence, U, S, [R], JID});
presence_probe(_Host, _JID, _Pid) ->
%% ignore presence_probe from remote contacts,
%% those are handled via caps_add
presence_probe(#jid{lserver = S} = From, #jid{lserver = S} = To, _Pid) ->
send_last_pep(To, From);
presence_probe(_From, _To, _Pid) ->
%% ignore presence_probe from remote contacts, those are handled via caps_add
ok.
presence(ServerHost, Presence) ->
{SendLoop, _} = case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
undefined -> init_send_loop(ServerHost);
Pid -> {Pid, undefined}
end,
SendLoop ! Presence,
ok.
-spec on_user_online(ejabberd_c2s:state()) -> ejabberd_c2s:state().
on_user_online(C2SState) ->
JID = maps:get(jid, C2SState),
send_last_items(JID),
C2SState.
-spec on_user_offline(ejabberd_c2s:state(), atom()) -> ejabberd_c2s:state().
on_user_offline(#{jid := JID} = C2SState, _Reason) ->
purge_offline(jid:tolower(JID)),
C2SState;
on_user_offline(C2SState, _Reason) ->
C2SState.
%% -------
%% subscription hooks handling functions
@@ -708,14 +604,8 @@ presence(ServerHost, Presence) ->
-spec out_subscription(
binary(), binary(), jid(),
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
out_subscription(User, Server, JID, subscribed) ->
Owner = jid:make(User, Server),
{PUser, PServer, PResource} = jid:tolower(JID),
PResources = case PResource of
<<>> -> user_resources(PUser, PServer);
_ -> [PResource]
end,
presence(Server, {presence, PUser, PServer, PResources, Owner}),
out_subscription(User, Server, To, subscribed) ->
send_last_pep(jid:make(User, Server), To),
true;
out_subscription(_, _, _, _) ->
true.
@@ -864,7 +754,7 @@ handle_info(_Info, State) ->
%%--------------------------------------------------------------------
%% @private
terminate(_Reason,
#state{host = Host, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
#state{hosts = Hosts, server_host = ServerHost, nodetree = TreePlugin, plugins = Plugins}) ->
case lists:member(?PEPNODE, Plugins) of
true ->
ejabberd_hooks:delete(caps_add, ServerHost,
@@ -884,7 +774,9 @@ terminate(_Reason,
false ->
ok
end,
ejabberd_hooks:delete(sm_remove_connection_hook, ServerHost,
ejabberd_hooks:delete(c2s_session_opened, ServerHost,
?MODULE, on_user_online, 75),
ejabberd_hooks:delete(c2s_terminated, ServerHost,
?MODULE, on_user_offline, 75),
ejabberd_hooks:delete(disco_local_identity, ServerHost,
?MODULE, disco_local_identity, 75),
@@ -902,20 +794,17 @@ terminate(_Reason,
?MODULE, remove_user, 50),
ejabberd_hooks:delete(c2s_handle_info, ServerHost,
?MODULE, c2s_handle_info, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of
undefined ->
?ERROR_MSG("~s process is dead, pubsub was broken", [?LOOPNAME]);
Pid ->
Pid ! stop
end,
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
ejabberd_router:unregister_route(Host).
lists:foreach(
fun(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_PUBSUB_OWNER),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_COMMANDS),
terminate_plugins(Host, ServerHost, Plugins, TreePlugin),
ejabberd_router:unregister_route(Host)
end, Hosts).
%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
@@ -1723,7 +1612,7 @@ delete_node(Host, Node, Owner) ->
%%<li>The node does not support subscriptions.</li>
%%<li>The node does not exist.</li>
%%</ul>
-spec subscribe_node(host(), binary(), jid(), binary(), [{binary(), [binary()]}]) ->
-spec subscribe_node(host(), binary(), jid(), jid(), [{binary(), [binary()]}]) ->
{result, pubsub()} | {error, stanza_error()}.
subscribe_node(Host, Node, From, JID, Configuration) ->
SubModule = subscription_plugin(Host),
@@ -1795,7 +1684,7 @@ subscribe_node(Host, Node, From, JID, Configuration) ->
Nidx = TNode#pubsub_node.id,
Type = TNode#pubsub_node.type,
Options = TNode#pubsub_node.options,
send_items(Host, Node, Nidx, Type, Options, Subscriber, last),
send_items(Host, Node, Nidx, Type, Options, Subscriber, 1),
ServerHost = serverhost(Host),
ejabberd_hooks:run(pubsub_subscribe_node, ServerHost,
[ServerHost, Host, Node, Subscriber, SubId]),
@@ -1906,8 +1795,6 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
broadcast -> Payload;
PluginPayload -> PluginPayload
end,
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
set_cached_item(Host, Nidx, ItemId, Publisher, BrPayload),
case get_option(Options, deliver_notifications) of
true ->
@@ -1916,6 +1803,8 @@ publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, PubOpts, Access
false ->
ok
end,
ejabberd_hooks:run(pubsub_publish_item, ServerHost,
[ServerHost, Node, Publisher, service_jid(Host), ItemId, BrPayload]),
case Result of
default -> {result, Reply};
_ -> {result, Result}
@@ -2067,20 +1956,14 @@ purge_node(Host, Node, Owner) ->
%% @doc <p>Return the items of a given node.</p>
%% <p>The number of items to return is limited by MaxItems.</p>
%% <p>The permission are not checked in this function.</p>
%% @todo We probably need to check that the user doing the query has the right
%% to read the items.
-spec get_items(host(), binary(), jid(), binary(),
binary(), [binary()], undefined | rsm_set()) ->
{result, pubsub()} | {error, stanza_error()}.
get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
MaxItems = if SMaxItems == undefined ->
case get_max_items_node(Host) of
undefined -> ?MAXITEMS;
Max -> Max
end;
true ->
SMaxItems
end,
get_items(Host, Node, From, SubId, MaxItems, ItemIds, undefined)
when MaxItems =/= undefined ->
get_items(Host, Node, From, SubId, MaxItems, ItemIds,
#rsm_set{max = MaxItems, before = <<>>});
get_items(Host, Node, From, SubId, _MaxItems, ItemIds, RSM) ->
Action =
fun(#pubsub_node{options = Options, type = Type,
id = Nidx, owners = O}) ->
@@ -2099,8 +1982,14 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
Owners = node_owners_call(Host, Type, Nidx, O),
{PS, RG} = get_presence_and_roster_permissions(
Host, From, Owners, AccessModel, AllowedGroups),
node_call(Host, Type, get_items,
[Nidx, From, AccessModel, PS, RG, SubId, RSM])
case ItemIds of
[ItemId] ->
node_call(Host, Type, get_item,
[Nidx, ItemId, From, AccessModel, PS, RG, undefined]);
_ ->
node_call(Host, Type, get_items,
[Nidx, From, AccessModel, PS, RG, SubId, RSM])
end
end
end,
case transaction(Host, Node, Action, sync_dirty) of
@@ -2116,8 +2005,12 @@ get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) ->
end,
{result,
#pubsub{items = #ps_items{node = Node,
items = itemsEls(lists:sublist(SendItems, MaxItems))},
items = itemsEls(SendItems)},
rsm = RsmOut}};
{result, {_, Item}} ->
{result,
#pubsub{items = #ps_items{node = Node,
items = itemsEls([Item])}}};
Error ->
Error
end.
@@ -2151,38 +2044,23 @@ get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) ->
{PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups),
node_call(Host, Type, get_items, [Nidx, From, AccessModel, PS, RG, undefined, RSM]).
get_last_items(Host, Type, Nidx, LJID, Count) ->
get_last_items(Host, Type, Nidx, LJID, 1) ->
case get_cached_item(Host, Nidx) of
undefined ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, 1]) of
{result, Items} -> Items;
_ -> []
end;
LastItem ->
[LastItem]
end;
get_last_items(Host, Type, Nidx, LJID, Count) when Count > 1 ->
case node_action(Host, Type, get_last_items, [Nidx, LJID, Count]) of
{result, Items} -> Items;
_ -> []
end.
%% @doc <p>Resend the items of a node to the user.</p>
%% @todo use cache-last-item feature
send_items(Host, Node, Nidx, Type, Options, LJID, last) ->
case get_last_items(Host, Type, Nidx, LJID, 1) of
[LastItem] ->
Stanza = items_event_stanza(Node, Options, [LastItem]),
dispatch_items(Host, LJID, Node, Stanza);
_ ->
ok
end;
send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 ->
Stanza = items_event_stanza(Node, Options, get_last_items(Host, Type, Nidx, Number, LJID)),
dispatch_items(Host, LJID, Node, Stanza);
send_items(Host, Node, _Nidx, _Type, Options, LJID, _) ->
Stanza = items_event_stanza(Node, Options, []),
dispatch_items(Host, LJID, Node, Stanza).
dispatch_items({FromU, FromS, FromR}, To, Node, Stanza) ->
SenderResource = user_resource(FromU, FromS, FromR),
ejabberd_sm:route(jid:make(FromU, FromS, SenderResource),
{send_filtered, {pep_message, <<((Node))/binary, "+notify">>},
jid:make(FromU, FromS), jid:make(To),
Stanza});
dispatch_items(From, To, _Node, Stanza) ->
ejabberd_router:route(
xmpp:set_from_to(Stanza, service_jid(From), jid:make(To))).
get_last_items(_Host, _Type, _Nidx, _LJID, _Count) ->
[].
%% @doc <p>Return the list of affiliations as an XMPP response.</p>
-spec get_affiliations(host(), binary(), jid(), [binary()]) ->
@@ -2529,14 +2407,15 @@ get_subscriptions_for_send_last(Host, PType, sql, JID, LJID, BJID) ->
{result, Subs} = node_action(Host, PType,
get_entity_subscriptions_for_send_last,
[Host, JID]),
[{Node, Sub, SubId, SubJID}
[{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID)];
% sql version already filter result by on_sub_and_presence
get_subscriptions_for_send_last(Host, PType, _, JID, LJID, BJID) ->
{result, Subs} = node_action(Host, PType,
get_entity_subscriptions,
[Host, JID]),
[{Node, Sub, SubId, SubJID}
[{Node, SubId, SubJID}
|| {Node, Sub, SubId, SubJID} <- Subs,
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID),
match_option(Node, send_last_published_item, on_sub_and_presence)].
@@ -2925,8 +2804,9 @@ get_options_for_subs(Host, Nidx, Subs, true) ->
broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
NotificationType = get_option(NodeOptions, notification_type, headline),
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), %% XXX this is not standard, but usefull
From = service_jid(Host),
Stanza = add_message_type(BaseStanza, NotificationType),
Stanza = add_message_type(
xmpp:set_from(BaseStanza, service_jid(Host)),
NotificationType),
%% Handles explicit subscriptions
SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth),
lists:foreach(fun ({LJID, _NodeName, SubIDs}) ->
@@ -2949,7 +2829,7 @@ broadcast_stanza(Host, _Node, _Nidx, _Type, NodeOptions, SubsByDepth, NotifyType
end,
lists:foreach(fun(To) ->
ejabberd_router:route(
xmpp:set_from_to(StanzaToSend, From, jid:make(To)))
xmpp:set_to(StanzaToSend, jid:make(To)))
end, LJIDs)
end, SubIDsByJID).
@@ -2958,55 +2838,142 @@ broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeO
%% Handles implicit presence subscriptions
SenderResource = user_resource(LUser, LServer, LResource),
NotificationType = get_option(NodeOptions, notification_type, headline),
Stanza = add_message_type(BaseStanza, NotificationType),
Stanza = add_message_type(
xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
NotificationType),
%% set the from address on the notification to the bare JID of the account owner
%% Also, add "replyto" if entity has presence subscription to the account owner
%% See XEP-0163 1.1 section 4.3.1
ejabberd_sm:route(jid:make(LUser, LServer, SenderResource),
{pep_message, <<((Node))/binary, "+notify">>,
jid:make(LUser, LServer),
add_extended_headers(
Stanza, extended_headers([Publisher]))});
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM) ->
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, NotifyType, BaseStanza, SHIM).
-spec c2s_handle_info(ejabberd_c2s:state(), term()) -> ejabberd_c2s:state().
c2s_handle_info(#{server := Server} = C2SState,
{pep_message, Feature, From, Packet}) ->
LServer = jid:nameprep(Server),
lists:foreach(
fun({USR, Caps}) ->
Features = mod_caps:get_features(LServer, Caps),
case lists:member(Feature, Features) of
true ->
To = jid:make(USR),
NewPacket = xmpp:set_from_to(Packet, From, To),
ejabberd_router:route(NewPacket);
false ->
ok
end
end, mod_caps:list_features(C2SState)),
c2s_handle_info(#{lserver := LServer} = C2SState,
{pep_message, Feature, Packet}) ->
[maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet)
|| {USR, Caps} <- mod_caps:list_features(C2SState)],
{stop, C2SState};
c2s_handle_info(#{server := Server} = C2SState,
{send_filtered, {pep_message, Feature}, From, To, Packet}) ->
LServer = jid:nameprep(Server),
case mod_caps:get_user_caps(To, C2SState) of
{ok, Caps} ->
Features = mod_caps:get_features(LServer, Caps),
case lists:member(Feature, Features) of
true ->
NewPacket = xmpp:set_from_to(Packet, From, To),
ejabberd_router:route(NewPacket);
false ->
ok
end;
error ->
ok
c2s_handle_info(#{lserver := LServer} = C2SState,
{pep_message, Feature, Packet, USR}) ->
case mod_caps:get_user_caps(USR, C2SState) of
{ok, Caps} -> maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet);
error -> ok
end,
{stop, C2SState};
c2s_handle_info(C2SState, _) ->
C2SState.
send_items(Host, Node, Nidx, Type, Options, LJID, Number) ->
send_items(Host, Node, Nidx, Type, Options, Host, LJID, LJID, Number).
send_items(Host, Node, Nidx, Type, Options, Publisher, SubLJID, ToLJID, Number) ->
case get_last_items(Host, Type, Nidx, SubLJID, Number) of
[] ->
ok;
Items ->
Stanza = items_event_stanza(Node, Options, Items),
send_stanza(Publisher, ToLJID, Node, Stanza)
end.
send_stanza({LUser, LServer, _} = Publisher, USR, Node, BaseStanza) ->
Stanza = xmpp:set_from(BaseStanza, jid:make(LUser, LServer)),
USRs = case USR of
{PUser, PServer, <<>>} ->
[{PUser, PServer, PRessource}
|| PRessource <- user_resources(PUser, PServer)];
_ ->
[USR]
end,
[ejabberd_sm:route(jid:make(Publisher),
{pep_message, <<((Node))/binary, "+notify">>,
add_extended_headers(
Stanza, extended_headers([Publisher])),
To}) || To <- USRs];
send_stanza(Host, USR, _Node, Stanza) ->
ejabberd_router:route(
xmpp:set_from_to(Stanza, service_jid(Host), jid:make(USR))).
maybe_send_pep_stanza(LServer, USR, Caps, Feature, Packet) ->
Features = mod_caps:get_features(LServer, Caps),
case lists:member(Feature, Features) of
true ->
ejabberd_router:route(xmpp:set_to(Packet, jid:make(USR)));
false ->
ok
end.
send_last_items(JID) ->
ServerHost = JID#jid.lserver,
Host = host(ServerHost),
DBType = config(ServerHost, db_type),
LJID = jid:tolower(JID),
BJID = jid:remove_resource(LJID),
lists:foreach(
fun(PType) ->
Subs = get_subscriptions_for_send_last(Host, PType, DBType, JID, LJID, BJID),
lists:foreach(
fun({#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx,
options = Options}, _, SubJID})
when Type == PType->
send_items(Host, Node, Nidx, PType, Options, Host, SubJID, LJID, 1);
(_) ->
ok
end,
lists:usort(Subs))
end, config(ServerHost, plugins)).
% pep_from_offline hack can not work anymore, as sender c2s does not
% exists when sender is offline, so we can't get match receiver caps
% does it make sens to send PEP from an offline contact anyway ?
% case config(ServerHost, ignore_pep_from_offline) of
% false ->
% Roster = ejabberd_hooks:run_fold(roster_get, ServerHost, [],
% [{JID#jid.luser, ServerHost}]),
% lists:foreach(
% fun(#roster{jid = {U, S, R}, subscription = Sub})
% when Sub == both orelse Sub == from,
% S == ServerHost ->
% case user_resources(U, S) of
% [] -> send_last_pep(jid:make(U, S, R), JID);
% _ -> ok %% this is already handled by presence probe
% end;
% (_) ->
% ok %% we can not do anything in any cases
% end, Roster);
% true ->
% ok
% end.
send_last_pep(From, To) ->
ServerHost = From#jid.lserver,
Host = host(ServerHost),
Publisher = jid:tolower(From),
Owner = jid:remove_resource(Publisher),
lists:foreach(
fun(#pubsub_node{nodeid = {_, Node}, type = Type, id = Nidx, options = Options}) ->
case match_option(Options, send_last_published_item, on_sub_and_presence) of
true ->
LJID = jid:tolower(To),
Subscribed = case get_option(Options, access_model) of
open -> true;
presence -> true;
whitelist -> false; % subscribers are added manually
authorize -> false; % likewise
roster ->
Grps = get_option(Options, roster_groups_allowed, []),
{OU, OS, _} = Owner,
element(2, get_roster_info(OU, OS, LJID, Grps))
end,
if Subscribed -> send_items(Owner, Node, Nidx, Type, Options, Publisher, LJID, LJID, 1);
true -> ok
end;
_ ->
ok
end
end,
tree_action(Host, get_nodes, [Owner, From])).
subscribed_nodes_by_jid(NotifyType, SubsByDepth) ->
NodesToDeliver = fun (Depth, Node, Subs, Acc) ->
NodeName = case Node#pubsub_node.nodeid of
@@ -3129,10 +3096,10 @@ get_option(Options, Var, Def) ->
-spec node_options(host(), binary()) -> [{atom(), any()}].
node_options(Host, Type) ->
case config(Host, default_node_config) of
undefined -> node_plugin_options(Host, Type);
[] -> node_plugin_options(Host, Type);
Config -> Config
DefaultOpts = node_plugin_options(Host, Type),
case config(Host, plugins) of
[Type|_] -> config(Host, default_node_config, DefaultOpts);
_ -> DefaultOpts
end.
-spec node_plugin_options(host(), binary()) -> [{atom(), any()}].
@@ -3146,13 +3113,6 @@ node_plugin_options(Host, Type) ->
Result
end.
-spec filter_node_options([{atom(), any()}], [{atom(), any()}]) -> [{atom(), any()}].
filter_node_options(Options, BaseOptions) ->
lists:foldl(fun({Key, Val}, Acc) ->
DefaultValue = proplists:get_value(Key, Options, Val),
[{Key, DefaultValue}|Acc]
end, [], BaseOptions).
-spec node_owners_action(host(), binary(), nodeIdx(), [ljid()]) -> [ljid()].
node_owners_action(Host, Type, Nidx, []) ->
case node_action(Host, Type, get_node_affiliations, [Nidx]) of
@@ -3179,9 +3139,6 @@ node_owners_call(_Host, _Type, _Nidx, Owners) ->
%% @doc <p>Return the maximum number of items for a given node.</p>
%% <p>Unlimited means that there is no limit in the number of items that can
%% be stored.</p>
%% @todo In practice, the current data structure means that we cannot manage
%% millions of items on a given node. This should be addressed in a new
%% version.
-spec max_items(host(), [{atom(), any()}]) -> non_neg_integer().
max_items(Host, Options) ->
case get_option(Options, persist_items) of
@@ -3240,8 +3197,8 @@ set_configure(Host, Node, From, Config, Lang) ->
case tree_call(Host,
set_node,
[N#pubsub_node{options = NewOpts}]) of
{result, Nidx} -> {result, ok};
ok -> {result, ok};
{result, Nidx} -> {result, NewOpts};
ok -> {result, NewOpts};
Err -> Err
end;
_ ->
@@ -3250,10 +3207,9 @@ set_configure(Host, Node, From, Config, Lang) ->
end
end,
case transaction(Host, Node, Action, transaction) of
{result, {TNode, ok}} ->
{result, {TNode, Options}} ->
Nidx = TNode#pubsub_node.id,
Type = TNode#pubsub_node.type,
Options = TNode#pubsub_node.options,
broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang),
{result, undefined};
Other ->
@@ -3261,11 +3217,11 @@ set_configure(Host, Node, From, Config, Lang) ->
end.
-spec merge_config([proplists:property()], [proplists:property()]) -> [proplists:property()].
merge_config(Config1, Config2) ->
merge_config(CustomConfig, DefaultConfig) ->
lists:foldl(
fun({Opt, Val}, Acc) ->
lists:keystore(Opt, 1, Acc, {Opt, Val})
end, Config2, Config1).
end, DefaultConfig, CustomConfig).
-spec decode_node_config(undefined | xdata(), binary(), binary()) ->
pubsub_node_config:result() |
@@ -3785,14 +3741,6 @@ subid_shim(SubIds) ->
extended_headers(Jids) ->
[#address{type = replyto, jid = Jid} || Jid <- Jids].
-spec on_user_offline(ejabberd_sm:sid(), jid(), ejabberd_sm:info()) -> ok.
on_user_offline(_, JID, _) ->
{User, Server, Resource} = jid:tolower(JID),
case user_resources(User, Server) of
[] -> purge_offline({User, Server, Resource});
_ -> ok
end.
-spec purge_offline(ljid()) -> ok.
purge_offline(LJID) ->
Host = host(element(2, LJID)),
@@ -3833,7 +3781,7 @@ purge_offline(LJID) ->
end
end, lists:usort(lists:flatten(Affs)));
{Error, _} ->
?DEBUG("on_user_offline ~p", [Error])
?ERROR_MSG("can not purge offline: ~p", [Error])
end.
-spec purge_offline(host(), ljid(), binary()) -> ok | {error, stanza_error()}.
@@ -3845,13 +3793,13 @@ purge_offline(Host, LJID, Node) ->
{result, {[], _}} ->
ok;
{result, {Items, _}} ->
{User, Server, _} = LJID,
{User, Server, Resource} = LJID,
PublishModel = get_option(Options, publish_model),
ForceNotify = get_option(Options, notify_retract),
{_, NodeId} = Node#pubsub_node.nodeid,
lists:foreach(fun
(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}})
when (U == User) and (S == Server) ->
(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, R}}})
when (U == User) and (S == Server) and (R == Resource) ->
case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of
{result, {_, broadcast}} ->
broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify),
@@ -3871,9 +3819,14 @@ purge_offline(Host, LJID, Node) ->
Error
end.
export(Server) ->
pubsub_db_sql:export(Server).
mod_opt_type(access_createnode) -> fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(hosts) ->
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(ignore_pep_from_offline) ->
fun (A) when is_boolean(A) -> A end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
@@ -3892,7 +3845,7 @@ mod_opt_type(pep_mapping) ->
mod_opt_type(plugins) ->
fun (A) when is_list(A) -> A end;
mod_opt_type(_) ->
[access_createnode, db_type, host,
[access_createnode, db_type, host, hosts,
ignore_pep_from_offline, iqdisc, last_item_cache,
max_items_node, nodetree, pep_mapping, plugins,
max_subscriptions_node, default_node_config].
+596
View File
@@ -0,0 +1,596 @@
%%%----------------------------------------------------------------------
%%% File : mod_push.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Purpose : Push Notifications (XEP-0357)
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_push).
-author('holger@zedat.fu-berlin.de').
-protocol({xep, 357, '0.2'}).
-behavior(gen_mod).
%% gen_mod callbacks.
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
%% ejabberd_hooks callbacks.
-export([disco_sm_features/5, c2s_session_pending/1, c2s_copy_session/2,
c2s_handle_cast/2, c2s_stanza/3, mam_message/6, offline_message/1,
remove_user/2]).
%% gen_iq_handler callback.
-export([process_iq/1]).
%% ejabberd command.
-export([get_commands_spec/0, delete_old_sessions/1]).
%% API (used by mod_push_keepalive).
-export([notify/1, notify/3, notify/5]).
-include("ejabberd.hrl").
-include("ejabberd_commands.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
-define(PUSH_CACHE, push_cache).
-type c2s_state() :: ejabberd_c2s:state().
-type timestamp() :: erlang:timestamp().
-type push_session() :: {timestamp(), ljid(), binary(), xdata()}.
-callback init(binary(), gen_mod:opts())
-> any().
-callback store_session(binary(), binary(), timestamp(), jid(), binary(),
xdata())
-> {ok, push_session()} | error.
-callback lookup_session(binary(), binary(), jid(), binary())
-> {ok, push_session()} | error.
-callback lookup_session(binary(), binary(), timestamp())
-> {ok, push_session()} | error.
-callback lookup_sessions(binary(), binary(), jid())
-> {ok, [push_session()]} | error.
-callback lookup_sessions(binary(), binary())
-> {ok, [push_session()]} | error.
-callback lookup_sessions(binary())
-> {ok, [push_session()]} | error.
-callback delete_session(binary(), binary(), timestamp())
-> ok | error.
-callback delete_old_sessions(binary() | global, erlang:timestamp())
-> any().
-callback use_cache(binary())
-> boolean().
-callback cache_nodes(binary())
-> [node()].
-optional_callbacks([use_cache/1, cache_nodes/1]).
%%--------------------------------------------------------------------
%% gen_mod callbacks.
%%--------------------------------------------------------------------
-spec start(binary(), gen_mod:opts()) -> ok.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
init_cache(Mod, Host, Opts),
register_iq_handlers(Host, IQDisc),
register_hooks(Host),
ejabberd_commands:register_commands(get_commands_spec()).
-spec stop(binary()) -> ok.
stop(Host) ->
unregister_hooks(Host),
unregister_iq_handlers(Host),
ejabberd_commands:unregister_commands(get_commands_spec()).
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
reload(Host, NewOpts, OldOpts) ->
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
if NewMod /= OldMod ->
NewMod:init(Host, NewOpts);
true ->
ok
end,
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts,
gen_iq_handler:iqdisc(Host)) of
{false, IQDisc, _} ->
register_iq_handlers(Host, IQDisc);
true ->
ok
end.
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[].
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
mod_opt_type(db_type) ->
fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun(I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(iqdisc) ->
fun gen_iq_handler:check_type/1;
mod_opt_type(_) ->
[db_type, cache_life_time, cache_size, use_cache, cache_missed, iqdisc].
%%--------------------------------------------------------------------
%% ejabberd command callback.
%%--------------------------------------------------------------------
-spec get_commands_spec() -> [ejabberd_commands()].
get_commands_spec() ->
[#ejabberd_commands{name = delete_old_push_sessions, tags = [purge],
desc = "Remove push sessions older than DAYS",
module = ?MODULE, function = delete_old_sessions,
args = [{days, integer}],
result = {res, rescode}}].
-spec delete_old_sessions(non_neg_integer()) -> ok | any().
delete_old_sessions(Days) ->
CurrentTime = p1_time_compat:system_time(micro_seconds),
Diff = Days * 24 * 60 * 60 * 1000000,
TimeStamp = misc:usec_to_now(CurrentTime - Diff),
DBTypes = lists:usort(
lists:map(
fun(Host) ->
case gen_mod:db_type(Host, ?MODULE) of
sql -> {sql, Host};
Other -> {Other, global}
end
end, ?MYHOSTS)),
Results = lists:map(
fun({DBType, Host}) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:delete_old_sessions(Host, TimeStamp)
end, DBTypes),
ets_cache:clear(?PUSH_CACHE, ejabberd_cluster:get_nodes()),
case lists:filter(fun(Res) -> Res /= ok end, Results) of
[] ->
?INFO_MSG("Deleted push sessions older than ~B days", [Days]),
ok;
[NotOk | _] ->
?ERROR_MSG("Error while deleting old push sessions: ~p", [NotOk]),
NotOk
end.
%%--------------------------------------------------------------------
%% Register/unregister hooks.
%%--------------------------------------------------------------------
-spec register_hooks(binary()) -> ok.
register_hooks(Host) ->
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
disco_sm_features, 50),
ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
c2s_session_pending, 50),
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50),
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
c2s_handle_cast, 50),
ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
c2s_stanza, 50),
ejabberd_hooks:add(store_mam_message, Host, ?MODULE,
mam_message, 50),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE,
offline_message, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50).
-spec unregister_hooks(binary()) -> ok.
unregister_hooks(Host) ->
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
disco_sm_features, 50),
ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
c2s_session_pending, 50),
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50),
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
c2s_handle_cast, 50),
ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
c2s_stanza, 50),
ejabberd_hooks:delete(store_mam_message, Host, ?MODULE,
mam_message, 50),
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE,
offline_message, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50).
%%--------------------------------------------------------------------
%% Service discovery.
%%--------------------------------------------------------------------
-spec disco_sm_features(empty | {result, [binary()]} | {error, stanza_error()},
jid(), jid(), binary(), binary())
-> {result, [binary()]} | {error, stanza_error()}.
disco_sm_features(empty, From, To, Node, Lang) ->
disco_sm_features({result, []}, From, To, Node, Lang);
disco_sm_features({result, OtherFeatures},
#jid{luser = U, lserver = S},
#jid{luser = U, lserver = S}, <<"">>, _Lang) ->
{result, [?NS_PUSH_0 | OtherFeatures]};
disco_sm_features(Acc, _From, _To, _Node, _Lang) ->
Acc.
%%--------------------------------------------------------------------
%% IQ handlers.
%%--------------------------------------------------------------------
-spec register_iq_handlers(binary(), gen_iq_handler:type()) -> ok.
register_iq_handlers(Host, IQDisc) ->
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0,
?MODULE, process_iq, IQDisc).
-spec unregister_iq_handlers(binary()) -> ok.
unregister_iq_handlers(Host) ->
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_PUSH_0).
-spec process_iq(iq()) -> iq().
process_iq(#iq{type = get, lang = Lang} = IQ) ->
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_iq(#iq{lang = Lang, sub_els = [#push_enable{node = <<>>}]} = IQ) ->
Txt = <<"Enabling push without 'node' attribute is not supported">>,
xmpp:make_error(IQ, xmpp:err_feature_not_implemented(Txt, Lang));
process_iq(#iq{from = #jid{lserver = LServer} = JID,
to = #jid{lserver = LServer},
sub_els = [#push_enable{jid = PushJID,
node = Node,
xdata = XData}]} = IQ) ->
case enable(JID, PushJID, Node, XData) of
ok ->
xmpp:make_iq_result(IQ);
error ->
xmpp:make_error(IQ, xmpp:err_internal_server_error())
end;
process_iq(#iq{from = #jid{lserver = LServer} = JID,
to = #jid{lserver = LServer},
sub_els = [#push_disable{jid = PushJID,
node = Node}]} = IQ) ->
case disable(JID, PushJID, Node) of
ok ->
xmpp:make_iq_result(IQ);
error ->
xmpp:make_error(IQ, xmpp:err_item_not_found())
end;
process_iq(IQ) ->
xmpp:make_error(IQ, xmpp:err_not_allowed()).
-spec enable(jid(), jid(), binary(), xdata()) -> ok | error.
enable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
PushJID, Node, XData) ->
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
{TS, PID} ->
case store_session(LUser, LServer, TS, PushJID, Node, XData) of
{ok, _} ->
?INFO_MSG("Enabling push notifications for ~s",
[jid:encode(JID)]),
ejabberd_c2s:cast(PID, push_enable);
error ->
?ERROR_MSG("Cannot enable push for ~s: database error",
[jid:encode(JID)]),
error
end;
none ->
?WARNING_MSG("Cannot enable push for ~s: session not found",
[jid:encode(JID)]),
error
end.
-spec disable(jid(), jid(), binary() | undefined) -> ok | error.
disable(#jid{luser = LUser, lserver = LServer, lresource = LResource} = JID,
PushJID, Node) ->
case ejabberd_sm:get_session_sid(LUser, LServer, LResource) of
{_TS, PID} ->
?INFO_MSG("Disabling push notifications for ~s",
[jid:encode(JID)]),
ejabberd_c2s:cast(PID, push_disable);
none ->
?WARNING_MSG("Session not found while disabling push for ~s",
[jid:encode(JID)])
end,
if Node /= undefined ->
delete_session(LUser, LServer, PushJID, Node);
true ->
delete_sessions(LUser, LServer, PushJID)
end.
%%--------------------------------------------------------------------
%% Hook callbacks.
%%--------------------------------------------------------------------
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
_Pkt, _SendResult) ->
notify(State),
State;
c2s_stanza(State, _Pkt, _SendResult) ->
State.
-spec mam_message(message() | drop, binary(), binary(), jid(),
chat | groupchat, recv | send) -> message().
mam_message(#message{meta = #{push_notified := true}} = Pkt,
_LUser, _LServer, _Peer, _Type, _Dir) ->
Pkt;
mam_message(#message{} = Pkt, LUser, LServer, _Peer, chat, _Dir) ->
case lookup_sessions(LUser, LServer) of
{ok, [_|_] = Clients} ->
case drop_online_sessions(LUser, LServer, Clients) of
[_|_] = Clients1 ->
?DEBUG("Notifying ~s@~s of MAM message", [LUser, LServer]),
notify(LUser, LServer, Clients1);
[] ->
ok
end;
_ ->
ok
end,
xmpp:put_meta(Pkt, push_notified, true);
mam_message(Pkt, _LUser, _LServer, _Peer, _Type, _Dir) ->
Pkt.
-spec offline_message({any(), message()}) -> {any(), message()}.
offline_message({_Action, #message{meta = #{push_notified := true}}} = Acc) ->
Acc;
offline_message({Action, #message{to = #jid{luser = LUser,
lserver = LServer}} = Pkt}) ->
case lookup_sessions(LUser, LServer) of
{ok, [_|_] = Clients} ->
?DEBUG("Notifying ~s@~s of offline message", [LUser, LServer]),
notify(LUser, LServer, Clients);
_ ->
ok
end,
{Action, xmpp:put_meta(Pkt, push_notified, true)}.
-spec c2s_session_pending(c2s_state()) -> c2s_state().
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
case p1_queue:len(Queue) of
Len when Len > 0 ->
?DEBUG("Notifying client of unacknowledged messages", []),
notify(State),
State;
0 ->
State
end;
c2s_session_pending(State) ->
State.
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
c2s_copy_session(State, #{push_enabled := true}) ->
State#{push_enabled => true};
c2s_copy_session(State, _) ->
State.
-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
c2s_handle_cast(State, push_enable) ->
{stop, State#{push_enabled => true}};
c2s_handle_cast(State, push_disable) ->
{stop, maps:remove(push_enabled, State)};
c2s_handle_cast(State, _Msg) ->
State.
-spec remove_user(binary(), binary()) -> ok | error.
remove_user(LUser, LServer) ->
?INFO_MSG("Removing any push sessions of ~s@~s", [LUser, LServer]),
Mod = gen_mod:db_mod(LServer, ?MODULE),
LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer) end,
delete_sessions(LUser, LServer, LookupFun, Mod).
%%--------------------------------------------------------------------
%% Generate push notifications.
%%--------------------------------------------------------------------
-spec notify(c2s_state()) -> ok.
notify(#{jid := #jid{luser = LUser, lserver = LServer}, sid := {TS, _}}) ->
case lookup_session(LUser, LServer, TS) of
{ok, Client} ->
notify(LUser, LServer, [Client]);
error ->
ok
end.
-spec notify(binary(), binary(), [push_session()]) -> ok.
notify(LUser, LServer, Clients) ->
lists:foreach(
fun({TS, PushLJID, Node, XData}) ->
HandleResponse = fun(#iq{type = result}) ->
ok;
(#iq{type = error}) ->
delete_session(LUser, LServer, TS);
(timeout) ->
ok % Hmm.
end,
notify(LServer, PushLJID, Node, XData, HandleResponse)
end, Clients).
-spec notify(binary(), ljid(), binary(), xdata(),
fun((iq() | timeout) -> any())) -> ok.
notify(LServer, PushLJID, Node, XData, HandleResponse) ->
From = jid:make(LServer),
Item = #ps_item{xml_els = [xmpp:encode(#push_notification{})]},
PubSub = #pubsub{publish = #ps_publish{node = Node, items = [Item]},
publish_options = XData},
IQ = #iq{type = set,
from = From,
to = jid:make(PushLJID),
id = randoms:get_string(),
sub_els = [PubSub]},
ejabberd_local:route_iq(IQ, HandleResponse),
ok.
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
-spec store_session(binary(), binary(), timestamp(), jid(), binary(), xdata())
-> {ok, push_session()} | error.
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
delete_session(LUser, LServer, PushJID, Node),
case use_cache(Mod, LServer) of
true ->
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
cache_nodes(Mod, LServer)),
ets_cache:update(
?PUSH_CACHE,
{LUser, LServer, TS}, {ok, {TS, PushJID, Node, XData}},
fun() ->
Mod:store_session(LUser, LServer, TS, PushJID, Node,
XData)
end, cache_nodes(Mod, LServer));
false ->
Mod:store_session(LUser, LServer, TS, PushJID, Node, XData)
end.
-spec lookup_session(binary(), binary(), timestamp())
-> {ok, push_session()} | error.
lookup_session(LUser, LServer, TS) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?PUSH_CACHE, {LUser, LServer, TS},
fun() -> Mod:lookup_session(LUser, LServer, TS) end);
false ->
Mod:lookup_session(LUser, LServer, TS)
end.
-spec lookup_sessions(binary(), binary()) -> {ok, [push_session()]} | error.
lookup_sessions(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case use_cache(Mod, LServer) of
true ->
ets_cache:lookup(
?PUSH_CACHE, {LUser, LServer},
fun() -> Mod:lookup_sessions(LUser, LServer) end);
false ->
Mod:lookup_sessions(LUser, LServer)
end.
-spec delete_session(binary(), binary(), timestamp()) -> ok | error.
delete_session(LUser, LServer, TS) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
ok = Mod:delete_session(LUser, LServer, TS),
case use_cache(Mod, LServer) of
true ->
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
cache_nodes(Mod, LServer)),
ets_cache:delete(?PUSH_CACHE, {LUser, LServer, TS},
cache_nodes(Mod, LServer));
false ->
ok
end.
-spec delete_session(binary(), binary(), jid(), binary()) -> ok | error.
delete_session(LUser, LServer, PushJID, Node) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:lookup_session(LUser, LServer, PushJID, Node) of
{ok, {TS, _, _, _}} ->
delete_session(LUser, LServer, TS);
error ->
error
end.
-spec delete_sessions(binary(), binary(), jid()) -> ok | error.
delete_sessions(LUser, LServer, PushJID) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
LookupFun = fun() -> Mod:lookup_sessions(LUser, LServer, PushJID) end,
delete_sessions(LUser, LServer, LookupFun, Mod).
-spec delete_sessions(binary(), binary(), fun(() -> ok | error), module())
-> ok | error.
delete_sessions(LUser, LServer, LookupFun, Mod) ->
case LookupFun() of
{ok, Clients} ->
case use_cache(Mod, LServer) of
true ->
ets_cache:delete(?PUSH_CACHE, {LUser, LServer},
cache_nodes(Mod, LServer));
false ->
ok
end,
lists:foreach(
fun({TS, _, _, _}) ->
ok = Mod:delete_session(LUser, LServer, TS),
case use_cache(Mod, LServer) of
true ->
ets_cache:delete(?PUSH_CACHE,
{LUser, LServer, TS},
cache_nodes(Mod, LServer));
false ->
ok
end
end, Clients);
error ->
error
end.
-spec drop_online_sessions(binary(), binary(), [push_session()])
-> [push_session()].
drop_online_sessions(LUser, LServer, Clients) ->
SessIDs = ejabberd_sm:get_session_sids(LUser, LServer),
[Client || {TS, _, _, _} = Client <- Clients,
lists:keyfind(TS, 1, SessIDs) == false].
%%--------------------------------------------------------------------
%% Caching.
%%--------------------------------------------------------------------
-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
init_cache(Mod, Host, Opts) ->
case use_cache(Mod, Host) of
true ->
CacheOpts = cache_opts(Host, Opts),
ets_cache:new(?PUSH_CACHE, CacheOpts);
false ->
ets_cache:delete(?PUSH_CACHE)
end.
-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
cache_opts(Host, Opts) ->
MaxSize = gen_mod:get_opt(
cache_size, Opts,
ejabberd_config:cache_size(Host)),
CacheMissed = gen_mod:get_opt(
cache_missed, Opts,
ejabberd_config:cache_missed(Host)),
LifeTime = case gen_mod:get_opt(
cache_life_time, Opts,
ejabberd_config:cache_life_time(Host)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(Host);
false ->
gen_mod:get_module_opt(
Host, ?MODULE, use_cache,
ejabberd_config:use_cache(Host))
end.
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, Host) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(Host);
false -> ejabberd_cluster:get_nodes()
end.
+236
View File
@@ -0,0 +1,236 @@
%%%----------------------------------------------------------------------
%%% File : mod_push_keepalive.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Purpose : Keep pending XEP-0198 sessions alive with XEP-0357
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_push_keepalive).
-author('holger@zedat.fu-berlin.de').
-behavior(gen_mod).
%% gen_mod callbacks.
-export([start/2, stop/1, reload/3, mod_opt_type/1, depends/2]).
%% ejabberd_hooks callbacks.
-export([c2s_session_pending/1, c2s_session_resumed/1, c2s_copy_session/2,
c2s_handle_cast/2, c2s_handle_info/2, c2s_stanza/3]).
-include("logger.hrl").
-include("xmpp.hrl").
-define(PUSH_BEFORE_TIMEOUT_SECS, 120).
-type c2s_state() :: ejabberd_c2s:state().
%%--------------------------------------------------------------------
%% gen_mod callbacks.
%%--------------------------------------------------------------------
-spec start(binary(), gen_mod:opts()) -> ok.
start(Host, Opts) ->
case gen_mod:get_opt(wake_on_start, Opts, false) of
true ->
wake_all(Host);
false ->
ok
end,
register_hooks(Host).
-spec stop(binary()) -> ok.
stop(Host) ->
unregister_hooks(Host).
-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
reload(Host, NewOpts, OldOpts) ->
case gen_mod:is_equal_opt(wake_on_start, NewOpts, OldOpts, false) of
{false, true, _} ->
wake_all(Host);
_ ->
ok
end,
ok.
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
[{mod_push, hard},
{mod_client_state, soft},
{mod_stream_mgmt, soft}].
-spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
mod_opt_type(resume_timeout) ->
fun(I) when is_integer(I), I >= 0 -> I;
(undefined) -> undefined
end;
mod_opt_type(wake_on_start) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(wake_on_timeout) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(O) when O == cache_life_time; O == cache_size ->
fun(I) when is_integer(I), I > 0 -> I;
(infinity) -> infinity
end;
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[resume_timeout, wake_on_start, wake_on_timeout, cache_life_time,
cache_size, use_cache, cache_missed, iqdisc].
%%--------------------------------------------------------------------
%% Register/unregister hooks.
%%--------------------------------------------------------------------
-spec register_hooks(binary()) -> ok.
register_hooks(Host) ->
ejabberd_hooks:add(c2s_session_pending, Host, ?MODULE,
c2s_session_pending, 50),
ejabberd_hooks:add(c2s_session_resumed, Host, ?MODULE,
c2s_session_resumed, 50),
ejabberd_hooks:add(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50),
ejabberd_hooks:add(c2s_handle_cast, Host, ?MODULE,
c2s_handle_cast, 40),
ejabberd_hooks:add(c2s_handle_info, Host, ?MODULE,
c2s_handle_info, 50),
ejabberd_hooks:add(c2s_handle_send, Host, ?MODULE,
c2s_stanza, 50).
-spec unregister_hooks(binary()) -> ok.
unregister_hooks(Host) ->
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
disco_sm_features, 50),
ejabberd_hooks:delete(c2s_session_pending, Host, ?MODULE,
c2s_session_pending, 50),
ejabberd_hooks:delete(c2s_session_resumed, Host, ?MODULE,
c2s_session_resumed, 50),
ejabberd_hooks:delete(c2s_copy_session, Host, ?MODULE,
c2s_copy_session, 50),
ejabberd_hooks:delete(c2s_handle_cast, Host, ?MODULE,
c2s_handle_cast, 40),
ejabberd_hooks:delete(c2s_handle_info, Host, ?MODULE,
c2s_handle_info, 50),
ejabberd_hooks:delete(c2s_handle_send, Host, ?MODULE,
c2s_stanza, 50).
%%--------------------------------------------------------------------
%% Hook callbacks.
%%--------------------------------------------------------------------
-spec c2s_stanza(c2s_state(), xmpp_element() | xmlel(), term()) -> c2s_state().
c2s_stanza(#{push_enabled := true, mgmt_state := pending} = State,
_Pkt, _SendResult) ->
maybe_restore_resume_timeout(State);
c2s_stanza(State, _Pkt, _SendResult) ->
State.
-spec c2s_session_pending(c2s_state()) -> c2s_state().
c2s_session_pending(#{push_enabled := true, mgmt_queue := Queue} = State) ->
case p1_queue:len(Queue) of
0 ->
State1 = maybe_adjust_resume_timeout(State),
maybe_start_wakeup_timer(State1);
_ ->
State
end;
c2s_session_pending(State) ->
State.
-spec c2s_session_resumed(c2s_state()) -> c2s_state().
c2s_session_resumed(#{push_enabled := true} = State) ->
maybe_restore_resume_timeout(State);
c2s_session_resumed(State) ->
State.
-spec c2s_copy_session(c2s_state(), c2s_state()) -> c2s_state().
c2s_copy_session(State, #{push_enabled := true,
push_resume_timeout := ResumeTimeout,
push_wake_on_timeout := WakeOnTimeout}) ->
State#{push_resume_timeout => ResumeTimeout,
push_wake_on_timeout => WakeOnTimeout};
c2s_copy_session(State, _) ->
State.
-spec c2s_handle_cast(c2s_state(), any()) -> c2s_state().
c2s_handle_cast(#{lserver := LServer} = State, push_enable) ->
ResumeTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
resume_timeout, 86400),
WakeOnTimeout = gen_mod:get_module_opt(LServer, ?MODULE,
wake_on_timeout, true),
State#{push_resume_timeout => ResumeTimeout,
push_wake_on_timeout => WakeOnTimeout};
c2s_handle_cast(State, push_disable) ->
State1 = maps:remove(push_resume_timeout, State),
maps:remove(push_wake_on_timeout, State1);
c2s_handle_cast(State, _Msg) ->
State.
-spec c2s_handle_info(c2s_state(), any()) -> c2s_state() | {stop, c2s_state()}.
c2s_handle_info(#{push_enabled := true, mgmt_state := pending,
jid := JID} = State, {timeout, _, push_keepalive}) ->
?INFO_MSG("Waking ~s before session times out", [jid:encode(JID)]),
mod_push:notify(State),
{stop, State};
c2s_handle_info(State, _) ->
State.
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
-spec maybe_adjust_resume_timeout(c2s_state()) -> c2s_state().
maybe_adjust_resume_timeout(#{push_resume_timeout := undefined} = State) ->
State;
maybe_adjust_resume_timeout(#{push_resume_timeout := Timeout} = State) ->
OrigTimeout = mod_stream_mgmt:get_resume_timeout(State),
?DEBUG("Adjusting resume timeout to ~B seconds", [Timeout]),
State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
State1#{push_resume_timeout_orig => OrigTimeout}.
-spec maybe_restore_resume_timeout(c2s_state()) -> c2s_state().
maybe_restore_resume_timeout(#{push_resume_timeout_orig := Timeout} = State) ->
?DEBUG("Restoring resume timeout to ~B seconds", [Timeout]),
State1 = mod_stream_mgmt:set_resume_timeout(State, Timeout),
maps:remove(push_resume_timeout_orig, State1);
maybe_restore_resume_timeout(State) ->
State.
-spec maybe_start_wakeup_timer(c2s_state()) -> c2s_state().
maybe_start_wakeup_timer(#{push_wake_on_timeout := true,
push_resume_timeout := ResumeTimeout} = State)
when is_integer(ResumeTimeout), ResumeTimeout > ?PUSH_BEFORE_TIMEOUT_SECS ->
WakeTimeout = ResumeTimeout - ?PUSH_BEFORE_TIMEOUT_SECS,
?DEBUG("Scheduling wake-up timer to fire in ~B seconds", [WakeTimeout]),
erlang:start_timer(timer:seconds(WakeTimeout), self(), push_keepalive),
State;
maybe_start_wakeup_timer(State) ->
State.
-spec wake_all(binary()) -> ok | error.
wake_all(LServer) ->
?INFO_MSG("Waking all push clients on ~s", [LServer]),
Mod = gen_mod:db_mod(LServer, mod_push),
case Mod:lookup_sessions(LServer) of
{ok, Sessions} ->
IgnoreResponse = fun(_) -> ok end,
lists:foreach(fun({_, PushLJID, Node, XData}) ->
mod_push:notify(LServer, PushLJID, Node,
XData, IgnoreResponse)
end, Sessions);
error ->
error
end.
+204
View File
@@ -0,0 +1,204 @@
%%%----------------------------------------------------------------------
%%% File : mod_push_mnesia.erl
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
%%% Purpose : Mnesia backend for Push Notifications (XEP-0357)
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
%%%
%%%
%%% ejabberd, Copyright (C) 2017 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(mod_push_mnesia).
-author('holger@zedat.fu-berlin.de').
-behavior(mod_push).
%% API
-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
delete_session/3, delete_old_sessions/2]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
-record(push_session,
{us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = p1_time_compat:timestamp() :: erlang:timestamp(),
service = {<<"">>, <<"">>, <<"">>} :: ljid(),
node = <<"">> :: binary(),
xdata = #xdata{} :: xdata()}).
%%%-------------------------------------------------------------------
%%% API
%%%-------------------------------------------------------------------
init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, push_session,
[{disc_only_copies, [node()]},
{type, bag},
{attributes, record_info(fields, push_session)}]).
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
US = {LUser, LServer},
PushLJID = jid:tolower(PushJID),
MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer),
F = fun() ->
if is_integer(MaxSessions) ->
enforce_max_sessions(US, MaxSessions - 1);
MaxSessions == infinity ->
ok
end,
mnesia:write(#push_session{us = US,
timestamp = TS,
service = PushLJID,
node = Node,
xdata = XData})
end,
case mnesia:transaction(F) of
{atomic, ok} ->
{ok, {TS, PushLJID, Node, XData}};
{aborted, E} ->
?ERROR_MSG("Cannot store push session for ~s@~s: ~p",
[LUser, LServer, E]),
error
end.
lookup_session(LUser, LServer, PushJID, Node) ->
PushLJID = jid:tolower(PushJID),
MatchSpec = ets:fun2ms(
fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
when U == LUser,
S == LServer,
P == PushLJID,
N == Node ->
Rec
end),
case mnesia:dirty_select(push_session, MatchSpec) of
[#push_session{timestamp = TS, xdata = XData}] ->
{ok, {TS, PushLJID, Node, XData}};
_ ->
?DEBUG("No push session found for ~s@~s (~p, ~s)",
[LUser, LServer, PushJID, Node]),
error
end.
lookup_session(LUser, LServer, TS) ->
MatchSpec = ets:fun2ms(
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
when U == LUser,
S == LServer,
T == TS ->
Rec
end),
case mnesia:dirty_select(push_session, MatchSpec) of
[#push_session{service = PushLJID, node = Node, xdata = XData}] ->
{ok, {TS, PushLJID, Node, XData}};
_ ->
?DEBUG("No push session found for ~s@~s (~p)",
[LUser, LServer, TS]),
error
end.
lookup_sessions(LUser, LServer, PushJID) ->
PushLJID = jid:tolower(PushJID),
MatchSpec = ets:fun2ms(
fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
when U == LUser,
S == LServer,
P == PushLJID ->
Rec
end),
{ok, mnesia:dirty_select(push_session, MatchSpec)}.
lookup_sessions(LUser, LServer) ->
Records = mnesia:dirty_read(push_session, {LUser, LServer}),
Clients = [{TS, PushLJID, Node, XData}
|| #push_session{timestamp = TS,
service = PushLJID,
node = Node,
xdata = XData} <- Records],
{ok, Clients}.
lookup_sessions(LServer) ->
MatchSpec = ets:fun2ms(
fun(#push_session{us = {_U, S},
timestamp = TS,
service = PushLJID,
node = Node,
xdata = XData})
when S == LServer ->
{TS, PushLJID, Node, XData}
end),
{ok, mnesia:dirty_select(push_session, MatchSpec)}.
delete_session(LUser, LServer, TS) ->
MatchSpec = ets:fun2ms(
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
when U == LUser,
S == LServer,
T == TS ->
Rec
end),
F = fun() ->
Recs = mnesia:select(push_session, MatchSpec),
lists:foreach(fun mnesia:delete_object/1, Recs)
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{aborted, E} ->
?ERROR_MSG("Cannot delete push session of ~s@~s: ~p",
[LUser, LServer, E]),
error
end.
delete_old_sessions(_LServer, Time) ->
DelIfOld = fun(#push_session{timestamp = T} = Rec, ok) when T < Time ->
mnesia:delete_object(Rec);
(_Rec, ok) ->
ok
end,
F = fun() ->
mnesia:foldl(DelIfOld, ok, push_session)
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{aborted, E} ->
?ERROR_MSG("Cannot delete old push sessions: ~p", [E]),
error
end.
%%--------------------------------------------------------------------
%% Internal functions.
%%--------------------------------------------------------------------
-spec enforce_max_sessions({binary(), binary()}, non_neg_integer()) -> ok.
enforce_max_sessions({U, S} = US, Max) ->
Recs = mnesia:wread({push_session, US}),
NumRecs = length(Recs),
if NumRecs > Max ->
NumOldRecs = NumRecs - Max,
Recs1 = lists:keysort(#push_session.timestamp, Recs),
Recs2 = lists:reverse(Recs1),
OldRecs = lists:sublist(Recs2, Max + 1, NumOldRecs),
?INFO_MSG("Disabling ~B old push session(s) of ~s@~s",
[NumOldRecs, U, S]),
lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs);
true ->
ok
end.
+15 -6
View File
@@ -127,7 +127,7 @@ process_iq(#iq{from = From, to = To} = IQ, Source) ->
process_iq(#iq{type = set, lang = Lang,
sub_els = [#register{remove = true}]} = IQ,
_Source, _IsCaptchaEnabled, _AllowRemove = false) ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang));
process_iq(#iq{type = set, lang = Lang, to = To, from = From,
sub_els = [#register{remove = true,
@@ -210,7 +210,14 @@ process_iq(#iq{type = get, from = From, to = To, id = ID, lang = Lang} = IQ,
Instr = translate:translate(
Lang, <<"Choose a username and password to register "
"with this server">>),
if IsCaptchaEnabled and not IsRegistered ->
URL = gen_mod:get_module_opt(Server, ?MODULE, redirect_url, <<"">>),
if (URL /= <<"">>) and not IsRegistered ->
Txt = translate:translate(Lang, <<"To register, visit ~s">>),
Desc = str:format(Txt, [URL]),
xmpp:make_iq_result(
IQ, #register{instructions = Desc,
sub_els = [#oob_x{url = URL}]});
IsCaptchaEnabled and not IsRegistered ->
TopInstr = translate:translate(
Lang, <<"You need a client that supports x:data "
"and CAPTCHA to register">>),
@@ -263,7 +270,7 @@ try_register_or_set_password(User, Server, Password,
xmpp:make_error(IQ, Error)
end;
deny ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_forbidden(Txt, Lang))
end;
_ ->
@@ -315,8 +322,8 @@ try_register(User, Server, Password, SourceRaw, Lang) ->
case {acl:match_rule(Server, Access, JID),
check_ip_access(SourceRaw, IPAccess)}
of
{deny, _} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{_, deny} -> {error, xmpp:err_forbidden(<<"Denied by ACL">>, Lang)};
{deny, _} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
{_, deny} -> {error, xmpp:err_forbidden(<<"Access denied by service policy">>, Lang)};
{allow, allow} ->
Source = may_remove_resource(SourceRaw),
case check_timeout(Source) of
@@ -614,9 +621,11 @@ mod_opt_type({welcome_message, subject}) ->
fun iolist_to_binary/1;
mod_opt_type({welcome_message, body}) ->
fun iolist_to_binary/1;
mod_opt_type(redirect_url) ->
fun iolist_to_binary/1;
mod_opt_type(_) ->
[access, access_from, access_remove, captcha_protected, ip_access,
iqdisc, password_strength, registration_watchers,
iqdisc, password_strength, registration_watchers, redirect_url,
{welcome_message, subject}, {welcome_message, body}].
-spec opt_type(registration_timeout) -> fun((timeout()) -> timeout());
+4 -8
View File
@@ -35,7 +35,6 @@
-module(mod_roster).
-protocol({xep, 237, '1.3'}).
-protocol({xep, 321, '0.1'}).
-author('alexey@process-one.net').
@@ -185,7 +184,7 @@ process_local_iq(#iq{type = set, from = From, lang = Lang,
Access = gen_mod:get_module_opt(Server, ?MODULE, access, all),
case acl:match_rule(Server, Access, From) of
deny ->
Txt = <<"Denied by ACL">>,
Txt = <<"Access denied by service policy">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
allow ->
process_iq_set(IQ)
@@ -441,14 +440,13 @@ decode_item(Item, R, Managed) ->
end,
groups = Item#roster_item.groups}.
process_iq_set(#iq{from = From, to = To,
process_iq_set(#iq{from = _From, to = To,
sub_els = [#roster_query{items = [QueryItem]}]} = IQ) ->
#jid{user = User, luser = LUser, lserver = LServer} = To,
Managed = {From#jid.luser, From#jid.lserver} /= {LUser, LServer},
LJID = jid:tolower(QueryItem#roster_item.jid),
F = fun () ->
Item = get_roster_item(LUser, LServer, LJID),
Item2 = decode_item(QueryItem, Item, Managed),
Item2 = decode_item(QueryItem, Item, false),
Item3 = ejabberd_hooks:run_fold(roster_process_item,
LServer, Item2,
[LServer]),
@@ -1200,8 +1198,6 @@ mod_opt_type(access) ->
fun acl:access_rules_validator/1;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(managers) ->
fun (B) when is_list(B) -> B end;
mod_opt_type(store_current_id) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(versioning) ->
@@ -1213,5 +1209,5 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[access, db_type, iqdisc, managers, store_current_id,
[access, db_type, iqdisc, store_current_id,
versioning, cache_life_time, cache_size, use_cache, cache_missed].
+1 -1
View File
@@ -320,7 +320,7 @@ check_from_to(From, To) ->
-spec mk_error(term()) -> stanza_error().
mk_error(forbidden) ->
xmpp:err_forbidden(<<"Denied by ACL">>, ?MYLANG);
xmpp:err_forbidden(<<"Access denied by service policy">>, ?MYLANG);
mk_error(host_unknown) ->
xmpp:err_not_allowed(<<"Host unknown">>, ?MYLANG);
mk_error({codec_error, Why}) ->
+3 -4
View File
@@ -27,8 +27,7 @@
-ifndef(SIP).
-export([]).
-else.
-define(GEN_FSM, p1_fsm).
-behaviour(?GEN_FSM).
-behaviour(p1_fsm).
%% API
-export([start/2, start_link/2, route/3, route/4]).
@@ -58,10 +57,10 @@ start(LServer, Opts) ->
supervisor:start_child(mod_sip_proxy_sup, [LServer, Opts]).
start_link(LServer, Opts) ->
?GEN_FSM:start_link(?MODULE, [LServer, Opts], []).
p1_fsm:start_link(?MODULE, [LServer, Opts], []).
route(SIPMsg, _SIPSock, TrID, Pid) ->
?GEN_FSM:send_event(Pid, {SIPMsg, TrID}).
p1_fsm:send_event(Pid, {SIPMsg, TrID}).
route(#sip{hdrs = Hdrs} = Req, LServer, Opts) ->
case proplists:get_bool(authenticated, Opts) of
+48 -15
View File
@@ -33,6 +33,8 @@
c2s_unbinded_packet/2, c2s_closed/2, c2s_terminated/2,
c2s_handle_send/3, c2s_handle_info/2, c2s_handle_call/3,
c2s_handle_recv/3]).
%% adjust pending session timeout
-export([get_resume_timeout/1, set_resume_timeout/2]).
-include("xmpp.hrl").
-include("logger.hrl").
@@ -69,7 +71,12 @@ start(Host, _Opts) ->
ejabberd_hooks:add(c2s_terminated, Host, ?MODULE, c2s_terminated, 50).
stop(Host) ->
%% TODO: do something with global 'c2s_init' hook
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
true ->
ok;
false ->
ejabberd_hooks:delete(c2s_init, ?MODULE, c2s_stream_init, 50)
end,
ejabberd_hooks:delete(c2s_stream_started, Host, ?MODULE,
c2s_stream_started, 50),
ejabberd_hooks:delete(c2s_post_auth_features, Host, ?MODULE,
@@ -235,8 +242,9 @@ c2s_handle_info(#{mgmt_ack_timer := TRef, jid := JID, mod := Mod} = State,
[jid:encode(JID)]),
State1 = Mod:close(State),
{stop, transition_to_pending(State1)};
c2s_handle_info(#{mgmt_state := pending, jid := JID, mod := Mod} = State,
{timeout, _, pending_timeout}) ->
c2s_handle_info(#{mgmt_state := pending,
mgmt_pending_timer := TRef, jid := JID, mod := Mod} = State,
{timeout, TRef, pending_timeout}) ->
?DEBUG("Timed out waiting for resumption of stream for ~s",
[jid:encode(JID)]),
Mod:stop(State#{mgmt_state => timeout});
@@ -282,6 +290,20 @@ c2s_terminated(#{mgmt_state := MgmtState, mgmt_stanzas_in := In, sid := SID,
c2s_terminated(State, _Reason) ->
State.
%%%===================================================================
%%% Adjust pending session timeout
%%%===================================================================
-spec get_resume_timeout(state()) -> non_neg_integer().
get_resume_timeout(#{mgmt_timeout := Timeout}) ->
Timeout.
-spec set_resume_timeout(state(), non_neg_integer()) -> state().
set_resume_timeout(#{mgmt_timeout := Timeout} = State, Timeout) ->
State;
set_resume_timeout(State, Timeout) ->
State1 = restart_pending_timer(State, Timeout),
State1#{mgmt_timeout => Timeout}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -388,8 +410,6 @@ handle_resume(#{user := User, lserver := LServer, sockmod := SockMod,
previd = AttrId}),
State3 = resend_unacked_stanzas(State2),
State4 = send(State3, #sm_r{xmlns = AttrXmlns}),
%% TODO: move this to mod_client_state
%% csi_flush_queue(State4),
State5 = ejabberd_hooks:run_fold(c2s_session_resumed, LServer, State4, []),
?INFO_MSG("(~s) Resumed session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]),
@@ -408,8 +428,8 @@ transition_to_pending(#{mgmt_state := active, jid := JID,
lserver := LServer, mgmt_timeout := Timeout} = State) ->
State1 = cancel_ack_timer(State),
?INFO_MSG("Waiting for resumption of stream for ~s", [jid:encode(JID)]),
erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
State2 = State1#{mgmt_state => pending},
TRef = erlang:start_timer(timer:seconds(Timeout), self(), pending_timeout),
State2 = State1#{mgmt_state => pending, mgmt_pending_timer => TRef},
ejabberd_hooks:run_fold(c2s_session_pending, LServer, State2, []);
transition_to_pending(State) ->
State.
@@ -648,20 +668,33 @@ add_resent_delay_info(_State, El, _Time) ->
send(#{mod := Mod} = State, Pkt) ->
Mod:send(State, Pkt).
-spec restart_pending_timer(state(), non_neg_integer()) -> state().
restart_pending_timer(#{mgmt_pending_timer := TRef} = State, NewTimeout) ->
cancel_timer(TRef),
NewTRef = erlang:start_timer(timer:seconds(NewTimeout), self(),
pending_timeout),
State#{mgmt_pending_timer => NewTRef};
restart_pending_timer(State, _NewTimeout) ->
State.
-spec cancel_ack_timer(state()) -> state().
cancel_ack_timer(#{mgmt_ack_timer := TRef} = State) ->
case erlang:cancel_timer(TRef) of
false ->
receive {timeout, TRef, _} -> ok
after 0 -> ok
end;
_ ->
ok
end,
cancel_timer(TRef),
maps:remove(mgmt_ack_timer, State);
cancel_ack_timer(State) ->
State.
-spec cancel_timer(reference()) -> ok.
cancel_timer(TRef) ->
case erlang:cancel_timer(TRef) of
false ->
receive {timeout, TRef, _} -> ok
after 0 -> ok
end;
_ ->
ok
end.
-spec bounce_message_queue() -> ok.
bounce_message_queue() ->
receive {route, Pkt} ->
+74 -56
View File
@@ -38,7 +38,7 @@
remove_user/2, export/1, import_info/0, import/5, import_start/2,
depends/2, process_search/1, process_vcard/1, get_vcard/2,
disco_items/5, disco_features/5, disco_identity/5,
decode_iq_subel/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
vcard_iq_set/1, mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@@ -67,7 +67,7 @@
-optional_callbacks([use_cache/1, cache_nodes/1]).
-record(state, {host :: binary(), server_host :: binary()}).
-record(state, {hosts :: [binary()], server_host :: binary()}).
%%====================================================================
%% gen_mod callbacks
@@ -95,37 +95,41 @@ init([Host, Opts]) ->
?NS_VCARD, ?MODULE, process_sm_iq, IQDisc),
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
get_sm_features, 50),
MyHost = gen_mod:get_opt_host(Host, Opts, <<"vjud.@HOST@">>),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts, false),
if Search ->
ejabberd_hooks:add(
disco_local_items, MyHost, ?MODULE, disco_items, 100),
ejabberd_hooks:add(
disco_local_features, MyHost, ?MODULE, disco_features, 100),
ejabberd_hooks:add(
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
case Mod:is_search_supported(Host) of
false ->
?WARNING_MSG("vcard search functionality is "
"not implemented for ~s backend",
[gen_mod:db_type(Host, Opts, ?MODULE)]);
true ->
ejabberd_router:register_route(MyHost, Host)
end;
lists:foreach(
fun(MyHost) ->
ejabberd_hooks:add(
disco_local_items, MyHost, ?MODULE, disco_items, 100),
ejabberd_hooks:add(
disco_local_features, MyHost, ?MODULE, disco_features, 100),
ejabberd_hooks:add(
disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_SEARCH, ?MODULE, process_search, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_VCARD, ?MODULE, process_vcard, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_DISCO_ITEMS, mod_disco,
process_local_iq_items, IQDisc),
gen_iq_handler:add_iq_handler(
ejabberd_local, MyHost, ?NS_DISCO_INFO, mod_disco,
process_local_iq_info, IQDisc),
case Mod:is_search_supported(Host) of
false ->
?WARNING_MSG("vcard search functionality is "
"not implemented for ~s backend",
[gen_mod:db_type(Host, Opts, ?MODULE)]);
true ->
ejabberd_router:register_route(MyHost, Host)
end
end, MyHosts);
true ->
ok
end,
{ok, #state{host = MyHost, server_host = Host}}.
{ok, #state{hosts = MyHosts, server_host = Host}}.
handle_call(_Call, _From, State) ->
{noreply, State}.
@@ -144,21 +148,25 @@ handle_info(Info, State) ->
?WARNING_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{host = MyHost, server_host = Host}) ->
terminate(_Reason, #state{hosts = MyHosts, server_host = Host}) ->
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_VCARD),
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE, vcard_iq_set, 50),
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:stop(Host),
ejabberd_router:unregister_route(MyHost),
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO).
lists:foreach(
fun(MyHost) ->
ejabberd_router:unregister_route(MyHost),
ejabberd_hooks:delete(disco_local_items, MyHost, ?MODULE, disco_items, 100),
ejabberd_hooks:delete(disco_local_features, MyHost, ?MODULE, disco_features, 100),
ejabberd_hooks:delete(disco_local_identity, MyHost, ?MODULE, disco_identity, 100),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_SEARCH),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_VCARD),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_ITEMS),
gen_iq_handler:remove_iq_handler(ejabberd_local, MyHost, ?NS_DISCO_INFO)
end, MyHosts).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@@ -185,14 +193,6 @@ get_sm_features(Acc, _From, _To, Node, _Lang) ->
_ -> Acc
end.
-spec decode_iq_subel(xmpp_element() | xmlel()) -> xmpp_element() | xmlel().
%% Tell gen_iq_handler not to decode vcard elements
decode_iq_subel(El) ->
case xmpp:get_ns(El) of
?NS_VCARD -> xmpp:encode(El);
_ -> xmpp:decode(El)
end.
-spec process_local_iq(iq()) -> iq().
process_local_iq(#iq{type = set, lang = Lang} = IQ) ->
Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
@@ -206,13 +206,15 @@ process_local_iq(#iq{type = get, lang = Lang} = IQ) ->
bday = <<"2002-11-16">>}).
-spec process_sm_iq(iq()) -> iq().
process_sm_iq(#iq{type = set, lang = Lang, from = From,
sub_els = [SubEl]} = IQ) ->
#jid{user = User, lserver = LServer} = From,
process_sm_iq(#iq{type = set, lang = Lang, from = From} = IQ) ->
#jid{lserver = LServer} = From,
case lists:member(LServer, ?MYHOSTS) of
true ->
set_vcard(User, LServer, SubEl),
xmpp:make_iq_result(IQ);
case ejabberd_hooks:run_fold(vcard_iq_set, LServer, IQ, []) of
drop -> ignore;
#stanza_error{} = Err -> xmpp:make_error(IQ, Err);
_ -> xmpp:make_iq_result(IQ)
end;
false ->
Txt = <<"The query is only allowed from local users">>,
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
@@ -374,19 +376,33 @@ make_vcard_search(User, LUser, LServer, VCARD) ->
orgunit = OrgUnit,
lorgunit = LOrgUnit}.
-spec set_vcard(binary(), binary(), xmlel()) -> {error, badarg} | ok.
-spec vcard_iq_set(iq()) -> iq() | {stop, stanza_error()}.
vcard_iq_set(#iq{from = From, lang = Lang, sub_els = [VCard]} = IQ) ->
#jid{user = User, lserver = LServer} = From,
case set_vcard(User, LServer, VCard) of
{error, badarg} ->
%% Should not be here?
Txt = <<"Nodeprep has failed">>,
{stop, xmpp:err_internal_server_error(Txt, Lang)};
ok ->
IQ
end;
vcard_iq_set(Acc) ->
Acc.
-spec set_vcard(binary(), binary(), xmlel() | vcard_temp()) -> {error, badarg} | ok.
set_vcard(User, LServer, VCARD) ->
case jid:nodeprep(User) of
error ->
{error, badarg};
LUser ->
VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
VCardEl = xmpp:encode(VCARD),
VCardSearch = make_vcard_search(User, LUser, LServer, VCardEl),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
Mod:set_vcard(LUser, LServer, VCardEl, VCardSearch),
ets_cache:delete(?VCARD_CACHE, {LUser, LServer},
cache_nodes(Mod, LServer)),
ejabberd_hooks:run(vcard_set, LServer,
[LUser, LServer, VCARD])
ok
end.
-spec string2lower(binary()) -> binary().
@@ -527,6 +543,8 @@ mod_opt_type(allow_return_all) ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(host) -> fun iolist_to_binary/1;
mod_opt_type(hosts) ->
fun (L) -> lists:map(fun iolist_to_binary/1, L) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
mod_opt_type(matches) ->
fun (infinity) -> infinity;
@@ -543,6 +561,6 @@ mod_opt_type(O) when O == cache_life_time; O == cache_size ->
mod_opt_type(O) when O == use_cache; O == cache_missed ->
fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[allow_return_all, db_type, host, iqdisc, matches,
[allow_return_all, db_type, host, hosts, iqdisc, matches,
search, search_all_hosts, cache_life_time, cache_size,
use_cache, cache_missed].
+27 -27
View File
@@ -42,12 +42,13 @@
-include("logger.hrl").
-include("eldap.hrl").
-include("xmpp.hrl").
-include("translate.hrl").
-define(PROCNAME, ejabberd_mod_vcard_ldap).
-record(state,
{serverhost = <<"">> :: binary(),
myhost = <<"">> :: binary(),
myhosts = [] :: [binary()],
eldap_id = <<"">> :: binary(),
search = false :: boolean(),
servers = [] :: [binary()],
@@ -324,35 +325,34 @@ default_vcard_map() ->
{<<"PHOTO">>, <<"%s">>, [<<"jpegPhoto">>]}].
default_search_fields() ->
[{<<"User">>, <<"%u">>},
{<<"Full Name">>, <<"displayName">>},
{<<"Given Name">>, <<"givenName">>},
{<<"Middle Name">>, <<"initials">>},
{<<"Family Name">>, <<"sn">>},
{<<"Nickname">>, <<"%u">>},
{<<"Birthday">>, <<"birthDay">>},
{<<"Country">>, <<"c">>},
{<<"City">>, <<"l">>},
{<<"Email">>, <<"mail">>},
{<<"Organization Name">>, <<"o">>},
{<<"Organization Unit">>, <<"ou">>}].
[{?T("User"), <<"%u">>},
{?T("Full Name"), <<"displayName">>},
{?T("Given Name"), <<"givenName">>},
{?T("Middle Name"), <<"initials">>},
{?T("Family Name"), <<"sn">>},
{?T("Nickname"), <<"%u">>},
{?T("Birthday"), <<"birthDay">>},
{?T("Country"), <<"c">>},
{?T("City"), <<"l">>},
{?T("Email"), <<"mail">>},
{?T("Organization Name"), <<"o">>},
{?T("Organization Unit"), <<"ou">>}].
default_search_reported() ->
[{<<"Full Name">>, <<"FN">>},
{<<"Given Name">>, <<"FIRST">>},
{<<"Middle Name">>, <<"MIDDLE">>},
{<<"Family Name">>, <<"LAST">>},
{<<"Nickname">>, <<"NICK">>},
{<<"Birthday">>, <<"BDAY">>},
{<<"Country">>, <<"CTRY">>},
{<<"City">>, <<"LOCALITY">>},
{<<"Email">>, <<"EMAIL">>},
{<<"Organization Name">>, <<"ORGNAME">>},
{<<"Organization Unit">>, <<"ORGUNIT">>}].
[{?T("Full Name"), <<"FN">>},
{?T("Given Name"), <<"FIRST">>},
{?T("Middle Name"), <<"MIDDLE">>},
{?T("Family Name"), <<"LAST">>},
{?T("Nickname"), <<"NICK">>},
{?T("Birthday"), <<"BDAY">>},
{?T("Country"), <<"CTRY">>},
{?T("City"), <<"LOCALITY">>},
{?T("Email"), <<"EMAIL">>},
{?T("Organization Name"), <<"ORGNAME">>},
{?T("Organization Unit"), <<"ORGUNIT">>}].
parse_options(Host, Opts) ->
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"vjud.@HOST@">>),
MyHosts = gen_mod:get_opt_hosts(Host, Opts, <<"vjud.@HOST@">>),
Search = gen_mod:get_opt(search, Opts, false),
Matches = gen_mod:get_opt(matches, Opts, 30),
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?PROCNAME)),
@@ -394,7 +394,7 @@ parse_options(Host, Opts) ->
end,
SearchReported)
++ UIDAttrs),
#state{serverhost = Host, myhost = MyHost,
#state{serverhost = Host, myhosts = MyHosts,
eldap_id = Eldap_ID, search = Search,
servers = Cfg#eldap_config.servers,
backups = Cfg#eldap_config.backups,
+25 -24
View File
@@ -36,6 +36,7 @@
-include("xmpp.hrl").
-include("mod_vcard.hrl").
-include("logger.hrl").
-include("translate.hrl").
%%%===================================================================
%%% API
@@ -95,32 +96,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
end.
search_fields(_LServer) ->
[{<<"User">>, <<"user">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("User"), <<"user">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
search_reported(_LServer) ->
[{<<"Jabber ID">>, <<"jid">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("Jabber ID"), <<"jid">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
remove_user(LUser, LServer) ->
US = {LUser, LServer},
+25 -24
View File
@@ -37,6 +37,7 @@
-include("mod_vcard.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-include("translate.hrl").
%%%===================================================================
%%% API
@@ -150,32 +151,32 @@ search(LServer, Data, AllowReturnAll, MaxMatch) ->
end.
search_fields(_LServer) ->
[{<<"User">>, <<"user">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("User"), <<"user">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
search_reported(_LServer) ->
[{<<"Jabber ID">>, <<"jid">>},
{<<"Full Name">>, <<"fn">>},
{<<"Name">>, <<"first">>},
{<<"Middle Name">>, <<"middle">>},
{<<"Family Name">>, <<"last">>},
{<<"Nickname">>, <<"nick">>},
{<<"Birthday">>, <<"bday">>},
{<<"Country">>, <<"ctry">>},
{<<"City">>, <<"locality">>},
{<<"Email">>, <<"email">>},
{<<"Organization Name">>, <<"orgname">>},
{<<"Organization Unit">>, <<"orgunit">>}].
[{?T("Jabber ID"), <<"jid">>},
{?T("Full Name"), <<"fn">>},
{?T("Name"), <<"first">>},
{?T("Middle Name"), <<"middle">>},
{?T("Family Name"), <<"last">>},
{?T("Nickname"), <<"nick">>},
{?T("Birthday"), <<"bday">>},
{?T("Country"), <<"ctry">>},
{?T("City"), <<"locality">>},
{?T("Email"), <<"email">>},
{?T("Organization Name"), <<"orgname">>},
{?T("Organization Unit"), <<"orgunit">>}].
remove_user(LUser, LServer) ->
ejabberd_sql:sql_transaction(
+50 -24
View File
@@ -30,8 +30,8 @@
%% gen_mod callbacks
-export([start/2, stop/1, reload/3]).
-export([update_presence/1, vcard_set/3, remove_user/2,
mod_opt_type/1, depends/2]).
-export([update_presence/1, vcard_set/1, remove_user/2,
user_send_packet/1, mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -47,15 +47,19 @@ start(Host, Opts) ->
init_cache(Host, Opts),
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE,
update_presence, 100),
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
100),
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
user_send_packet, 50),
ejabberd_hooks:add(vcard_iq_set, Host, ?MODULE, vcard_set,
90),
ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50).
stop(Host) ->
ejabberd_hooks:delete(c2s_self_presence, Host,
?MODULE, update_presence, 100),
ejabberd_hooks:delete(vcard_set, Host, ?MODULE,
vcard_set, 100),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
user_send_packet, 50),
ejabberd_hooks:delete(vcard_iq_set, Host, ?MODULE,
vcard_set, 90),
ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50).
reload(Host, NewOpts, _OldOpts) ->
@@ -71,16 +75,33 @@ depends(_Host, _Opts) ->
-> {presence(), ejabberd_c2s:state()}.
update_presence({#presence{type = available} = Pres,
#{jid := #jid{luser = LUser, lserver = LServer}} = State}) ->
Hash = get_xupdate(LUser, LServer),
Pres1 = xmpp:set_subtag(Pres, #vcard_xupdate{hash = Hash}),
Pres1 = case get_xupdate(LUser, LServer) of
undefined -> xmpp:remove_subtag(Pres, #vcard_xupdate{});
XUpdate -> xmpp:set_subtag(Pres, XUpdate)
end,
{Pres1, State};
update_presence(Acc) ->
Acc.
-spec vcard_set(binary(), binary(), xmlel()) -> ok.
vcard_set(LUser, LServer, _VCARD) ->
-spec user_send_packet({presence(), ejabberd_c2s:state()})
-> {presence(), ejabberd_c2s:state()}.
user_send_packet({#presence{type = available,
to = #jid{luser = U, lserver = S,
lresource = <<"">>}},
#{jid := #jid{luser = U, lserver = S}}} = Acc) ->
%% This is processed by update_presence/2 explicitly, we don't
%% want to call this multiple times for performance reasons
Acc;
user_send_packet(Acc) ->
update_presence(Acc).
-spec vcard_set(iq()) -> iq().
vcard_set(#iq{from = #jid{luser = LUser, lserver = LServer}} = IQ) ->
ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}),
ejabberd_sm:force_update_presence({LUser, LServer}).
ejabberd_sm:force_update_presence({LUser, LServer}),
IQ;
vcard_set(Acc) ->
Acc.
-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
@@ -91,7 +112,7 @@ remove_user(User, Server) ->
%%====================================================================
%% Storage
%%====================================================================
-spec get_xupdate(binary(), binary()) -> binary() | undefined.
-spec get_xupdate(binary(), binary()) -> vcard_xupdate() | undefined.
get_xupdate(LUser, LServer) ->
Result = case use_cache(LServer) of
true ->
@@ -102,11 +123,12 @@ get_xupdate(LUser, LServer) ->
db_get_xupdate(LUser, LServer)
end,
case Result of
{ok, Hash} -> Hash;
error -> undefined
{ok, external} -> undefined;
{ok, Hash} -> #vcard_xupdate{hash = Hash};
error -> #vcard_xupdate{}
end.
-spec db_get_xupdate(binary(), binary()) -> {ok, binary()} | error.
-spec db_get_xupdate(binary(), binary()) -> {ok, binary() | external} | error.
db_get_xupdate(LUser, LServer) ->
case mod_vcard:get_vcard(LUser, LServer) of
[VCard] ->
@@ -147,17 +169,21 @@ use_cache(Host) ->
Host, ?MODULE, use_cache,
ejabberd_config:use_cache(Host)).
-spec compute_hash(xmlel()) -> binary().
-spec compute_hash(xmlel()) -> binary() | external.
compute_hash(VCard) ->
case fxml:get_path_s(VCard,
[{elem, <<"PHOTO">>},
{elem, <<"BINVAL">>},
cdata]) of
<<>> ->
case fxml:get_subtag(VCard, <<"PHOTO">>) of
false ->
<<>>;
BinVal ->
try str:sha(base64:decode(BinVal))
catch _:badarg -> <<>>
Photo ->
try xmpp:decode(Photo, ?NS_VCARD, []) of
#vcard_photo{binval = <<_, _/binary>> = BinVal} ->
str:sha(BinVal);
#vcard_photo{extval = <<_, _/binary>>} ->
external;
_ ->
<<>>
catch _:{xmpp_codec, _} ->
<<>>
end
end.

Some files were not shown because too many files have changed in this diff Show More