Compare commits

...

394 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
Evgeniy Khramtsov a35b9dd9cc Close accepted socket if sockname/peername has failed
Fixes #1834
2017-07-06 14:49:21 +03:00
Marc Philipp c69720a1ab Add integration test for set_room_affiliation 2017-07-06 13:33:13 +02:00
Paweł Chmielowski a58de70f06 Fix invalid argument in get_messages_susbset
This should fix #1818
2017-06-30 14:24:35 +02:00
Christophe Romain 800965a957 Avoid useless calls on simples subscriptions (#1313) 2017-06-29 15:24:18 +02:00
Alexey Shchepin fcf672c50e Add allow_local_users to mod_block_strangers (#1804, #1809) 2017-06-29 14:55:24 +03:00
Badlop b66e369a1d Fix Salt import from prosody SCRAMmed password (#1803) 2017-06-29 10:28:44 +02:00
Paweł Chmielowski 9dd03c873c Fix clone code in update-deps-release 2017-06-28 16:47:31 +02:00
Paweł Chmielowski e42bb47ce3 Update deps 2017-06-28 16:46:48 +02:00
Paweł Chmielowski dc7fa076d7 Fix problem with updating deps in rebar.config for non-tags 2017-06-28 16:36:59 +02:00
Paweł Chmielowski 960cf495c6 Don't use asn1rt:decode, it's not available on R20 2017-06-28 14:51:45 +02:00
Christophe Romain 0f12804a49 Quote paths to allow spaces (#1789) 2017-06-28 11:39:05 +02:00
Badlop 3c7c71cfa6 In offline export to SQL, first write all DELETE, later all INSERT (#1509) 2017-06-28 11:14:59 +02:00
Paweł Chmielowski aac190255b Update fast_tls 2017-06-27 15:02:23 +02:00
Paweł Chmielowski 85a08a087b Improve update-deps-releases deps parsing 2017-06-27 14:56:00 +02:00
Badlop 1bfb0ab39c Fix username in mam export (#1510)(thanks to themaverik) 2017-06-26 14:38:12 +02:00
Badlop 4ef1cdec12 Write validator for mod_multicast's limits option 2017-06-26 13:39:50 +02:00
Badlop 0534678028 Use YAML syntax for limits option in mod_multicast 2017-06-26 11:26:53 +02:00
Paweł Chmielowski 70606d7f1a Catch exception that may happen when sending data over websocket
This fixes #1667
2017-06-23 17:19:37 +02:00
Mickaël Rémond bb39ecbc08 More explicit bosh configuration handler
I also updated default url from to match URL suggested by XEP-0156
2017-06-23 16:47:14 +02:00
Evgeny Khramtsov 54e6e1a5fb Merge pull request #1793 from marcphilipp/bugfix/set_room_affiliation_master
Fix mod_muc_admin:set_room_affiliation
2017-06-22 18:28:12 +04:00
Evgeniy Khramtsov 5bb7a0b0db Don't let a receiver to crash if a controller is unavailable
Fixes #1796
2017-06-22 16:58:46 +03:00
Marc Philipp 976a8c9cc9 Fix mod_muc_admin:set_room_affiliation 2017-06-21 17:20:58 +02:00
Christophe Romain a095477b4c Fix outgoing_s2s_timeout description (#1684) 2017-06-21 16:30:51 +02:00
Holger Weiss 950aca380c mod_client_state: Reset state on session resume
Don't restore the previous CSI state when a stream management session is
resumed.
2017-06-21 01:05:46 +02:00
Holger Weiss 985b0a1933 mod_stream_mgmt: Add missing function specs 2017-06-21 01:00:29 +02:00
Christophe Romain a7841ed486 Improve API documentation generator 2017-06-20 14:45:57 +02:00
Christophe Romain a11e833a98 Make ext_mod api return rescode 2017-06-20 14:45:31 +02:00
Paweł Chmielowski 62ee051c6e Fix invalid {args,result}_examples in mod_muc_admin 2017-06-19 16:31:07 +02:00
Paweł Chmielowski 5424ead01d Generate better errors when not being able to generate documentation 2017-06-19 16:30:45 +02:00
Paweł Chmielowski 96d385bf82 Another tweak to md generator 2017-06-19 15:10:34 +02:00
Paweł Chmielowski 8e2258b16a Update markdown api document generator 2017-06-19 15:02:02 +02:00
Badlop f87b46f454 Fix srg_user_add/del for non-Mnesia database backends (#1780) 2017-06-15 11:05:41 +02:00
Christophe Romain 5418b37314 Add pubsub import from prosody/metronome 2017-06-15 09:56:05 +02:00
Paweł Chmielowski 6353a06a5d Catch exception from Elixir.ExUnit.Server.cases_loaded 2017-06-14 14:56:59 +02:00
Christophe Romain 58b9077b51 Fix OTP-17.5 support 2017-06-14 14:18:35 +02:00
Paweł Chmielowski 10fcfa860a Show operations to perform before asking to apply them 2017-06-14 14:05:41 +02:00
Holger Weiss 5f2dcc51ce Bump xmpp version 2017-06-14 01:05:15 +02:00
Evgeniy Khramtsov 0aa64381ff Fix IP address parsing for mod_metrics 2017-06-13 16:54:29 +03:00
Christophe Romain 6c8b037422 Fix refactor bug on wait_status 2017-06-13 12:37:27 +02:00
Christophe Romain d63ea000c7 Prepare package for 17.06-beta 2017-06-13 11:02:30 +02:00
Christophe Romain 5e148df0a9 Update esip and stun dependencies 2017-06-13 10:35:00 +02:00
Paweł Chmielowski d8f05acb67 Add script for managing updates to deps 2017-06-12 19:23:41 +02:00
Paweł Chmielowski d6f4c99243 Remove luerl from floating_deps 2017-06-12 19:23:41 +02:00
Badlop 63b6e0d381 Switch access rule delete_old_users with protect_old_users (#1772) 2017-06-09 19:18:47 +02:00
Paweł Chmielowski 8c1568ff93 Add more ipv6 loopback addresses 2017-06-09 14:38:34 +02:00
Paweł Chmielowski 7b5895c90d Allow api access on both ipv4 and 6 loopback addresses
This should fix issue #1769
2017-06-09 13:57:26 +02:00
Paweł Chmielowski 115cb23bd8 Fix elixir tests on elixir 1.4 2017-06-09 12:59:47 +02:00
Paweł Chmielowski 62806607bf Add missing , 2017-06-09 12:10:40 +02:00
Badlop b25b5c2f98 Improve export2sql explanation; remove obsolete and duplicated command 2017-06-09 12:02:49 +02:00
Christophe Romain 444c385f23 Update dependencies 2017-06-09 10:34:35 +02:00
Badlop ee8bbccb2a Fix and document push_roster_all command 2017-06-08 19:54:34 +02:00
Christophe Romain 66237abd35 Merge pull request #1766 from IRog/master
fixing exec_iex bug with proper --name arguement
2017-06-08 10:41:26 +02:00
Ivy Rogatko 0973a8d6c1 fixing iex bug with proper --name arguement 2017-06-07 21:15:17 -07:00
Christophe Romain fbead19c88 Update elixir 2017-06-07 17:09:06 +02:00
Christophe Romain baf574d6c4 Update lager p1_mysql and p1_pgsql dependencies 2017-06-07 16:56:00 +02:00
Christophe Romain e3c801f1f5 Update dependencies 2017-06-07 16:18:41 +02:00
Paweł Chmielowski f773edcb98 Override version of subdeps with version from main rebar.config 2017-06-07 16:06:28 +02:00
Christophe Romain 2a73068aac Add missing space separator on EJABBERD_OPTS 2017-06-06 15:41:48 +02:00
Evgeny Khramtsov 5971eb3da0 Merge pull request #1758 from reneklacan/fix-mix-warnings
Fix mix warnings
2017-06-04 14:25:31 +04:00
Rene Klacan 04fd5567a7 Fix mix warnings 2017-06-04 12:24:35 +02:00
Evgeny Khramtsov 4f8c132b53 Merge pull request #1759 from reneklacan/fix-version-to-be-semver
Fix version to be SemVer compatible
2017-06-04 14:18:39 +04:00
Rene Klacan a98685e0bb Fix version to be SemVer compatible 2017-06-04 03:01:51 +02:00
Christophe Romain 12733bd21b Fix EJABBERD_OPTS 2017-06-02 17:56:57 +02:00
Badlop f6767ed061 Fix rooms list in WebAdmin (#1753) 2017-06-01 19:27:41 +02:00
Christophe Romain de10a7a8ce Respect INSTALLUSER when creating spool directory 2017-06-01 17:48:44 +02:00
Christophe Romain 5081a180fa Create spool directory at start if not exists 2017-06-01 17:44:23 +02:00
Christophe Romain a576f3a6d1 Remove use of getopt to support simpler shells 2017-06-01 12:10:06 +02:00
Christophe Romain 3201f8e513 Improve ejabberdctl parameters parsing 2017-06-01 11:48:11 +02:00
Christophe Romain 92003fa4dc Minor ejabberdctl improvements 2017-06-01 08:04:11 +02:00
Christophe Romain 818d9c8c42 Merge branch 'joudinet-master' 2017-05-31 18:12:26 +02:00
Christophe Romain cbe6553baa Refactor ejabberdctl 2017-05-31 18:11:45 +02:00
Paweł Chmielowski 5d3870faa3 Add --enable-system-deps configure option 2017-05-31 11:30:34 +02:00
Christophe Romain 2d8ce266bd Add license files into lib and deps directories 2017-05-30 15:03:06 +02:00
Christophe Romain 0042f18c1f Merge branch 'master' of https://github.com/joudinet/ejabberd into joudinet-master 2017-05-30 12:37:27 +02:00
Badlop 0982a9bc3c Parse correctly presence_broadcast option in change_room_option command 2017-05-29 12:49:53 +02:00
Evgeniy Khramtsov 50327a0cfc Fix case clause
Fixes #1746
2017-05-25 13:46:17 +03:00
Holger Weiss 5802062746 Cosmetic change: Fix indentation errors 2017-05-24 17:16:16 +02:00
Evgeniy Khramtsov 69de1780a0 Introduce --enable-stun and --enable-sip configure options
STUN/TURN and SIP is not compiled by default anymore.
Use --enable-stun, --enable-sip or --enable-all to enable them.
2017-05-23 13:12:48 +03:00
Evgeniy Khramtsov 6e8895f9e9 Get rid of sql_queries.erl 2017-05-23 12:25:13 +03:00
Evgeniy Khramtsov e93762a720 Deprecate misc:encode_base64/1 and misc:decode_base64/1 2017-05-23 10:43:26 +03:00
Evgeniy Khramtsov 268065e5c4 Validate all certfiles on startup 2017-05-23 09:27:52 +03:00
Evgeniy Khramtsov d7878ef131 Implement cache for mod_announce 2017-05-22 16:14:28 +03:00
Badlop 908bedeaa6 Describe command arguments and results in mod_muc_admin 2017-05-22 12:55:32 +02:00
Evgeniy Khramtsov 504860f065 Don't leak with UDP sockets 2017-05-22 11:29:53 +03:00
Evgeniy Khramtsov 3a96d72a7f Implement cache for mod_private 2017-05-22 10:34:57 +03:00
Evgeniy Khramtsov d88e4d495f Don't store messages via a single process 2017-05-21 23:21:13 +03:00
Evgeniy Khramtsov 66a4e405e0 Improve mod_metrics
* Do not spawn a process per event
* Avoid UDP socket creation on every event
* Get rid of calls to str.erl module
* Add options 'ip' and 'port'
2017-05-21 14:24:57 +03:00
Evgeniy Khramtsov 0a77b9f43e Get rid of a workaround against old Erlang bug 2017-05-21 13:30:46 +03:00
Evgeniy Khramtsov 470669fa6b Get rid of db_type warning for mod_vcard_xupdate 2017-05-21 11:40:24 +03:00
Evgeniy Khramtsov af29fb21df Get rid of detection of modules' db_type detection
The detection sometimes leads to errorneous warnings.
We need to improve it later. For now I just remove the
detection as it doesn't fully work anyway.
2017-05-21 11:33:16 +03:00
Evgeniy Khramtsov be50d57ddd Declare ejabberd_oauth behaviour 2017-05-21 11:31:30 +03:00
Evgeniy Khramtsov 35d19b32f4 Implement cache for mod_privacy/mod_blocking 2017-05-20 22:36:32 +03:00
Paweł Chmielowski 654d907dcf export_all is not needed here 2017-05-19 17:03:41 +02:00
Paweł Chmielowski b013c29c7e Fix values put in args_examples 2017-05-19 16:56:37 +02:00
Evgeniy Khramtsov 0ed23980a6 Get rid of Mnesia transaction in get_vcard/2 2017-05-18 21:24:47 +03:00
Evgeniy Khramtsov a78862e05e The default 'iqdisc' is now 'no_queue' 2017-05-18 19:13:18 +03:00
Evgeniy Khramtsov bcb44ccb6f Implement cache for mod_last 2017-05-18 13:21:17 +03:00
Evgeniy Khramtsov 736a182544 ?SQL_UPSERT returns 'ok' on success 2017-05-18 12:10:36 +03:00
Evgeniy Khramtsov 97bb1250ba Avoid erroneous usage of ?MODULE macro 2017-05-18 12:09:28 +03:00
Evgeniy Khramtsov b0b7ac101c Fix function_clause after authentication refactoring
Fixes https://github.com/processone/ejabberd-contrib/issues/213
2017-05-18 09:51:04 +03:00
Evgeniy Khramtsov 3e35d44b0f Replace 'if_version_above' directive with 'if_have_fun' 2017-05-17 19:37:06 +03:00
Evgeniy Khramtsov 6691c59a7a Clean up database code related to mod_vcard_xupdate 2017-05-17 19:29:19 +03:00
Evgeniy Khramtsov 1391d5a304 Use disc_only_copies for oauth_token Mnesia table 2017-05-17 17:42:22 +03:00
Evgeniy Khramtsov 8f595b58a7 Increase gen_mod's supervisor shutdown time 2017-05-17 17:33:07 +03:00
Evgeniy Khramtsov 5bdc6c0822 Get rid of deprecated option 'resume_timeout' in test config 2017-05-17 17:21:59 +03:00
Evgeniy Khramtsov 1925b94131 Implement cache for mod_vcard and mod_vcard_xupdate 2017-05-17 17:13:34 +03:00
Evgeniy Khramtsov fc794b680a Add cache options to the validator 2017-05-17 16:03:41 +03:00
Evgeniy Khramtsov a71065fcda Ciphers should be a binary string 2017-05-17 15:42:18 +03:00
Evgeniy Khramtsov 7165196211 Get rid of unused variable 2017-05-17 15:24:32 +03:00
Evgeniy Khramtsov f782955c06 Implement cache for roster 2017-05-17 14:47:35 +03:00
Evgeniy Khramtsov 3f13396d73 Fix use_cache/1 callback 2017-05-15 08:58:37 +03:00
Evgeniy Khramtsov 061d5f2380 Shut up dialyzer/xref if public_key:short_name_hash/1 is not available 2017-05-13 13:11:08 +03:00
Evgeniy Khramtsov 2d17a2850c Only validate certfiles if public_key:short_name_hash/1 is available 2017-05-12 17:51:17 +03:00
Evgeniy Khramtsov cc58ce6301 Introduce Certficate Manager
The major goal is to simplify certificate management in ejabberd.
Currently it requires some effort from a user to configure certficates,
especially in the situation where a lot of virtual domains are hosted.

The task is splitted in several sub-tasks:
* Implement basic certificate validator. The validator should check all
configured certificates for existence, validity, duration and so on. The
validator should not perform any actions in the case of errors except
logging an error message. This is actually implemented by this commit.
* All certificates should be configured inside a single section (something
like 'certfiles') where ejabberd should parse them, check the full-chain,
find the corresponding private keys and, if needed, resort chains and
split the certficates into separate files for easy to use by fast_tls.
* Options like 'domain_certfile', 'c2s_certfile' or 's2s_certfile' should
probably be deprecated, since the process of matching certificates with the
corresponding virtual hosts should be done automatically and these options
only introduce configuration errors without any meaningful purpose.
2017-05-12 16:27:09 +03:00
Evgeniy Khramtsov d3c8fb7705 Check presence of some files during option validation 2017-05-12 09:34:57 +03:00
Evgeniy Khramtsov 9fe16a29e1 Gracefully process malformed passwords during password change 2017-05-11 17:15:23 +03:00
Evgeniy Khramtsov 31a3cc7b10 Gracefully process malformed passwords during registration 2017-05-11 17:09:26 +03:00
Evgeniy Khramtsov a8dc5f80d1 Add 'access_remove' ACL to mod_register 2017-05-11 16:37:01 +03:00
Evgeniy Khramtsov 81d9770d4f Update Elixir tests for using new auth API 2017-05-11 16:15:18 +03:00
Evgeniy Khramtsov cdb191bb48 Rename is_user_exists -> user_exists 2017-05-11 15:49:06 +03:00
Evgeniy Khramtsov 633b68db11 Use cache for authentication backends
The commit introduces the following API incompatibilities:

In ejabberd_auth.erl:
* dirty_get_registered_users/0 is renamed to get_users/0
* get_vh_registered_users/1 is renamed to get_users/1
* get_vh_registered_users/2 is renamed to get_users/2
* get_vh_registered_users_number/1 is renamed to count_users/1
* get_vh_registered_users_number/2 is renamed to count_users/2

In ejabberd_auth callbacks
* plain_password_required/0 is replaced by plain_password_required/1
  where the argument is a virtual host
* store_type/0 is replaced by store_type/1 where the argument is
  a virtual host
* set_password/3 is now an optional callback
* remove_user/3 callback is no longer needed
* remove_user/2 now should return `ok | {error, atom()}`
* is_user_exists/2 now must only be implemented for backends
  with `external` store type
* check_password/6 is no longer needed
* check_password/4 now must only be implemented for backends
  with `external` store type
* try_register/3 is now an optional callback and should return
  `ok | {error, atom()}`
* dirty_get_registered_users/0 is no longer needed
* get_vh_registered_users/1 is no longer needed
* get_vh_registered_users/2 is renamed to get_users/2
* get_vh_registered_users_number/1 is no longer needed
* get_vh_registered_users_number/2 is renamed to count_users/2
* get_password_s/2 is no longer needed
* get_password/2 now must only be implemented for backends with
  `plain` or `scram` store type

Additionally, the commit introduces two new callbacks:
* use_cache/1 where the argument is a virtual host
* cache_nodes/1 where the argument is a virtual host

New options are also introduced: `auth_use_cache`, `auth_cache_missed`,
`auth_cache_life_time` and `auth_cache_size`.
2017-05-11 14:37:21 +03:00
Badlop e890525788 Use misc:atom_to_binary/1 instead of the deprecated jlib.erl (#1510) 2017-05-10 12:05:52 +02:00
Badlop 6b8c61b3a2 Update comment: aux.erl was renamed to misc.erl 2017-05-10 12:03:13 +02:00
Badlop 4849ac9781 Use jid:encode/1 instead of the deprecated jid:to_string/1 (#1510) 2017-05-10 11:22:15 +02:00
Lamtei W cd18d3d8a7 Fix: update sql statement, added missing delimeter for sql queries 2017-05-10 11:22:11 +02:00
Lamtei W a0908ba393 Added export function for mam module 2017-05-10 11:22:07 +02:00
Evgeniy Khramtsov 5d7a704ca5 Remove forgotten 'export_all' 2017-05-08 17:23:29 +03:00
Evgeniy Khramtsov cee90a886e Don't list 'redis_pool_size' option multiple times 2017-05-08 17:22:34 +03:00
Evgeniy Khramtsov 6b6d07745d Split some functions in smaller ones 2017-05-08 16:29:01 +03:00
Evgeniy Khramtsov bf2a2f291f State that Erlang 17.5 or higher is only supported 2017-05-08 15:10:42 +03:00
Evgeniy Khramtsov 8368a0850a Don't call gen_mod:get_opt() outside of modules 2017-05-08 14:34:35 +03:00
Evgeniy Khramtsov 01a2c9fe12 Add type specs for Module:opt_type/1 2017-05-08 12:59:28 +03:00
Evgeniy Khramtsov 6aefd24eb3 LOG_PATH macro should be of string type 2017-05-06 09:42:06 +03:00
Evgeniy Khramtsov 3241c2506b Introduce 'sql_connect_timeout' option (#1698) 2017-05-05 16:25:10 +03:00
Evgeniy Khramtsov 48d6ae1def Introduce 'sql_query_timeout' option
Fixes #1698
2017-05-05 13:20:28 +03:00
Evgeniy Khramtsov f2dc8c0442 Emit deprecation warning for SM related listening options 2017-05-05 12:31:17 +03:00
Evgeniy Khramtsov b174e2c9c6 Improve validation of second-level options 2017-05-05 11:11:17 +03:00
Evgeniy Khramtsov fb17c1b99f Make it possible to validate second-level options 2017-05-04 17:34:32 +03:00
Paweł Chmielowski e790e66f47 Fix elixir tests 2017-05-04 12:17:41 +02:00
Paweł Chmielowski c64e77a08c Catch exceptions from acl:add_list in web admin 2017-05-04 12:01:22 +02:00
Evgeniy Khramtsov cf53d834e9 Introduce 'iqdisc' global option 2017-05-04 12:24:47 +03:00
Evgeniy Khramtsov a2a4a4970e Validate module options on start_module/2 2017-05-04 09:09:10 +03:00
Badlop 31fa36003f Parse ldap_uids in mod_vcard_ldap like in eldap_utils (#319) 2017-05-03 23:32:56 +02:00
Christophe Romain cd1c41e448 Merge pull request #1697 from pipo02mix/dockerfile-17-04
Update Dockerfile to be able to build 17.04 ejabberd version
2017-05-03 09:29:23 +02:00
Evgeniy Khramtsov 54cc49bc70 Validate new options before module reloading 2017-05-01 14:01:12 +03:00
Evgeniy Khramtsov fe662c1a0a Don't forget to delete digraph 2017-05-01 10:14:00 +03:00
Evgeniy Khramtsov fddd6110e0 Don't validate an option in gen_mod:get*opt() functions
The changes are very similar to those from previous commit:
* Now there is no need to pass validating function in
  gen_mod:get_opt() and gen_mod:get_module_opt() functions,
  because the modules' configuration keeps already validated values.
* New functions gen_mod:get_opt/2 and gen_mod:get_module_opt/3 are
  introduced.
* Functions gen_mod:get_opt/4 and get_module_opt/5 are deprecated.
  If the functions are still called, the "function" argument is
  simply ignored.
* Validating callback Mod:listen_opt_type/1 is introduced to validate
  listening options at startup.
2017-04-30 19:01:47 +03:00
Evgeniy Khramtsov 2b63d07329 Merge branch 'new-option-validation' 2017-04-29 11:48:57 +03:00
Evgeniy Khramtsov b82b93f8f0 Don't validate an option in ejabberd_config:get_option() functions
The commit introduces the following changes:
* Now there is no need to pass validating function in
  ejabberd_config:get_option() functions, because the configuration
  keeps already validated values.
* New function ejabberd_config:get_option/1 is introduced
* Function ejabberd_config:get_option/3 is deprecated. If the function
  is still called, the second argument (validating function) is simply
  ignored.
* The second argument for ejabberd_config:get_option/2 is now
  a default value, not a validating function.
2017-04-29 11:39:40 +03:00
Evgeniy Khramtsov 7129aebe76 Don't re-define validation functions in multiple places 2017-04-28 13:23:32 +03:00
Paweł Chmielowski 2bcf822637 Fix elixir tests 2017-04-28 10:08:09 +02:00
Evgeniy Khramtsov 6658c0d386 Bump cache_tab version 2017-04-27 20:07:44 +03:00
Evgeniy Khramtsov 0b93cb7ece Store options using p1_options module 2017-04-27 19:44:58 +03:00
badlop b51e2aa213 Merge pull request #1702 from skuroki/patch-1
Fix typo
2017-04-27 09:58:37 +02:00
Badlop d18d99e8ec Bug requesting non-existent data with private_get command (#1690) 2017-04-26 23:14:30 +02:00
Holger Weiss 9b8364b6c8 Merge remote-tracking branch 'processone/pr/1699'
* processone/pr/1699:
  Add support for HTTP File Upload, version 0.3.0
2017-04-26 21:18:16 +02:00
KUROKI Shinsuke 959419419f Fix typo 2017-04-26 17:35:51 +09:00
Paweł Chmielowski 2988d84cfb Don't check result of jid.start 2017-04-26 09:50:49 +02:00
Badlop c0eb85ce53 Allow a room admin also to subscribe another JID 2017-04-26 01:30:12 +02:00
Holger Weiss 56a4bf8f3c mod_stream_mgmt: Fix typo in variable name 2017-04-25 22:37:27 +02:00
Holger Weiss 9cc332d6b3 mod_stream_mgmt: Fix 'if_offline' detection 2017-04-25 22:32:03 +02:00
Evgeniy Khramtsov 120682ec8b Include original 'id' and 'type' attributes in offline event 2017-04-25 17:59:26 +03:00
Paweł Chmielowski a67b3dc6a6 Don't check result of jid.start 2017-04-25 16:54:01 +02:00
Paweł Chmielowski f4c98f635b Don't check result of jid.start 2017-04-25 16:46:00 +02:00
Evgeniy Khramtsov 069bf6dec6 Make sure only jabber:x:event tag is present in offline event 2017-04-25 17:21:24 +03:00
Fernando Ripoll a59bef1afe Update Dockerfile to be able to build 17.04 ejabberd version
Because the default installation prefix has changed in the new release, it is needed to add in the configure line of Docker file the argument  --prefix=/
2017-04-25 09:35:07 +02:00
Holger Weiss d0f3696596 randoms: Keep compatibility with Erlang/OTP 17 2017-04-24 23:51:01 +02:00
Evgeniy Khramtsov c923bb5c10 Avoid changing configuration on listener deletion 2017-04-23 16:42:54 +03:00
Evgeniy Khramtsov 9a93acc62a Improve Mnesia tables creation and transformation 2017-04-23 16:37:58 +03:00
Evgeniy Khramtsov 8770fc98e1 Use round-robin algorithm when selecting worker from DB pool 2017-04-23 11:54:56 +03:00
Evgeniy Khramtsov 18433e289f Add clear_cache admin command 2017-04-22 11:33:39 +03:00
Holger Weiss 168712ebbd Add support for HTTP File Upload, version 0.3.0
Support the current XEP-0363 version in addition to the previous
revisions.
2017-04-21 18:36:53 +02:00
Paweł Chmielowski 44ae6bcc83 Fix elixir tests 2017-04-21 12:17:23 +02:00
Evgeniy Khramtsov 02790b105e Speedup Mnesia tables initialization 2017-04-21 12:27:15 +03:00
Evgeniy Khramtsov d88c08e074 Use new cache API in mod_shared_roster_ldap 2017-04-21 10:43:14 +03:00
Evgeniy Khramtsov 9937cb48fd Use new cache API in ejabberd_oauth 2017-04-21 09:02:10 +03:00
Evgeniy Khramtsov 5444475b1d Correct option validation 2017-04-20 18:55:16 +03:00
Evgeniy Khramtsov 264a40f217 Use new cache API in mod_mam 2017-04-20 18:52:16 +03:00
Evgeniy Khramtsov a26f90a346 Use new cache API in mod_caps 2017-04-20 18:18:26 +03:00
Badlop ca9d04ba6b Fix private_get command sending a proper xmlel (#1683) 2017-04-20 16:50:08 +02:00
Badlop 4e86a71ab2 When getting user rooms, filter by the serverhost as expected (#1683) 2017-04-20 16:49:32 +02:00
Holger Weiss 3682888655 mod_stream_mgmt: Preserve stanza count on timeout
If a pending stream management session times out, call
ejabberd_c2s:process_terminated/2 *before* storing the incoming stanza
count.  Without this change, the session table entry that holds the
stanza count was purged while closing the session.
2017-04-19 23:04:20 +02:00
Holger Weiss 3adf720bc1 Use #jid{} type for #muc_unsubscribe.jid 2017-04-19 22:18:23 +02:00
Badlop d7a999eaf5 Don't use jid:from_string as it's deprecated, see jid.erl line 43 2017-04-19 21:47:59 +02:00
Evgeniy Khramtsov 64333f69ea Don't try to load already loaded applications 2017-04-19 11:40:58 +03:00
Holger Weiss b8a7720986 ejabberd_c2s: Don't close session on stream resume
Don't let ejabberd_c2s close the session and unset presence if a
'c2s_terminated' callback stops hook execution, as is done in
mod_stream_mgmt:c2s_terminated/2 on resumption.

Fixes #1680.
2017-04-19 01:20:28 +02:00
Evgeniy Khramtsov 7c9415356d Function fix_from_to/2 should not crash when 'from' is undefined
Fixes #1678
2017-04-18 01:38:35 +03:00
Evgeny Khramtsov c97aade33e Merge pull request #1677 from getong/catch_lager_crash_log_not_run
lager_crash_log in some cases not run, catch it
2017-04-17 09:44:00 +04:00
getong ab751d290a lager_crash_log in some cases not run, catch it 2017-04-17 12:07:23 +08:00
Evgeniy Khramtsov f496d22074 Improve logging message 2017-04-16 23:56:12 +03:00
Evgeniy Khramtsov 86b680a3ad Move compile_exprs() to misc module 2017-04-16 20:05:46 +03:00
Evgeniy Khramtsov 78dba217bf Speedup configuration options lookup
We now avoid excessive ETS lookups; instead, we use dynamically
compiled module 'ejabberd_options' keeping the configuration options
2017-04-16 15:29:10 +03:00
Evgeniy Khramtsov 3b14b35252 Default config file should be ejabberd.yml 2017-04-16 01:26:33 +03:00
Evgeniy Khramtsov 878c762cdf Log human readable description when configuration file is not found 2017-04-16 01:22:55 +03:00
Evgeniy Khramtsov 920f2678ac Report configuration file location on startup 2017-04-16 01:02:46 +03:00
Evgeniy Khramtsov b6182e6fe8 Speedup loading of translation files
A dump of 'translations' ETS table is now stored on disc.
The table is only re-created when new/deleted/modified translation
files are detected; otherwise, the ETS table is restored from
the dump file on startup.
2017-04-16 00:29:55 +03:00
Evgeniy Khramtsov 41fe062a8d Lower log level for some messages 2017-04-15 15:47:00 +03:00
Evgeniy Khramtsov 4c5f97bb9a Add Riak as mod_proxy65 RAM backend 2017-04-15 15:38:48 +03:00
Evgeniy Khramtsov f9c24ab16d Add Riak as mod_carboncopy RAM backend 2017-04-15 14:41:14 +03:00
Evgeniy Khramtsov 72b536b52d Add Riak as BOSH RAM backend 2017-04-15 13:52:36 +03:00
Evgeniy Khramtsov 598c79ff86 Fix cleaning of Riak route table 2017-04-15 13:36:29 +03:00
Evgeniy Khramtsov da66eb5714 Add Riak as router RAM backend 2017-04-15 13:07:56 +03:00
Evgeniy Khramtsov c290b4284f Fix closing of outbound S2S connections 2017-04-15 11:15:50 +03:00
Evgeniy Khramtsov fcb978248f Add Riak as session manager RAM backend 2017-04-15 10:02:32 +03:00
Evgeniy Khramtsov 5774edfe79 Improve ejabberd_c2s:close() 2017-04-15 08:30:41 +03:00
Evgeniy Khramtsov 5c23187d2c Make sure stream trailer is sent in the very end 2017-04-14 20:56:01 +03:00
Evgeniy Khramtsov 0a7eb33772 Better process session close 2017-04-14 20:41:25 +03:00
Evgeniy Khramtsov 9de075029b Fix a typo 2017-04-14 20:40:39 +03:00
Evgeniy Khramtsov d110cbb6e2 Fix ejabberd_router:is_my_route/1 2017-04-14 20:34:00 +03:00
Evgeniy Khramtsov 5f1f126613 Don't forget to disconnect 2017-04-14 20:32:26 +03:00
Evgeniy Khramtsov e40baf0bda Use cache in front of Redis/SQL RAM backends 2017-04-14 13:57:52 +03:00
Paweł Chmielowski aa7d5df6a0 Bump xmpp dependency, it's required by previous commit 2017-04-14 09:47:25 +02:00
Badlop 177d5fec86 Allow a room admin to unsubscribe another JID 2017-04-13 22:37:39 +02:00
Holger Weiss 0c0d79fd93 Travis CI: Update MySQL package
The "mysql-server-5.6" package is no longer available.
2017-04-11 13:52:10 +02:00
Holger Weiss 87ae2d7996 mod_muc_room: Replace deprecated function call
Use jid:decode/1 instead of jlib:string_to_jid/1.
2017-04-11 13:38:33 +02:00
Johan Oudinet d364eab74b Merge remote-tracking branch 'upstream/master' 2016-11-23 11:45:13 +01:00
Johan Oudinet 404a7c3381 Remove bashismes from ejabberdctl
To avoid unecessary extra quoting, do not call commands with sh -c ''
and use the -- option from su to supply arguments.

Parse command line parameters is a bit tricky as the previous behavior
allows to mix options to ejabberdctl with unknown options given to
the next script (usually, the ctl). This is solved by relying on the
fact that for loop saves its argument, so we can flush its content
with set -- and re-add unknown options with set -- "$@" "$arg".

Finally, remove unecessary quotes in mnesia options and in the ping
command.
2016-11-21 15:25:58 +01:00
266 changed files with 15537 additions and 12500 deletions
+3 -2
View File
@@ -21,12 +21,13 @@ before_install:
- sudo apt-key adv --import .travis/mysql_repo_key.asc
- sudo add-apt-repository 'deb http://repo.mysql.com/apt/ubuntu/ precise mysql-5.6'
- sudo apt-get -qq update
- sudo apt-get -qq -o Dpkg::Options::=--force-confold install mysql-server-5.6
- sudo apt-get -qq -o Dpkg::Options::=--force-confold install mysql-server
- sudo mysql_upgrade
# /END MYSQL 5.6
- 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 -5
View File
@@ -1,7 +1,7 @@
FROM debian:jessie-slim
MAINTAINER Rafael Römhild <rafael@roemhild.de>
ENV EJABBERD_BRANCH=17.03 \
ENV EJABBERD_BRANCH=17.08 \
EJABBERD_USER=ejabberd \
EJABBERD_HTTPS=true \
EJABBERD_STARTTLS=true \
@@ -9,7 +9,7 @@ ENV EJABBERD_BRANCH=17.03 \
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 \
' \
@@ -81,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
+4 -4
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,13 +123,13 @@ 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)/lib/*/ebin/*.beam deps/$(DEP)/lib/*/ebin/*.app))
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))
DEPS_DIRS=$(sort deps/ $(foreach DEP,$(DEPS),deps/$(DEP)/) $(dir $(DEPS_FILES)))
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl))
MAIN_FILES=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl COPYING))
MAIN_DIRS=$(sort $(dir $(MAIN_FILES)) priv/bin priv/sql)
define DEP_VERSION_template
+5 -2
View File
@@ -108,7 +108,7 @@ To compile ejabberd you need:
- GCC.
- Libexpat 1.95 or higher.
- Libyaml 0.1.4 or higher.
- Erlang/OTP 17.1 or higher.
- Erlang/OTP 17.5 or higher.
- OpenSSL 1.0.0 or higher, for STARTTLS, SASL and SSL encryption.
- Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional.
- PAM library. Optional. For Pluggable Authentication Modules (PAM).
@@ -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: {}
+41 -5
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])
@@ -101,10 +101,10 @@ AC_ARG_ENABLE(mssql,
esac],[db_type=generic])
AC_ARG_ENABLE(all,
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
[AC_HELP_STRING([--enable-all], [same as --enable-odbc --enable-mysql --enable-pgsql --enable-sqlite --enable-pam --enable-zlib --enable-riak --enable-redis --enable-elixir --enable-iconv --enable-stun --enable-sip --enable-debug --enable-tools (useful for Dialyzer checks, default: no)])],
[case "${enableval}" in
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true debug=true tools=true ;;
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false debug=false tools=false ;;
yes) odbc=true mysql=true pgsql=true sqlite=true pam=true zlib=true riak=true redis=true elixir=true iconv=true stun=true sip=true debug=true tools=true ;;
no) odbc=false mysql=false pgsql=false sqlite=false pam=false zlib=false riak=false redis=false elixir=false iconv=false stun=false sip=false debug=false tools=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
esac],[])
@@ -212,6 +212,38 @@ AC_ARG_ENABLE(latest_deps,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-latest-deps) ;;
esac],[if test "x$latest_deps" = "x"; then latest_deps=false; fi])
AC_ARG_ENABLE(system_deps,
[AC_HELP_STRING([--enable-system-deps], [makes rebar use localy installed dependences instead of downloading them (default: no)])],
[case "${enableval}" in
yes) system_deps=true ;;
no) system_deps=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-system-deps) ;;
esac],[if test "x$system_deps" = "x"; then system_deps=false; fi])
AC_ARG_ENABLE(stun,
[AC_HELP_STRING([--enable-stun], [enable STUN/TURN support (default: no)])],
[case "${enableval}" in
yes) stun=true ;;
no) stun=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-stun) ;;
esac],[if test "x$stun" = "x"; then stun=false; fi])
AC_ARG_ENABLE(sip,
[AC_HELP_STRING([--enable-sip], [enable SIP support (default: no)])],
[case "${enableval}" in
yes) sip=true ;;
no) sip=false ;;
*) 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])
@@ -253,9 +285,13 @@ AC_SUBST(riak)
AC_SUBST(redis)
AC_SUBST(elixir)
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)
AC_SUBST(CFLAGS)
AC_SUBST(CPPFLAGS)
AC_SUBST(LDFLAGS)
-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
+84 -42
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
@@ -68,7 +95,7 @@ or use a data container
```bash
docker create --name ejabberd-data rroemhild/ejabberd-data
docker run -d --name ejabberd --volumes-from processone-data rroemhild/ejabberd
docker run -d --name ejabberd --volumes-from ejabberd-data rroemhild/ejabberd
```
## SSL Certificates
@@ -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"
+19 -8
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
@@ -158,11 +160,11 @@ listen:
ip: "::"
module: ejabberd_http
request_handlers:
"/websocket": ejabberd_http_ws
"/ws": ejabberd_http_ws
"/bosh": mod_bosh
"/api": mod_http_api
## "/pub/archive": mod_http_fileserver
web_admin: true
http_bind: true
## register: true
captcha: true
##
@@ -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
@@ -265,12 +267,12 @@ listen:
## Outgoing S2S options
##
## Preferred address families (which to try first) and connect timeout
## in milliseconds.
## in seconds.
##
## outgoing_s2s_families:
## - ipv4
## - ipv6
## outgoing_s2s_timeout: 10000
## outgoing_s2s_timeout: 190
###. ==============
###' AUTHENTICATION
@@ -487,6 +489,8 @@ acl:
loopback:
ip:
- "127.0.0.0/8"
- "::1/128"
- "::FFFF:127.0.0.1/128"
##
## Bad XMPP servers
@@ -589,14 +593,14 @@ api_permissions:
who:
- access:
- allow:
- ip: "127.0.0.1/8"
- acl: loopback
- acl: admin
- oauth:
- scope: "ejabberd:admin"
- access:
- allow:
- ip: "127.0.0.1/8"
- acl: admin
- acl: loopback
- acl: admin
what:
- "*"
- "!stop"
@@ -723,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.
@@ -762,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
+175 -306
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
# define default configuration
POLL=true
@@ -7,138 +7,93 @@ 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
SCRIPT_DIR=`cd ${0%/*} && pwd`
ERL={{erl}}
IEX={{bindir}}/iex
EPMD={{epmd}}
INSTALLUSER={{installuser}}
ERL_LIBS={{libdir}}
ERL="{{erl}}"
IEX="{{bindir}}/iex"
EPMD="{{epmd}}"
INSTALLUSER="{{installuser}}"
# check the proper system user is used if defined
if [ "$INSTALLUSER" != "" ] ; then
EXEC_CMD="false"
for GID in `id -G`; do
if [ $GID -eq 0 ] ; then
INSTALLUSER_HOME=$(getent passwd "$INSTALLUSER" | cut -d: -f6)
if [ -n "$INSTALLUSER_HOME" ] && [ ! -d "$INSTALLUSER_HOME" ] ; then
mkdir -p "$INSTALLUSER_HOME"
chown "$INSTALLUSER" "$INSTALLUSER_HOME"
fi
EXEC_CMD="su $INSTALLUSER -c"
# check the proper system user is used
case $(id -un) in
"$INSTALLUSER")
EXEC_CMD="as_current_user"
;;
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
done
if [ `id -g` -eq `id -g $INSTALLUSER` ] ; then
EXEC_CMD="bash -c"
fi
if [ "$EXEC_CMD" = "false" ] ; then
echo "This command can only be run by root or the user $INSTALLUSER" >&2
exit 4
fi
else
EXEC_CMD="bash -c"
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
declare -a ARGS=()
while [ $# -ne 0 ] ; do
PARAM="$1"
shift
case $PARAM in
--) break ;;
--no-timeout) EJABBERD_NO_TIMEOUT="--no-timeout" ;;
--node) ERLANG_NODE_ARG=$1 ; shift ;;
--config-dir) ETC_DIR="$1" ; shift ;;
--config) EJABBERD_CONFIG_PATH="$1" ; shift ;;
--ctl-config) EJABBERDCTL_CONFIG_PATH="$1" ; shift ;;
--logs) LOGS_DIR="$1" ; shift ;;
--spool) SPOOL_DIR="$1" ; shift ;;
*) ARGS=("${ARGS[@]}" "$PARAM") ;;
for arg; do
case $arg in
-n|--node) ERLANG_NODE_ARG=$2; shift;;
-s|--spool) SPOOL_DIR=$2; shift;;
-l|--logs) LOGS_DIR=$2; shift;;
-f|--config) EJABBERD_CONFIG_PATH=$2; shift;;
-c|--ctl-config) EJABBERDCTL_CONFIG_PATH=$2; shift;;
-d|--config-dir) ETC_DIR=$2; shift;;
-t|--no-timeout) NO_TIMEOUT="--no-timeout";;
--) :;;
*) break;;
esac
shift
done
# Define ejabberd variable if they have not been defined from the command line
if [ "$ETC_DIR" = "" ] ; then
ETC_DIR={{sysconfdir}}/ejabberd
fi
if [ "$EJABBERDCTL_CONFIG_PATH" = "" ] ; then
EJABBERDCTL_CONFIG_PATH=$ETC_DIR/ejabberdctl.cfg
fi
if [ -f "$EJABBERDCTL_CONFIG_PATH" ] ; then
. "$EJABBERDCTL_CONFIG_PATH"
fi
if [ "$EJABBERD_CONFIG_PATH" = "" ] ; then
EJABBERD_CONFIG_PATH=$ETC_DIR/ejabberd.yml
fi
if [ "$LOGS_DIR" = "" ] ; then
LOGS_DIR={{localstatedir}}/log/ejabberd
fi
if [ "$SPOOL_DIR" = "" ] ; then
SPOOL_DIR={{localstatedir}}/lib/ejabberd
fi
if [ "$EJABBERD_DOC_PATH" = "" ] ; then
EJABBERD_DOC_PATH={{docdir}}
fi
if [ "$ERLANG_NODE_ARG" != "" ] ; then
ERLANG_NODE=$ERLANG_NODE_ARG
fi
if [ "{{release}}" != "true" -a "$EJABBERD_BIN_PATH" = "" ] ; then
EJABBERD_BIN_PATH={{libdir}}/ejabberd/priv/bin
fi
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
DATETIME=`date "+%Y%m%d-%H%M%S"`
ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump
ERL_INETRC=$ETC_DIR/inetrc
# 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"}"
[ -f "$EJABBERDCTL_CONFIG_PATH" ] && . "$EJABBERDCTL_CONFIG_PATH"
[ -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"}"
# define mnesia options
MNESIA_OPTS="-mnesia dir \"\\\"$SPOOL_DIR\\\"\" $MNESIA_OPTIONS"
# define erl parameters
ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES $ERL_OPTIONS"
KERNEL_OPTS=""
if [ "$FIREWALL_WINDOW" != "" ] ; then
KERNEL_OPTS="${KERNEL_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
INET_DIST_INTERFACE2="$(echo $INET_DIST_INTERFACE | sed 's/\./,/g')"
if [ "$INET_DIST_INTERFACE" != "$INET_DIST_INTERFACE2" ] ; then
INET_DIST_INTERFACE2="{$INET_DIST_INTERFACE2}"
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 [ -n "$INET_DIST_INTERFACE2" ] ; then
ERLANG_OPTS="$ERLANG_OPTS -kernel inet_dist_use_interface $INET_DIST_INTERFACE2"
fi
KERNEL_OPTS="${KERNEL_OPTS} -kernel inet_dist_use_interface \"${INET_DIST_INTERFACE2}\""
fi
if [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] ; then
NAME="-sname"
else
NAME="-name"
fi
IEXNAME="-$NAME"
ERL_LIBS={{libdir}}
ERL_CRASH_DUMP="$LOGS_DIR"/erl_crash_$(date "+%Y%m%d-%H%M%S").dump
ERL_INETRC="$ETC_DIR"/inetrc
# define ejabberd environment parameters
if [ "$EJABBERD_CONFIG_PATH" != "${EJABBERD_CONFIG_PATH%.yml}" ] ; then
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*://;s/ *//' $EJABBERD_CONFIG_PATH)
else
rate=$(sed '/^[ ]*log_rate_limit/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
rotate=$(sed '/^[ ]*log_rotate_size/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
count=$(sed '/^[ ]*log_rotate_count/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
date=$(sed '/^[ ]*log_rotate_date/!d;s/.*,//;s/ *//;s/}\.//' $EJABBERD_CONFIG_PATH)
fi
[ -z "$rate" ] || EJABBERD_OPTS="log_rate_limit $rate"
[ -z "$rotate" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_size $rotate"
[ -z "$count" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_count $count"
[ -z "$date" ] || EJABBERD_OPTS="${EJABBERD_OPTS} log_rotate_date '$date'"
[ -z "$EJABBERD_OPTS" ] || EJABBERD_OPTS="-ejabberd ${EJABBERD_OPTS}"
[ -d "$SPOOL_DIR" ] || $EXEC_CMD "mkdir -p $SPOOL_DIR"
cd "$SPOOL_DIR"
# define ejabberd parameters
EJABBERD_OPTS="$EJABBERD_OPTS\
$(sed '/^log_rate_limit/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
$(sed '/^log_rotate_size/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
$(sed '/^log_rotate_count/!d;s/:[ \t]*\([0-9]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")\
$(sed '/^log_rotate_date/!d;s/:[ \t]*\(.[^ ]*\).*/ \1/;s/^/ /' "$EJABBERD_CONFIG_PATH")"
[ -n "$EJABBERD_OPTS" ] && EJABBERD_OPTS="-ejabberd $EJABBERD_OPTS"
EJABBERD_OPTS="-mnesia dir \"$SPOOL_DIR\" $MNESIA_OPTIONS $EJABBERD_OPTS -s ejabberd"
# export global variables
export EJABBERD_CONFIG_PATH
export EJABBERD_LOG_PATH
export EJABBERD_BIN_PATH
export EJABBERD_DOC_PATH
export EJABBERD_PID_PATH
export ERL_CRASH_DUMP
@@ -150,116 +105,26 @@ export CONTRIB_MODULES_PATH
export CONTRIB_MODULES_CONF_DIR
export ERL_LIBS
shell_escape_str()
# run command either directly or via su $INSTALLUSER
exec_cmd()
{
if test $# -eq 0; then
printf '"" '
else
shell_escape "$@"
fi
}
shell_escape()
{
local RES=()
for i in "$@"; do
if test -z "$i"; then
printf '"" '
else
printf '%q ' "$i"
fi
done
}
# start server
start()
{
check_start
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$ERLANG_NODE\"` \
-noinput -detached \
$MNESIA_OPTS \
$KERNEL_OPTS \
$EJABBERD_OPTS \
-s ejabberd \
$ERLANG_OPTS \
`shell_escape \"${ARGS[@]}\" \"$@\"`"
$EXEC_CMD "$CMD"
}
# attach to server
debug()
{
debugwarning
NID=$(uid debug)
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
-remsh $ERLANG_NODE \
-hidden \
$KERNEL_OPTS \
$ERLANG_OPTS \
`shell_escape \"${ARGS[@]}\" \"$@\"`"
$EXEC_CMD "$CMD"
}
# attach to server using Elixir
iexdebug()
{
debugwarning
# Elixir shell is hidden as default
NID=$(uid debug)
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"$NID\"` \
-remsh $ERLANG_NODE \
--erl `shell_escape \"$KERNEL_OPTS\"` \
--erl `shell_escape \"$ERLANG_OPTS\"` \
--erl `shell_escape \"${ARGS[@]}\"` \
--erl `shell_escape_str \"$@\"`"
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
}
# start interactive server
live()
{
livewarning
CMD="`shell_escape \"$ERL\" \"$NAME\" \"${ERLANG_NODE}\"` \
$MNESIA_OPTS \
$KERNEL_OPTS \
$EJABBERD_OPTS \
-s ejabberd \
$ERLANG_OPTS \
`shell_escape \"${ARGS[@]}\" \"$@\"`"
$EXEC_CMD "$CMD"
}
# start interactive server with Elixir
iexlive()
{
livewarning
echo $@
CMD="`shell_escape \"$IEX\" \"$IEXNAME\" \"${ERLANG_NODE}\"` \
--erl \"-mnesia dir \\\"$SPOOL_DIR\\\"\" \
--erl \"`shell_escape \"$KERNEL_OPTS\"`\" \
--erl \"`shell_escape \"$EJABBERD_OPTS\"`\" \
--app ejabberd \
--erl `shell_escape \"$ERLANG_OPTS\"` \
--erl `shell_escape \"${ARGS[@]}\"` \
--erl `shell_escape_str \"$@\"`"
$EXEC_CMD "ERL_PATH=\"$ERL\" $CMD"
}
# start server in the foreground
foreground()
{
check_start
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$ERLANG_NODE\"` \
-noinput \
$MNESIA_OPTS \
$KERNEL_OPTS \
$EJABBERD_OPTS \
-s ejabberd \
$ERLANG_OPTS \
`shell_escape \"${ARGS[@]}\" \"$@\"`"
$EXEC_CMD "$CMD"
case $EXEC_CMD in
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_iex()
{
NODE=$1; shift
exec_cmd "$IEX" -${S:--}name "$NODE" --erl "$ERLANG_OPTS" "$@"
}
# usage
debugwarning()
{
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
@@ -279,14 +144,13 @@ 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
}
livewarning()
{
check_start
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
echo "--------------------------------------------------------------------"
echo ""
@@ -303,37 +167,11 @@ 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
}
etop()
{
NID=$(uid top)
$EXEC_CMD "$ERL \
$NAME $NID \
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
}
ping()
{
[ -z "$1" ] && PEER=${ERLANG_NODE} || PEER=$1
if [ "$PEER" = "${PEER%.*}" ] ; then
PING_NAME="-sname"
PING_NODE=$(hostname -s)
else
PING_NAME="-name"
PING_NODE=$(hostname)
fi
NID=$(uid ping ${PING_NODE})
$EXEC_CMD "$ERL \
$PING_NAME $NID \
-hidden $KERNEL_OPTS $ERLANG_OPTS \
-eval 'io:format(\"~p~n\",[net_adm:ping('\"'\"'$PEER'\"'\"')])' \
-s erlang halt -output text -noinput"
}
help()
{
echo ""
@@ -355,33 +193,16 @@ help()
echo ""
}
# common control function
ctl()
{
NID=$(uid ctl)
CMD="`shell_escape \"$ERL\" \"$NAME\" \"$NID\"` \
-noinput -hidden $KERNEL_OPTS -s ejabberd_ctl \
-extra `shell_escape \"$ERLANG_NODE\"` $EJABBERD_NO_TIMEOUT \
`shell_escape \"$@\"`"
$EXEC_CMD "$CMD"
result=$?
case $result in
2) help;;
3) help;;
*) :;;
esac
return $result
}
# dynamic node name helper
uid()
{
uuid=$(uuidgen 2>/dev/null)
[ -z "$uuid" -a -f /proc/sys/kernel/random/uuid ] && uuid=$(</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
@@ -391,56 +212,104 @@ stop_epmd()
}
# make sure node not already running and node name unregistered
# if all ok, ensure runtime directory exists and make it current directory
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
}
}
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
}
}
# allow sync calls
wait_for_status()
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`
[ $timeout -eq 0 ] && {
status=$1
} || {
ctl status > /dev/null
status=$?
}
while [ "$status" -ne "$1" ] ; do
sleep "$3"
timeout=$((timeout - 1))
if [ $timeout -eq 0 ] ; then
status="$1"
else
exec_erl "$(uid ctl)" -hidden -noinput -s ejabberd_ctl \
-extra "$ERLANG_NODE" $NO_TIMEOUT status > /dev/null
status="$?"
fi
done
[ $timeout -eq 0 ] && return 1 || return 0
[ $timeout -gt 0 ]
}
# main handler
case "${ARGS[0]}" in
'start') start;;
'debug') debug;;
'iexdebug') iexdebug;;
'live') live;;
'iexlive') iexlive;;
'foreground') foreground;;
'ping'*) ping ${ARGS[1]};;
'etop') etop;;
'started') wait_for_status 0 30 2;; # wait 30x2s before timeout
'stopped') wait_for_status 3 30 2 && stop_epmd;; # wait 30x2s before timeout
*) ctl "${ARGS[@]}";;
# 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
;;
foreground)
check_start
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS -noinput
;;
live)
livewarning
check_start
exec_erl "$ERLANG_NODE" $EJABBERD_OPTS
;;
debug)
debugwarning
exec_erl "$(uid debug)" -hidden -remsh "$ERLANG_NODE"
;;
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"
;;
iexlive)
livewarning
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'"')])' \
-s erlang halt -output text
;;
started)
wait_status 0 30 2 # wait 30x2s before timeout
;;
stopped)
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 "$@"
result=$?
case $result in
2|3) help;;
*) :;;
esac
exit $result
;;
esac
+2
View File
@@ -47,3 +47,5 @@
-define(HEADER(CType),
[CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
-define(BOSH_CACHE, bosh_cache).
+2 -2
View File
@@ -33,9 +33,9 @@
-define(SQL_DIR, filename:join(["priv", "sql"])).
-define(CONFIG_PATH, <<"ejabberd.cfg">>).
-define(CONFIG_PATH, <<"ejabberd.yml">>).
-define(LOG_PATH, <<"ejabberd.log">>).
-define(LOG_PATH, "ejabberd.log").
-define(EJABBERD_URI, <<"http://www.process-one.net/en/ejabberd/">>).
+2
View File
@@ -1,3 +1,5 @@
-define(ROUTES_CACHE, routes_cache).
-type local_hint() :: integer() | {apply, atom(), atom()}.
-record(route, {domain :: binary() | '_',
+2
View File
@@ -21,6 +21,8 @@
-ifndef(EJABBERD_SM_HRL).
-define(EJABBERD_SM_HRL, true).
-define(SM_CACHE, sm_cache).
-record(session, {sid, usr, us, priority, info = []}).
-record(session_counter, {vhost, count}).
-type sid() :: {erlang:timestamp(), pid()}.
+5 -2
View File
@@ -18,7 +18,10 @@
%%%
%%%----------------------------------------------------------------------
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3'.
-type matchspec_atom() :: '_' | '$1' | '$2' | '$3' | '$4'.
-record(carboncopy, {us :: {binary(), binary()} | matchspec_atom(),
resource :: binary() | matchspec_atom(),
version :: binary() | matchspec_atom()}).
version :: binary() | matchspec_atom(),
node = node() :: node() | matchspec_atom()}).
-define(CARBONCOPY_CACHE, carboncopy_cache).
-8
View File
@@ -38,11 +38,3 @@
-type listitem_type() :: none | jid | group | subscription.
-type listitem_value() :: none | both | from | to | jid:ljid() | binary().
-type listitem_action() :: allow | deny.
-record(userlist, {name = none :: none | binary(),
list = [] :: [listitem()],
needdb = false :: boolean()}).
-type userlist() :: #userlist{}.
-export_type([userlist/0]).
-7
View File
@@ -104,13 +104,6 @@
).
%% @type affiliation() = 'none' | 'owner' | 'publisher' | 'publish-only' | 'member' | 'outcast'.
-type(subscription() :: 'none'
| 'pending'
| 'unconfigured'
| 'subscribed'
).
%% @type subscription() = 'none' | 'pending' | 'unconfigured' | 'subscribed'.
-type(accessModel() :: 'open'
| 'presence'
| 'roster'
+1
View File
@@ -0,0 +1 @@
-define(T(S), <<S>>).
+13 -13
View File
@@ -3,18 +3,18 @@ defmodule Ejabberd.Mixfile do
def project do
[app: :ejabberd,
version: "17.03.0",
description: description,
elixir: "~> 1.3",
version: "17.9.0",
description: description(),
elixir: "~> 1.4",
elixirc_paths: ["lib"],
compile_path: ".",
compilers: [:asn1] ++ Mix.compilers,
erlc_options: erlc_options,
erlc_options: erlc_options(),
erlc_paths: ["asn1", "src"],
# Elixir tests are starting the part of ejabberd they need
aliases: [test: "test --no-start"],
package: package,
deps: deps]
package: package(),
deps: deps()]
end
def description do
@@ -29,7 +29,7 @@ defmodule Ejabberd.Mixfile do
included_applications: [:lager, :mnesia, :inets, :p1_utils, :cache_tab,
:fast_tls, :stringprep, :fast_xml, :xmpp,
:stun, :fast_yaml, :esip, :jiffy, :p1_oauth2]
++ cond_apps]
++ cond_apps()]
end
defp erlc_options do
@@ -39,7 +39,7 @@ defmodule Ejabberd.Mixfile do
end
defp deps do
[{:lager, "~> 3.2"},
[{:lager, "~> 3.4.0"},
{:p1_utils, "~> 1.0"},
{:fast_xml, "~> 1.1"},
{:xmpp, "~> 1.1"},
@@ -53,7 +53,7 @@ defmodule Ejabberd.Mixfile do
{:p1_oauth2, "~> 0.6.1"},
{:distillery, "~> 1.0"},
{:ex_doc, ">= 0.0.0", only: :dev}]
++ cond_deps
++ cond_deps()
end
defp deps_include(deps) do
@@ -89,7 +89,7 @@ defmodule Ejabberd.Mixfile do
app
end
def package do
defp package do
[# These are the default files included in the package
files: ["lib", "src", "priv", "mix.exs", "include", "README.md", "COPYING"],
maintainers: ["ProcessOne"],
@@ -100,7 +100,7 @@ defmodule Ejabberd.Mixfile do
"ProcessOne" => "http://www.process-one.net/"}]
end
def vars do
defp vars do
case :file.consult("vars.config") do
{:ok,config} -> config
_ -> [zlib: true, iconv: true]
@@ -108,7 +108,7 @@ defmodule Ejabberd.Mixfile do
end
defp config(key) do
case vars[key] do
case vars()[key] do
nil -> false
value -> value
end
@@ -142,7 +142,7 @@ defmodule Mix.Tasks.Compile.Asn1 do
end)
end
def manifests, do: [manifest]
def manifests, do: [manifest()]
defp manifest, do: Path.join(Mix.Project.manifest_path, @manifest)
def clean, do: Erlang.clean(manifest())
+16 -16
View File
@@ -1,22 +1,22 @@
%{"cache_tab": {:hex, :cache_tab, "1.0.7", "8e2c958c0c2178e6c015aa7340c761521dc71b642192a5a7887f4aeffa29c7b1", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"distillery": {:hex, :distillery, "1.2.2", "d5a52920cbe2378c8a21dfc83b526b4225944b9dce7bf170fe5f5cddda81ffb3", [:mix], []},
"earmark": {:hex, :earmark, "1.2.0", "bf1ce17aea43ab62f6943b97bd6e3dc032ce45d4f787504e3adf738e54b42f3a", [:mix], []},
%{"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.11", "eeb0b1cbb64d56201dd6abb09126afab1e96da2418e833cba6c55f890f2f6649", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}, {:stun, "1.0.10", [hex: :stun, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.15.0", "e73333785eef3488cf9144a6e847d3d647e67d02bd6fdac500687854dd5c599f", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, optional: false]}]},
"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.11", "8ccff4b68e6bb79b91c689da8cf92ec1006a575d2b6a09ac1ed5f9bf4724a39a", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"fast_xml": {:hex, :fast_xml, "1.1.22", "7eb81a738218541208fa3a126ee36197fb0346852d8c12ad678039e539e019df", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"fast_yaml": {:hex, :fast_yaml, "1.0.9", "1bf41a576d3eedcb690499350994932340908b4968832adcec4b55152d4e5f20", [:rebar3], [{:p1_utils, "1.0.7", [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.4", "faa4ac6755567a2806c7bee2cf26fc318016794ffc25481f7230db5f9d025dd8", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"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.2.4", "a6deb74dae7927f46bd13255268308ef03eb206ec784a94eaf7c1c0f3b811615", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, optional: false]}]},
"p1_mysql": {:hex, :p1_mysql, "1.0.2", "893a99415f98ce8b6ad014ef950d4e878895787b6c8333587f1e506f831571e0", [: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.2", "27d3137e0b0098808d9c60bf197344669ed1107ed47ce4af2254099a62ccc27e", [:rebar3], []},
"p1_utils": {:hex, :p1_utils, "1.0.7", "030adbce8935f1b87aaedfdb037d3127cc671ee3e1904b394e6dde9e449d6979", [: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.8", "870d72db031796177261af88d1e6eb081dc314ad217377d441e5ea3c8504a310", [:rebar3], [{:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"stun": {:hex, :stun, "1.0.10", "9fa83d4c5a76ca5ed3b536852ea00f3fbd2023241559ed6cb23a4ada62183b44", [:rebar3], [{:fast_tls, "1.0.11", [hex: :fast_tls, optional: false]}, {:p1_utils, "1.0.7", [hex: :p1_utils, optional: false]}]},
"xmpp": {:hex, :xmpp, "1.1.9", "3548dc09faa414ee437c5db53a24af691724cb984b73af832d547c83f50313b9", [:rebar3], [{:fast_xml, "1.1.22", [hex: :fast_xml, optional: false]}, {:stringprep, "1.0.8", [hex: :stringprep, optional: false]}]}}
"stringprep": {:hex, :stringprep, "1.0.9", "9182ba39931cd1db528b8883cad0d63530abe2bf21835d26cec2f9af8bc00be0", [:rebar3], [{:p1_utils, "1.0.9", [hex: :p1_utils, 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]}]}}
+126
View File
@@ -0,0 +1,126 @@
-module(override_deps_versions).
-export([preprocess/2, 'pre_update-deps'/2, new_replace/1, new_replace/0]).
preprocess(Config, _Dirs) ->
update_deps(Config).
update_deps(Config) ->
LocalDeps = rebar_config:get_local(Config, deps, []),
TopDeps = case rebar_config:get_xconf(Config, top_deps, []) of
[] -> LocalDeps;
Val -> Val
end,
Config2 = rebar_config:set_xconf(Config, top_deps, TopDeps),
NewDeps = lists:map(fun({Name, _, _} = Dep) ->
case lists:keyfind(Name, 1, TopDeps) of
false -> Dep;
TopDep -> TopDep
end
end, LocalDeps),
%io:format("LD ~p~n", [LocalDeps]),
%io:format("TD ~p~n", [TopDeps]),
Config3 = rebar_config:set(Config2, deps, NewDeps),
{ok, Config3, []}.
'pre_update-deps'(Config, _Dirs) ->
{ok, Config2, _} = update_deps(Config),
case code:is_loaded(old_rebar_config) of
false ->
{_, Beam, _} = code:get_object_code(rebar_config),
NBeam = rename(Beam, old_rebar_config),
code:load_binary(old_rebar_config, "blank", NBeam),
replace_mod(Beam);
_ ->
ok
end,
{ok, Config2}.
new_replace() ->
old_rebar_config:new().
new_replace(Config) ->
NC = old_rebar_config:new(Config),
{ok, Conf, _} = update_deps(NC),
Conf.
replace_mod(Beam) ->
{ok, {_, [{exports, Exports}]}} = beam_lib:chunks(Beam, [exports]),
Funcs = lists:filtermap(
fun({module_info, _}) ->
false;
({Name, Arity}) ->
Args = args(Arity),
Call = case Name of
new ->
[erl_syntax:application(
erl_syntax:abstract(override_deps_versions),
erl_syntax:abstract(new_replace),
Args)];
_ ->
[erl_syntax:application(
erl_syntax:abstract(old_rebar_config),
erl_syntax:abstract(Name),
Args)]
end,
{true, erl_syntax:function(erl_syntax:abstract(Name),
[erl_syntax:clause(Args, none,
Call)])}
end, Exports),
Forms0 = ([erl_syntax:attribute(erl_syntax:abstract(module),
[erl_syntax:abstract(rebar_config)])]
++ Funcs),
Forms = [erl_syntax:revert(Form) || Form <- Forms0],
%io:format("--------------------------------------------------~n"
% "~s~n",
% [[erl_pp:form(Form) || Form <- Forms]]),
{ok, Mod, Bin} = compile:forms(Forms, [report, export_all]),
code:purge(rebar_config),
{module, Mod} = code:load_binary(rebar_config, "mock", Bin).
args(0) ->
[];
args(N) ->
[arg(N) | args(N-1)].
arg(N) ->
erl_syntax:variable(list_to_atom("A"++integer_to_list(N))).
rename(BeamBin0, Name) ->
BeamBin = replace_in_atab(BeamBin0, Name),
update_form_size(BeamBin).
%% Replace the first atom of the atom table with the new name
replace_in_atab(<<"Atom", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
replace_first_atom(<<"Atom">>, Cnk, CnkSz0, Rest, latin1, Name);
replace_in_atab(<<"AtU8", CnkSz0:32, Cnk:CnkSz0/binary, Rest/binary>>, Name) ->
replace_first_atom(<<"AtU8">>, Cnk, CnkSz0, Rest, unicode, Name);
replace_in_atab(<<C, Rest/binary>>, Name) ->
<<C, (replace_in_atab(Rest, Name))/binary>>.
replace_first_atom(CnkName, Cnk, CnkSz0, Rest, Encoding, Name) ->
<<NumAtoms:32, NameSz0:8, _Name0:NameSz0/binary, CnkRest/binary>> = Cnk,
NumPad0 = num_pad_bytes(CnkSz0),
<<_:NumPad0/unit:8, NextCnks/binary>> = Rest,
NameBin = atom_to_binary(Name, Encoding),
NameSz = byte_size(NameBin),
CnkSz = CnkSz0 + NameSz - NameSz0,
NumPad = num_pad_bytes(CnkSz),
<<CnkName/binary, CnkSz:32, NumAtoms:32, NameSz:8, NameBin:NameSz/binary,
CnkRest/binary, 0:NumPad/unit:8, NextCnks/binary>>.
%% Calculate the number of padding bytes that have to be added for the
%% BinSize to be an even multiple of ?beam_num_bytes_alignment.
num_pad_bytes(BinSize) ->
case 4 - (BinSize rem 4) of
4 -> 0;
N -> N
end.
%% Update the size within the top-level form
update_form_size(<<"FOR1", _OldSz:32, Rest/binary>> = Bin) ->
Sz = size(Bin) - 8,
<<"FOR1", Sz:32, Rest/binary>>.
+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.
+33 -25
View File
@@ -18,39 +18,41 @@
%%%
%%%----------------------------------------------------------------------
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.8"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.7"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.11"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.8"}}},
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.21"}}},
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.1.9"}}},
{stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.10"}}},
{esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.11"}}},
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.9"}}},
{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.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.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.2"}}}},
{tag, "1.0.4"}}}},
{if_var_true, pgsql, {p1_pgsql, ".*", {git, "https://github.com/processone/p1_pgsql",
{tag, "1.1.2"}}}},
{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.2"}}}},
{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.2.6", "v1.1.1"}}}}},
{tag, {if_version_above, "17", "v1.4.4", "v1.1.1"}}}}},
%% TODO: When modules are fully migrated to new structure and mix, we will not need anymore rebar_elixir_plugin
{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.4"}}}},
{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",
@@ -64,18 +66,19 @@
stringprep,
fast_xml,
esip,
luerl,
stun,
fast_yaml,
xmpp,
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"},
@@ -84,12 +87,17 @@
{i, "deps/p1_utils/include"},
{if_var_false, debug, no_debug_info},
{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_version_above, "18", {d, 'STRONG_RAND_BYTES'}},
{if_version_above, "17", {d, 'GB_SETS_ITERATOR_FROM'}},
{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},
{src_dirs, [asn1, src,
{if_var_true, tools, tools},
@@ -99,7 +107,7 @@
{if_rebar3, {plugins, [rebar3_hex, {provider_asn1, "0.2.0"}]}}.
{if_not_rebar3, {plugins, [
deps_erl_opts,
deps_erl_opts, override_deps_versions, override_opts,
{if_var_true, elixir, rebar_elixir_compiler},
{if_var_true, elixir, rebar_exunit}
]}}.
@@ -145,16 +153,16 @@
{post_hook_configure, [{"fast_tls", []},
{"stringprep", []},
{"fast_yaml", []},
{"esip", []},
{if_var_true, sip, {"esip", []}},
{"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:
+21 -9
View File
@@ -23,10 +23,12 @@ Vars = case file:consult(filename:join([filename:dirname(SCRIPT),"vars.config"])
Terms;
_Err ->
[]
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"}, {ldflags, ""}],
end ++ [{cflags, "-g -O2 -Wall"}, {cppflags, "-g -O2 -Wall"},
{ldflags, ""}, {system_deps, false}],
{cflags, CFlags} = lists:keyfind(cflags, 1, Vars),
{cppflags, CPPFlags} = lists:keyfind(cppflags, 1, Vars),
{ldflags, LDFlags} = lists:keyfind(ldflags, 1, Vars),
{system_deps, SystemDeps} = lists:keyfind(system_deps, 1, Vars),
GetCfg0 = fun(F, Cfg, [Key | Tail], Default) ->
Val = case lists:keyfind(Key, 1, Cfg) of
@@ -141,6 +143,15 @@ ProcessVars = fun(_F, [], Acc) ->
_ ->
F(F, Tail, Acc)
end;
(F, [{if_have_fun, MFA, Value} | Tail], Acc) ->
{Mod, Fun, Arity} = MFA,
code:ensure_loaded(Mod),
case erlang:function_exported(Mod, Fun, Arity) of
true ->
F(F, Tail, ProcessSingleVar(F, Value, Acc));
false ->
F(F, Tail, Acc)
end;
(F, [Other1 | Tail1], Acc) ->
F(F, Tail1, [F(F, Other1, []) | Acc]);
(F, Val, Acc) when is_tuple(Val) ->
@@ -244,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, "\"",
@@ -258,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,
@@ -315,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), "", []},
@@ -327,11 +338,12 @@ Rules = [
ProcessFloatingDeps, [], []},
{[deps], IsRebar3,
Rebar3DepsFilter, []},
{[deps], os:getenv("USE_GLOBAL_DEPS") /= false,
{[deps], SystemDeps /= false,
GlobalDepsFilter, []}
],
Config = FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
Config = [{plugin_dir, filename:join([filename:dirname(SCRIPT),"plugins"])}]++
FilterConfig(FilterConfig, ProcessVars(ProcessVars, CONFIG, []), Rules),
%io:format("ejabberd configuration:~n ~p~n", [Config]),
-6
View File
@@ -116,12 +116,6 @@ CREATE TABLE vcard (
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE vcard_xupdate (
username text PRIMARY KEY,
hash text NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE vcard_search (
username text NOT NULL,
lusername text PRIMARY KEY,
-10
View File
@@ -470,16 +470,6 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
CREATE INDEX [vcard_search_lorgunit] ON [vcard_search] (lorgunit)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON);
CREATE TABLE [dbo].[vcard_xupdate] (
[username] [varchar] (250) NOT NULL,
[hash] [text] NOT NULL,
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
CONSTRAINT [vcard_xupdate_PRIMARY] PRIMARY KEY CLUSTERED
(
[username] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
) TEXTIMAGE_ON [PRIMARY];
ALTER TABLE [dbo].[pubsub_item] WITH CHECK ADD CONSTRAINT [pubsub_item_ibfk_1] FOREIGN KEY([nodeid])
REFERENCES [dbo].[pubsub_node] ([nodeid])
ON DELETE CASCADE;
+1 -7
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);
@@ -121,12 +121,6 @@ CREATE TABLE vcard (
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE vcard_xupdate (
username varchar(191) PRIMARY KEY,
hash text NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE vcard_search (
username varchar(191) NOT NULL,
lusername varchar(191) PRIMARY KEY,
-6
View File
@@ -120,12 +120,6 @@ CREATE TABLE vcard (
created_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE TABLE vcard_xupdate (
username text PRIMARY KEY,
hash text NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE TABLE vcard_search (
username text NOT NULL,
lusername text PRIMARY KEY,
+45 -27
View File
@@ -92,8 +92,6 @@ init([]) ->
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, access)}]),
mnesia:add_table_copy(acl, node(), ram_copies),
mnesia:add_table_copy(access, node(), ram_copies),
ejabberd_hooks:add(config_reloaded, ?MODULE, load_from_config, 20),
load_from_config(),
{ok, #state{}}.
@@ -201,13 +199,13 @@ load_from_config() ->
lists:foreach(
fun(Host) ->
ACLs = ejabberd_config:get_option(
{acl, Host}, fun(V) -> V end, []),
{acl, Host}, []),
AccessRules = ejabberd_config:get_option(
{access, Host}, fun(V) -> V end, []),
{access, Host}, []),
AccessRulesNew = ejabberd_config:get_option(
{access_rules, Host}, fun(V) -> V end, []),
{access_rules, Host}, []),
ShaperRules = ejabberd_config:get_option(
{shaper_rules, Host}, fun(V) -> V end, []),
{shaper_rules, Host}, []),
lists:foreach(
fun({ACLName, SpecList}) ->
lists:foreach(
@@ -268,24 +266,42 @@ normalize_spec(Spec) ->
case Spec of
all -> all;
none -> none;
{acl, N} -> {acl, N};
{user, {U, S}} -> {user, {nodeprep(U), nameprep(S)}};
{user, U} -> {user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
{shared_group, {G, H}} -> {shared_group, {b(G), nameprep(H)}};
{shared_group, G} -> {shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
{user_regexp, {UR, S}} -> {user_regexp, {b(UR), nameprep(S)}};
{user_regexp, UR} -> {user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_regexp, {UR, SR}} -> {node_regexp, {b(UR), b(SR)}};
{user_glob, {UR, S}} -> {user_glob, {b(UR), nameprep(S)}};
{user_glob, UR} -> {user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_glob, {UR, SR}} -> {node_glob, {b(UR), b(SR)}};
{server, S} -> {server, nameprep(S)};
{resource, R} -> {resource, resourceprep(R)};
{server_regexp, SR} -> {server_regexp, b(SR)};
{resource_regexp, R} -> {resource_regexp, b(R)};
{server_glob, S} -> {server_glob, b(S)};
{resource_glob, R} -> {resource_glob, b(R)};
{ip, {Net, Mask}} -> {ip, {Net, Mask}};
{acl, N} when is_atom(N) ->
{acl, N};
{user, {U, S}} when is_binary(U), is_binary(S) ->
{user, {nodeprep(U), nameprep(S)}};
{user, U} when is_binary(U) ->
{user, split_user_server(U, fun nodeprep/1, fun nameprep/1)};
{shared_group, {G, H}} when is_binary(G), is_binary(H) ->
{shared_group, {b(G), nameprep(H)}};
{shared_group, G} when is_binary(G) ->
{shared_group, split_user_server(G, fun b/1, fun nameprep/1)};
{user_regexp, {UR, S}} when is_binary(UR), is_binary(S) ->
{user_regexp, {b(UR), nameprep(S)}};
{user_regexp, UR} when is_binary(UR) ->
{user_regexp, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_regexp, {UR, SR}} when is_binary(UR), is_binary(SR) ->
{node_regexp, {b(UR), b(SR)}};
{user_glob, {UR, S}} when is_binary(UR), is_binary(S) ->
{user_glob, {b(UR), nameprep(S)}};
{user_glob, UR} when is_binary(UR) ->
{user_glob, split_user_server(UR, fun b/1, fun nameprep/1)};
{node_glob, {UR, SR}} when is_binary(UR), is_binary(SR) ->
{node_glob, {b(UR), b(SR)}};
{server, S} when is_binary(S) ->
{server, nameprep(S)};
{resource, R} when is_binary(R) ->
{resource, resourceprep(R)};
{server_regexp, SR} when is_binary(SR) ->
{server_regexp, b(SR)};
{resource_regexp, R} when is_binary(R) ->
{resource_regexp, b(R)};
{server_glob, S} when is_binary(S) ->
{server_glob, b(S)};
{resource_glob, R} when is_binary(R) ->
{resource_glob, b(R)};
{ip, {Net, Mask}} when is_binary(Net), is_integer(Mask) ->
{ip, {Net, Mask}};
{ip, S} ->
case parse_ip_netmask(b(S)) of
{ok, Net, Mask} ->
@@ -293,7 +309,9 @@ normalize_spec(Spec) ->
error ->
?INFO_MSG("Invalid network address: ~p", [S]),
none
end
end;
BadVal ->
throw({<<"Invalid acl value">>, BadVal})
end.
-spec any_rules_allowed(global | binary(), [access_name()],
@@ -607,7 +625,7 @@ access_rules_validator(Rules0) ->
(deny) -> true;
(_) -> false
end),
throw({replace_with, Rules}).
Rules.
shaper_rules_validator(Name) when is_atom(Name) ->
@@ -618,7 +636,7 @@ shaper_rules_validator(Rules0) ->
(V2) when is_integer(V2) -> true;
(_) -> false
end),
throw({replace_with, Rules}).
Rules.
access_shaper_rules_validator([{Type, Acls} = Rule | Rest], RuleTypeCheck) ->
case RuleTypeCheck(Type) of
+16 -24
View File
@@ -55,7 +55,7 @@
check_password :: check_password_fun(),
auth_module :: atom(),
host = <<"">> :: binary(),
hostfqdn = <<"">> :: binary() | [binary()]}).
hostfqdn = [] :: [binary()]}).
start(_Opts) ->
Fqdn = get_local_fqdn(),
@@ -204,8 +204,6 @@ is_digesturi_valid(DigestURICase, JabberDomain,
false
end.
is_host_fqdn(Host, Fqdn) when is_binary(Fqdn) ->
Host == Fqdn;
is_host_fqdn(_Host, []) ->
false;
is_host_fqdn(Host, [Fqdn | _FqdnTail]) when Host == Fqdn ->
@@ -214,26 +212,13 @@ is_host_fqdn(Host, [Fqdn | FqdnTail]) when Host /= Fqdn ->
is_host_fqdn(Host, FqdnTail).
get_local_fqdn() ->
case catch get_local_fqdn2() of
Str when is_binary(Str) -> Str;
List when is_list(List) -> List;
_ ->
<<"unknown-fqdn, please configure fqdn "
"option in ejabberd.yml!">>
end.
get_local_fqdn2() ->
case ejabberd_config:get_option(
fqdn, fun(X) -> X end) of
ConfiguredFqdn when is_binary(ConfiguredFqdn) ->
ConfiguredFqdn;
[A | _] = ConfiguredFqdns when is_binary(A) ->
ConfiguredFqdns;
undefined ->
{ok, Hostname} = inet:gethostname(),
{ok, {hostent, Fqdn, _, _, _, _}} =
inet:gethostbyname(Hostname),
list_to_binary(Fqdn)
case ejabberd_config:get_option(fqdn) of
undefined ->
{ok, Hostname} = inet:gethostname(),
{ok, {hostent, Fqdn, _, _, _, _}} = inet:gethostbyname(Hostname),
[list_to_binary(Fqdn)];
Fqdn ->
Fqdn
end.
hex(S) ->
@@ -275,5 +260,12 @@ response(KeyVals, User, Passwd, Nonce, AuthzId,
":", (hex((erlang:md5(A2))))/binary>>,
hex((erlang:md5(T))).
opt_type(fqdn) -> fun iolist_to_binary/1;
-spec opt_type(fqdn) -> fun((binary() | [binary()]) -> [binary()]);
(atom()) -> [atom()].
opt_type(fqdn) ->
fun(FQDN) when is_binary(FQDN) ->
[FQDN];
(FQDNs) when is_list(FQDNs) ->
[iolist_to_binary(FQDN) || FQDN <- FQDNs]
end;
opt_type(_) -> [fqdn].
+14 -6
View File
@@ -111,7 +111,11 @@ mech_step(#state{step = 2} = State, ClientIn) ->
{error, saslprep_failed, UserName};
true ->
{StoredKey, ServerKey, Salt, IterationCount} =
if is_tuple(Pass) -> Pass;
if is_record(Pass, scram) ->
{base64:decode(Pass#scram.storedkey),
base64:decode(Pass#scram.serverkey),
base64:decode(Pass#scram.salt),
Pass#scram.iterationcount};
true ->
TempSalt =
randoms:bytes(?SALT_LENGTH),
@@ -128,14 +132,14 @@ mech_step(#state{step = 2} = State, ClientIn) ->
str:substr(ClientIn,
str:str(ClientIn, <<"n=">>)),
ServerNonce =
misc:encode_base64(randoms:bytes(?NONCE_LENGTH)),
base64:encode(randoms:bytes(?NONCE_LENGTH)),
ServerFirstMessage =
iolist_to_binary(
["r=",
ClientNonce,
ServerNonce,
",", "s=",
misc:encode_base64(Salt),
base64:encode(Salt),
",", "i=",
integer_to_list(IterationCount)]),
{continue, ServerFirstMessage,
@@ -161,7 +165,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
ClientProofAttribute] ->
case parse_attribute(GS2ChannelBindingAttribute) of
{$c, CVal} ->
ChannelBindingSupport = binary:at(misc:decode_base64(CVal), 0),
ChannelBindingSupport = try binary:first(base64:decode(CVal))
catch _:badarg -> 0
end,
if (ChannelBindingSupport == $n)
or (ChannelBindingSupport == $y) ->
Nonce = <<(State#state.client_nonce)/binary,
@@ -170,7 +176,9 @@ mech_step(#state{step = 4} = State, ClientIn) ->
{$r, CompareNonce} when CompareNonce == Nonce ->
case parse_attribute(ClientProofAttribute) of
{$p, ClientProofB64} ->
ClientProof = misc:decode_base64(ClientProofB64),
ClientProof = try base64:decode(ClientProofB64)
catch _:badarg -> <<>>
end,
AuthMessage = iolist_to_binary(
[State#state.auth_message,
",",
@@ -191,7 +199,7 @@ mech_step(#state{step = 4} = State, ClientIn) ->
{auth_module, State#state.auth_module},
{authzid, State#state.username}],
<<"v=",
(misc:encode_base64(ServerSignature))/binary>>};
(base64:encode(ServerSignature))/binary>>};
true -> {error, not_authorized, State#state.username}
end;
_ -> {error, bad_attribute}
+2 -5
View File
@@ -239,8 +239,7 @@ get_definitions(#state{definitions = none, fragments_generators = Gens} = State)
[{acl,{acl,admin}},
{oauth,[<<"ejabberd:admin">>],[{acl,{acl,admin}}]}],
{all, [start, stop]}}}],
ApiPerms = ejabberd_config:get_option(api_permissions, fun(A) -> A end,
DefaultOptions),
ApiPerms = ejabberd_config:get_option(api_permissions, DefaultOptions),
AllCommands = ejabberd_commands:get_commands_definition(),
Frags = lists:foldl(
fun({_Name, Generator}, Acc) ->
@@ -334,7 +333,7 @@ command_matches_patterns(C, [_ | Tail]) ->
%%%===================================================================
parse_api_permissions(Data) when is_list(Data) ->
throw({replace_with, [parse_api_permission(Name, Args) || {Name, Args} <- Data]}).
[parse_api_permission(Name, Args) || {Name, Args} <- Data].
parse_api_permission(Name, Args0) ->
Args = lists:flatten(Args0),
@@ -374,8 +373,6 @@ parse_who(Name, Defs, ParseOauth) when is_list(Defs) ->
throw:{invalid_syntax, Msg} ->
report_error(<<"Invalid access rule: '~s' used inside 'who' section for api_permission '~s'">>,
[Msg, Name]);
throw:{replace_with, NVal} ->
{access, NVal};
error:_ ->
report_error(<<"Invalid access rule '~p' used inside 'who' section for api_permission '~s'">>,
[Val, Name])
+16 -6
View File
@@ -54,6 +54,7 @@
dump_to_textfile/1, dump_to_textfile/2,
mnesia_change_nodename/4,
restore/1, % Still used by some modules
clear_cache/0,
get_commands_spec/0
]).
%% gen_server callbacks
@@ -137,7 +138,7 @@ get_commands_spec() ->
desc = "Get the current loglevel",
module = ejabberd_logger, function = get,
result_desc = "Tuple with the log level number, its keyword and description",
result_example = {4, <<"info">>, <<"Info">>},
result_example = {4, info, <<"Info">>},
args = [],
result = {leveltuple, {tuple, [{levelnumber, integer},
{levelatom, atom},
@@ -302,6 +303,7 @@ get_commands_spec() ->
#ejabberd_commands{name = export2sql, tags = [mnesia],
desc = "Export virtual host information from Mnesia tables to SQL file",
longdesc = "Configure the modules to use SQL, then call this command.",
module = ejd2sql, function = export,
args_desc = ["Vhost", "Full path to the destination SQL file"],
args_example = ["example.com", "/var/lib/ejabberd/example.com.sql"],
@@ -360,7 +362,11 @@ get_commands_spec() ->
module = ?MODULE, function = install_fallback_mnesia,
args_desc = ["Full path to the fallback file"],
args_example = ["/var/lib/ejabberd/database.fallback"],
args = [{file, string}], result = {res, restuple}}
args = [{file, string}], result = {res, restuple}},
#ejabberd_commands{name = clear_cache, tags = [server],
desc = "Clear database cache on all nodes",
module = ?MODULE, function = clear_cache,
args = [], result = {res, rescode}}
].
@@ -473,9 +479,9 @@ update_module(ModuleNameString) ->
register(User, Host, Password) ->
case ejabberd_auth:try_register(User, Host, Password) of
{atomic, ok} ->
ok ->
{ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
{atomic, exists} ->
{error, exists} ->
Msg = io_lib:format("User ~s@~s already registered", [User, Host]),
{error, conflict, 10090, Msg};
{error, Reason} ->
@@ -489,7 +495,7 @@ unregister(User, Host) ->
{ok, ""}.
registered_users(Host) ->
Users = ejabberd_auth:get_vh_registered_users(Host),
Users = ejabberd_auth:get_users(Host),
SUsers = lists:sort(Users),
lists:map(fun({U, _S}) -> U end, SUsers).
@@ -611,7 +617,7 @@ restore(Path) ->
%% Obsolete tables or tables created by module who are no longer used are not
%% restored and are ignored.
keep_tables() ->
lists:flatten([acl, passwd, config, local_config,
lists:flatten([acl, passwd, config,
keep_modules_tables()]).
%% Returns the list of modules tables in use, according to the list of actually
@@ -759,3 +765,7 @@ mnesia_change_nodename(FromString, ToString, Source, Target) ->
{[Other], Acc}
end,
mnesia:traverse_backup(Source, Target, Convert, switched).
clear_cache() ->
Nodes = ejabberd_cluster:get_nodes(),
lists:foreach(fun(T) -> ets_cache:clear(T, Nodes) end, ets_cache:all()).
+15 -53
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").
@@ -46,19 +45,19 @@ start(normal, _Args) ->
start_apps(),
start_elixir_application(),
ejabberd:check_app(ejabberd),
db_init(),
setup_if_elixir_conf_used(),
ejabberd_config:start(),
set_settings_from_config(),
ejabberd_mnesia:start(),
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]),
lists:foreach(fun erlang:garbage_collect/1, processes()),
{ok, SupPid};
Err ->
Err
@@ -87,40 +86,6 @@ stop(_State) ->
%%% Internal functions
%%%
db_init() ->
ejabberd_config:env_binary_to_list(mnesia, dir),
MyNode = node(),
DbNodes = mnesia:system_info(db_nodes),
case lists:member(MyNode, DbNodes) of
true ->
ok;
false ->
?CRITICAL_MSG("Node name mismatch: I'm [~s], "
"the database is owned by ~p", [MyNode, DbNodes]),
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
"or change node name in Mnesia", []),
erlang:error(node_name_mismatch)
end,
case mnesia:system_info(extra_db_nodes) of
[] ->
mnesia:create_schema([node()]);
_ ->
ok
end,
ejabberd:start_app(mnesia, permanent),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity).
connect_nodes() ->
Nodes = ejabberd_config:get_option(
cluster_nodes,
fun(Ns) ->
true = lists:all(fun is_atom/1, Ns),
Ns
end, []),
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
@@ -163,13 +128,6 @@ delete_pid_file() ->
file:delete(PidFilename)
end.
set_settings_from_config() ->
Ticktime = ejabberd_config:get_option(
net_ticktime,
opt_type(net_ticktime),
60),
net_kernel:set_net_ticktime(Ticktime).
file_queue_init() ->
QueueDir = case ejabberd_config:queue_dir() of
undefined ->
@@ -184,16 +142,12 @@ start_apps() ->
crypto:start(),
ejabberd:start_app(sasl),
ejabberd:start_app(ssl),
ejabberd:start_app(p1_utils),
ejabberd:start_app(fast_yaml),
ejabberd:start_app(fast_tls),
ejabberd:start_app(xmpp),
ejabberd:start_app(cache_tab).
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
@@ -217,3 +171,11 @@ start_elixir_application() ->
_ ->
ok
end.
-ifdef(GRAPHICS).
start_eimp() ->
ejabberd:start_app(eimp).
-else.
start_eimp() ->
ok.
-endif.
+595 -284
View File
File diff suppressed because it is too large Load Diff
+28 -87
View File
@@ -26,9 +26,11 @@
-module(ejabberd_auth_anonymous).
-behaviour(ejabberd_config).
-behaviour(ejabberd_auth).
-author('mickael.remond@process-one.net').
-export([start/1,
stop/1,
allow_anonymous/1,
is_sasl_anonymous_enabled/1,
is_login_anonymous_enabled/1,
@@ -38,15 +40,9 @@
unregister_connection/3
]).
-export([login/2, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password_s/2,
get_password/2, get_password/3, is_user_exists/2,
remove_user/2, remove_user/3, store_type/0,
plain_password_required/0, opt_type/1]).
-export([login/2, check_password/4, user_exists/2,
get_users/2, count_users/2, store_type/1,
plain_password_required/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -59,6 +55,12 @@ start(Host) ->
?MODULE, unregister_connection, 100),
ok.
stop(Host) ->
ejabberd_hooks:delete(sm_register_connection_hook, Host,
?MODULE, register_connection, 100),
ejabberd_hooks:delete(sm_remove_connection_hook, Host,
?MODULE, unregister_connection, 100).
%% Return true if anonymous is allowed for host or false otherwise
allow_anonymous(Host) ->
lists:member(?MODULE, ejabberd_auth:auth_modules(Host)).
@@ -93,21 +95,12 @@ is_login_anonymous_enabled(Host) ->
%% Return the anonymous protocol to use: sasl_anon|login_anon|both
%% defaults to login_anon
anonymous_protocol(Host) ->
ejabberd_config:get_option(
{anonymous_protocol, Host},
fun(sasl_anon) -> sasl_anon;
(login_anon) -> login_anon;
(both) -> both
end,
sasl_anon).
ejabberd_config:get_option({anonymous_protocol, Host}, sasl_anon).
%% Return true if multiple connections have been allowed in the config file
%% defaults to false
allow_multiple_connections(Host) ->
ejabberd_config:get_option(
{allow_multiple_connections, Host},
fun(V) when is_boolean(V) -> V end,
false).
ejabberd_config:get_option({allow_multiple_connections, Host}, false).
anonymous_user_exist(User, Server) ->
lists:any(
@@ -140,17 +133,9 @@ unregister_connection(_SID,
%% ---------------------------------
%% Specific anonymous auth functions
%% ---------------------------------
%% When anonymous login is enabled, check the password for permenant users
%% before allowing access
check_password(User, AuthzId, Server, Password) ->
check_password(User, AuthzId, Server, Password, undefined,
undefined).
check_password(User, _AuthzId, Server, _Password, _Digest,
_DigestGen) ->
check_password(User, _AuthzId, Server, _Password) ->
case
ejabberd_auth:is_user_exists_in_other_modules(?MODULE,
ejabberd_auth:user_exists_in_other_modules(?MODULE,
User, Server)
of
%% If user exists in other module, reject anonnymous authentication
@@ -174,69 +159,25 @@ login(User, Server) ->
end
end.
%% When anonymous login is enabled, check that the user is permanent before
%% changing its password
set_password(User, Server, _Password) ->
case anonymous_user_exist(User, Server) of
true -> ok;
false -> {error, not_allowed}
end.
get_users(Server, _) ->
[{U, S} || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Server)].
%% When anonymous login is enabled, check if permanent users are allowed on
%% the server:
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
count_users(Server, Opts) ->
length(get_users(Server, Opts)).
dirty_get_registered_users() -> [].
get_vh_registered_users(Server) ->
[{U, S}
|| {U, S, _R}
<- ejabberd_sm:get_vh_session_list(Server)].
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
%% Return password of permanent user or false for anonymous users
get_password(User, Server) ->
get_password(User, Server, <<"">>).
get_password(User, Server, DefaultValue) ->
case anonymous_user_exist(User, Server) or
login(User, Server)
of
%% We return the default value if the user is anonymous
true -> DefaultValue;
%% We return the permanent user password otherwise
false -> false
end.
get_password_s(User, Server) ->
case get_password(User, Server) of
false ->
<<"">>;
Password ->
Password
end.
is_user_exists(User, Server) ->
user_exists(User, Server) ->
anonymous_user_exist(User, Server).
remove_user(_User, _Server) -> {error, not_allowed}.
plain_password_required(_) ->
false.
remove_user(_User, _Server, _Password) -> not_allowed.
plain_password_required() -> false.
store_type() ->
plain.
store_type(_) ->
external.
-spec opt_type(allow_multiple_connection) -> fun((boolean()) -> boolean());
(anonymous_protocol) -> fun((sasl_anon | login_anon | both) ->
sasl_anon | login_anon | both);
(atom()) -> [atom()].
opt_type(allow_multiple_connections) ->
fun (V) when is_boolean(V) -> V end;
opt_type(anonymous_protocol) ->
+29 -256
View File
@@ -32,14 +32,8 @@
-behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, plain_password_required/0,
opt_type/1]).
try_register/3, user_exists/2, remove_user/2,
store_type/1, plain_password_required/1, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -48,283 +42,62 @@
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
Cmd = ejabberd_config:get_option(
{extauth_program, Host},
fun(V) ->
binary_to_list(iolist_to_binary(V))
end,
"extauth"),
extauth:start(Host, Cmd),
check_cache_last_options(Host),
ejabberd_auth_mnesia:start(Host).
Cmd = ejabberd_config:get_option({extauth_program, Host}, "extauth"),
extauth:start(Host, Cmd).
stop(Host) ->
extauth:stop(Host),
ejabberd_auth_mnesia:stop(Host).
extauth:stop(Host).
check_cache_last_options(Server) ->
case get_cache_option(Server) of
false -> no_cache;
{true, _CacheTime} ->
case get_mod_last_configured(Server) of
no_mod_last ->
?ERROR_MSG("In host ~p extauth is used, extauth_cache "
"is enabled but mod_last is not enabled.",
[Server]),
no_cache;
_ -> cache
end
end.
plain_password_required(_) -> true.
plain_password_required() -> true.
store_type() -> external.
store_type(_) -> external.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
case get_cache_option(Server) of
false ->
check_password_extauth(User, AuthzId, Server, Password);
{true, CacheTime} ->
check_password_cache(User, AuthzId, Server, Password,
CacheTime)
end
check_password_extauth(User, AuthzId, Server, Password)
end.
check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
true ->
set_password_mnesia(User, Server, Password), ok;
_ -> {error, unknown_problem}
true -> ok;
_ -> {error, db_failure}
end.
try_register(User, Server, Password) ->
case get_cache_option(Server) of
false -> try_register_extauth(User, Server, Password);
{true, _CacheTime} ->
try_register_external_cache(User, Server, Password)
end.
extauth:try_register(User, Server, Password).
dirty_get_registered_users() ->
ejabberd_auth_mnesia:dirty_get_registered_users().
get_vh_registered_users(Server) ->
ejabberd_auth_mnesia:get_vh_registered_users(Server).
get_vh_registered_users(Server, Data) ->
ejabberd_auth_mnesia:get_vh_registered_users(Server,
Data).
get_vh_registered_users_number(Server) ->
ejabberd_auth_mnesia:get_vh_registered_users_number(Server).
get_vh_registered_users_number(Server, Data) ->
ejabberd_auth_mnesia:get_vh_registered_users_number(Server,
Data).
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
get_password(User, Server) ->
case get_cache_option(Server) of
false -> false;
{true, CacheTime} ->
get_password_cache(User, Server, CacheTime)
end.
get_password_s(User, Server) ->
case get_password(User, Server) of
false -> <<"">>;
Other -> Other
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
try extauth:is_user_exists(User, Server) of
Res -> Res
user_exists(User, Server) ->
try extauth:user_exists(User, Server) of
Res -> Res
catch
_:Error -> {error, Error}
_:Error ->
?ERROR_MSG("external authentication program failure: ~p",
[Error]),
{error, db_failure}
end.
remove_user(User, Server) ->
case extauth:remove_user(User, Server) of
false -> false;
true ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_mnesia:remove_user(User, Server)
end
false -> {error, not_allowed};
true -> ok
end.
remove_user(User, Server, Password) ->
case extauth:remove_user(User, Server, Password) of
false -> false;
true ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_mnesia:remove_user(User, Server,
Password)
end
end.
%%%
%%% Extauth cache management
%%%
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
get_cache_option(Host) ->
case ejabberd_config:get_option(
{extauth_cache, Host},
fun(false) -> undefined;
(I) when is_integer(I), I >= 0 -> I
end) of
undefined -> false;
CacheTime -> {true, CacheTime}
end.
%% @spec (User, AuthzId, Server, Password) -> true | false
check_password_extauth(User, _AuthzId, Server, Password) ->
extauth:check_password(User, Server, Password) andalso
Password /= <<"">>.
%% @spec (User, Server, Password) -> true | false
try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password).
check_password_cache(User, AuthzId, Server, Password, 0) ->
check_password_external_cache(User, AuthzId, Server, Password);
check_password_cache(User, AuthzId, Server, Password,
CacheTime) ->
case get_last_access(User, Server) of
online ->
check_password_mnesia(User, AuthzId, Server, Password);
never ->
check_password_external_cache(User, AuthzId, Server, Password);
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
check_password_external_cache(User, AuthzId, Server, Password);
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
%% If no need to refresh, check password against Mnesia
true ->
case check_password_mnesia(User, AuthzId, Server, Password) of
%% If password valid in Mnesia, accept it
true -> true;
%% Else (password nonvalid in Mnesia), check in extauth and cache result
false ->
check_password_external_cache(User, AuthzId, Server, Password)
end;
%% Else (need to refresh), check in extauth and cache result
false ->
check_password_external_cache(User, AuthzId, Server, Password)
end
end.
get_password_mnesia(User, Server) ->
ejabberd_auth_mnesia:get_password(User, Server).
-spec get_password_cache(User::binary(), Server::binary(), CacheTime::integer()) -> Password::string() | false.
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
online -> get_password_mnesia(User, Server);
never -> false;
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled "
"but mod_last is not enabled in that "
"host",
[]),
false;
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
true -> get_password_mnesia(User, Server);
false -> false
end
end.
%% Check the password using extauth; if success then cache it
check_password_external_cache(User, AuthzId, Server, Password) ->
case check_password_extauth(User, AuthzId, Server, Password) of
true ->
set_password_mnesia(User, Server, Password), true;
false -> false
end.
%% Try to register using extauth; if success then cache it
try_register_external_cache(User, Server, Password) ->
case try_register_extauth(User, Server, Password) of
{atomic, ok} = R ->
set_password_mnesia(User, Server, Password), R;
_ -> {error, not_allowed}
end.
%% @spec (User, AuthzId, Server, Password) -> true | false
check_password_mnesia(User, AuthzId, Server, Password) ->
ejabberd_auth_mnesia:check_password(User, AuthzId, Server,
Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
set_password_mnesia(User, Server, Password) ->
%% @spec (TimeLast, CacheTime) -> true | false
%% TimeLast = online | never | integer()
%% CacheTime = integer() | false
ejabberd_auth_mnesia:set_password(User, Server,
Password).
is_fresh_enough(TimeStampLast, CacheTime) ->
Now = p1_time_compat:system_time(seconds),
TimeStampLast + CacheTime > Now.
%% Code copied from mod_configure.erl
%% Code copied from web/ejabberd_web_admin.erl
%% TODO: Update time format to XEP-0202: Entity Time
-spec(get_last_access(User::binary(), Server::binary()) -> (online | never | mod_last_required | integer())).
get_last_access(User, Server) ->
case ejabberd_sm:get_user_resources(User, Server) of
[] ->
case get_last_info(User, Server) of
mod_last_required -> mod_last_required;
not_found -> never;
{ok, Timestamp, _Status} -> Timestamp
end;
_ -> online
end.
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
get_last_info(User, Server) ->
case get_mod_last_enabled(Server) of
mod_last -> mod_last:get_last_info(User, Server);
no_mod_last -> mod_last_required
end.
%% @spec (Server) -> mod_last | no_mod_last
get_mod_last_enabled(Server) ->
case gen_mod:is_loaded(Server, mod_last) of
true -> mod_last;
false -> no_mod_last
end.
get_mod_last_configured(Server) ->
case is_configured(Server, mod_last) of
true -> mod_last;
false -> no_mod_last
end.
is_configured(Host, Module) ->
Os = ejabberd_config:get_option({modules, Host},
fun(M) when is_list(M) -> M end),
lists:keymember(Module, 1, Os).
-spec opt_type(extauth_cache) -> fun((false | non_neg_integer()) ->
false | non_neg_integer());
(extauth_program) -> fun((binary()) -> string());
(atom()) -> [atom()].
opt_type(extauth_cache) ->
fun (false) -> undefined;
?WARNING_MSG("option 'extauth_cache' is deprecated and has no effect, "
"use authentication or global cache configuration "
"options: auth_use_cache, auth_cache_life_time, "
"use_cache, cache_life_time, and so on", []),
fun (false) -> false;
(I) when is_integer(I), I >= 0 -> I
end;
opt_type(extauth_program) ->
+31 -98
View File
@@ -37,13 +37,9 @@
handle_cast/2, terminate/2, code_change/3]).
-export([start/1, stop/1, start_link/1, set_password/3,
check_password/4, check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, plain_password_required/0,
check_password/4, user_exists/2,
get_users/2, count_users/2,
store_type/1, plain_password_required/1,
opt_type/1]).
-include("ejabberd.hrl").
@@ -112,9 +108,9 @@ init(Host) ->
State#state.password, State#state.tls_options),
{ok, State}.
plain_password_required() -> true.
plain_password_required(_) -> true.
store_type() -> external.
store_type(_) -> external.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
@@ -129,60 +125,34 @@ check_password(User, AuthzId, Server, Password) ->
end
end.
check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of
false -> {error, user_not_found};
false -> {error, notfound};
DN ->
eldap_pool:modify_passwd(State#state.eldap_id, DN,
Password)
case eldap_pool:modify_passwd(State#state.eldap_id, DN,
Password) of
ok -> ok;
_Err -> {error, db_failure}
end
end.
%% @spec (User, Server, Password) -> {error, not_allowed}
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
lists:flatmap(fun (Server) ->
get_vh_registered_users(Server)
end,
Servers).
get_vh_registered_users(Server) ->
case catch get_vh_registered_users_ldap(Server) of
get_users(Server, []) ->
case catch get_users_ldap(Server) of
{'EXIT', _} -> [];
Result -> Result
end.
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(_User, _Server) -> false.
get_password_s(_User, _Server) -> <<"">>.
count_users(Server, Opts) ->
length(get_users(Server, Opts)).
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
case catch is_user_exists_ldap(User, Server) of
{'EXIT', Error} -> {error, Error};
user_exists(User, Server) ->
case catch user_exists_ldap(User, Server) of
{'EXIT', _Error} -> {error, db_failure};
Result -> Result
end.
remove_user(_User, _Server) -> {error, not_allowed}.
remove_user(_User, _Server, _Password) -> not_allowed.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
@@ -199,7 +169,7 @@ check_password_ldap(User, Server, Password) ->
end
end.
get_vh_registered_users_ldap(Server) ->
get_users_ldap(Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
UIDs = State#state.uids,
Eldap_ID = State#state.eldap_id,
@@ -248,7 +218,7 @@ get_vh_registered_users_ldap(Server) ->
_ -> []
end.
is_user_exists_ldap(User, Server) ->
user_exists_ldap(User, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of
false -> false;
@@ -364,24 +334,11 @@ parse_options(Host) ->
Eldap_ID = misc:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
Bind_Eldap_ID = misc:atom_to_binary(
gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
UIDsTemp = gen_mod:get_opt(
{ldap_uids, Host}, [],
fun(Us) ->
lists:map(
fun({U, P}) ->
{iolist_to_binary(U),
iolist_to_binary(P)};
({U}) ->
{iolist_to_binary(U)};
(U) ->
{iolist_to_binary(U)}
end, lists:flatten(Us))
end, [{<<"uid">>, <<"%u">>}]),
UIDsTemp = ejabberd_config:get_option(
{ldap_uids, Host}, [{<<"uid">>, <<"%u">>}]),
UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
SubFilter = eldap_utils:generate_subfilter(UIDs),
UserFilter = case gen_mod:get_opt(
{ldap_filter, Host}, [],
fun check_filter/1, <<"">>) of
UserFilter = case ejabberd_config:get_option({ldap_filter, Host}, <<"">>) of
<<"">> ->
SubFilter;
F ->
@@ -390,20 +347,8 @@ parse_options(Host) ->
SearchFilter = eldap_filter:do_sub(UserFilter,
[{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} =
gen_mod:get_opt({ldap_dn_filter, Host}, [],
fun([{DNF, DNFA}]) ->
NewDNFA = case DNFA of
undefined ->
[];
_ ->
[iolist_to_binary(A)
|| A <- DNFA]
end,
NewDNF = check_filter(DNF),
{NewDNF, NewDNFA}
end, {undefined, []}),
LocalFilter = gen_mod:get_opt(
{ldap_local_filter, Host}, [], fun(V) -> V end),
ejabberd_config:get_option({ldap_dn_filter, Host}, {undefined, []}),
LocalFilter = ejabberd_config:get_option({ldap_local_filter, Host}),
#state{host = Host, eldap_id = Eldap_ID,
bind_eldap_id = Bind_Eldap_ID,
servers = Cfg#eldap_config.servers,
@@ -418,31 +363,19 @@ parse_options(Host) ->
sfilter = SearchFilter, lfilter = LocalFilter,
dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
check_filter(F) ->
NewF = iolist_to_binary(F),
{ok, _} = eldap_filter:parse(NewF),
NewF.
-spec opt_type(ldap_dn_filter) -> fun(([{binary(), binary()}]) ->
[{binary(), binary()}]);
(ldap_local_filter) -> fun((any()) -> any());
(atom()) -> [atom()].
opt_type(ldap_dn_filter) ->
fun ([{DNF, DNFA}]) ->
NewDNFA = case DNFA of
undefined -> [];
_ -> [iolist_to_binary(A) || A <- DNFA]
end,
NewDNF = check_filter(DNF),
NewDNF = eldap_utils:check_filter(DNF),
{NewDNF, NewDNFA}
end;
opt_type(ldap_filter) -> fun check_filter/1;
opt_type(ldap_local_filter) -> fun (V) -> V end;
opt_type(ldap_uids) ->
fun (Us) ->
lists:map(fun ({U, P}) ->
{iolist_to_binary(U), iolist_to_binary(P)};
({U}) -> {iolist_to_binary(U)};
(U) -> {iolist_to_binary(U)}
end,
lists:flatten(Us))
end;
opt_type(_) ->
[ldap_dn_filter, ldap_filter, ldap_local_filter,
ldap_uids].
[ldap_dn_filter, ldap_local_filter].
+183 -413
View File
@@ -27,21 +27,16 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2, init_db/0,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/2,
plain_password_required/0, opt_type/1]).
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, init_db/0,
count_users/2, get_password/2,
remove_user/2, store_type/1, export/1, import/2,
plain_password_required/1, use_cache/1]).
-export([need_transform/1, transform/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -53,16 +48,12 @@
-record(reg_users_counter, {vhost = <<"">> :: binary(),
count = 0 :: integer() | '$1'}).
-define(SALT_LENGTH, 16).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
init_db(),
update_table(),
update_reg_users_counter_table(Host),
maybe_alert_password_scrammed_without_option(),
ok.
stop(_Host) ->
@@ -70,14 +61,14 @@ stop(_Host) ->
init_db() ->
ejabberd_mnesia:create(?MODULE, passwd,
[{disc_copies, [node()]},
[{disc_only_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
ejabberd_mnesia:create(?MODULE, reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]).
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Set = get_users(Server, []),
Size = length(Set),
LServer = jid:nameprep(Server),
F = fun () ->
@@ -86,419 +77,201 @@ update_reg_users_counter_table(Server) ->
end,
mnesia:sync_dirty(F).
plain_password_required() ->
is_scrammed().
store_type() ->
case is_scrammed() of
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
use_cache(Host) ->
case mnesia:table_info(passwd, storage_type) of
disc_only_copies ->
ejabberd_config:get_option(
{auth_use_cache, Host},
ejabberd_config:use_cache(Host));
_ ->
false
end.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Password}] when is_binary(Password) ->
Password /= <<"">>;
[#passwd{password = Scram}] when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ -> false
end
end.
plain_password_required(Server) ->
store_type(Server) == scram.
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[#passwd{password = Passwd}] when is_binary(Passwd) ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
[#passwd{password = Scram}] when is_record(Scram, scram) ->
Passwd = misc:decode_base64(Scram#scram.storedkey),
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
end
end.
store_type(Server) ->
ejabberd_auth:password_format(Server).
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
LPassword == error ->
{error, invalid_password};
true ->
F = fun () ->
Password2 = case is_scrammed() and is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
mnesia:write(#passwd{us = US, password = Password2})
end,
{atomic, ok} = mnesia:transaction(F),
ok
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {error, not_allowed} | {error, Reason}
try_register(User, Server, PasswordList) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Password = if is_list(PasswordList); is_binary(PasswordList) ->
iolist_to_binary(PasswordList);
true -> PasswordList
end,
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LPassword == error) and not is_record(Password, scram) ->
{error, invalid_password};
true ->
F = fun () ->
case mnesia:read({passwd, US}) of
[] ->
Password2 = case is_scrammed() and
is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
mnesia:write(#passwd{us = US,
password = Password2}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, 1),
ok;
[_E] -> exists
end
end,
mnesia:transaction(F)
end.
%% Get all registered users in Mnesia
dirty_get_registered_users() ->
mnesia:dirty_all_keys(passwd).
get_vh_registered_users(Server) ->
LServer = jid:nameprep(Server),
mnesia:dirty_select(passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}], ['$1']}]).
get_vh_registered_users(Server,
[{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server,
[{limit, End - Start + 1}, {offset, Start}]);
get_vh_registered_users(Server,
[{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
case get_vh_registered_users(Server) of
[] -> [];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, [{prefix, Prefix}])
when is_binary(Prefix) ->
Set = [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)],
lists:keysort(1, Set);
get_vh_registered_users(Server,
[{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and
is_integer(End) ->
get_vh_registered_users(Server,
[{prefix, Prefix}, {limit, End - Start + 1},
{offset, Start}]);
get_vh_registered_users(Server,
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) ->
case [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)]
of
[] -> [];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
LServer = jid:nameprep(Server),
Query = mnesia:dirty_select(reg_users_counter,
[{#reg_users_counter{vhost = LServer,
count = '$1'},
[], ['$1']}]),
case Query of
[Count] -> Count;
_ -> 0
end.
get_vh_registered_users_number(Server,
[{prefix, Prefix}])
when is_binary(Prefix) ->
Set = [{U, S}
|| {U, S} <- get_vh_registered_users(Server),
str:prefix(Prefix, U)],
length(Set);
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}]
when is_binary(Password) ->
Password;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
{misc:decode_base64(Scram#scram.storedkey),
misc:decode_base64(Scram#scram.serverkey),
misc:decode_base64(Scram#scram.salt),
Scram#scram.iterationcount};
_ -> false
end.
get_password_s(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read(passwd, US) of
[#passwd{password = Password}]
when is_binary(Password) ->
Password;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
<<"">>;
_ -> <<"">>
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
case catch mnesia:dirty_read({passwd, US}) of
[] -> false;
[_] -> true;
Other -> {error, Other}
end.
%% @spec (User, Server) -> ok
%% @doc Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
US = {User, Server},
F = fun () ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, LServer,
-1)
mnesia:write(#passwd{us = US, password = Password})
end,
mnesia:transaction(F),
ok.
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
US = {LUser, LServer},
try_register(User, Server, Password) ->
US = {User, Server},
F = fun () ->
case mnesia:read({passwd, US}) of
[#passwd{password = Password}]
when is_binary(Password) ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, LServer,
-1),
ok;
[#passwd{password = Scram}]
when is_record(Scram, scram) ->
case is_password_scram_valid(Password, Scram) of
true ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
LServer, -1),
ok;
false -> not_allowed
end;
_ -> not_exists
[] ->
mnesia:write(#passwd{us = US, password = Password}),
mnesia:dirty_update_counter(reg_users_counter, Server, 1),
ok;
[_] ->
{error, exists}
end
end,
case mnesia:transaction(F) of
{atomic, ok} -> ok;
{atomic, Res} -> Res;
_ -> bad_request
{atomic, Res} ->
Res;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, db_failure}
end.
update_table() ->
Fields = record_info(fields, passwd),
case mnesia:table_info(passwd, attributes) of
Fields ->
convert_to_binary(Fields),
maybe_scram_passwords(),
ok;
_ ->
?INFO_MSG("Recreating passwd table", []),
mnesia:transform_table(passwd, ignore, Fields)
end.
get_users(Server, []) ->
mnesia:dirty_select(passwd,
[{#passwd{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, Server}], ['$1']}]);
get_users(Server, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
get_users(Server, [{limit, End - Start + 1}, {offset, Start}]);
get_users(Server, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
case get_users(Server, []) of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
lists:keysort(1, Set);
get_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and is_integer(End) ->
get_users(Server, [{prefix, Prefix}, {limit, End - Start + 1},
{offset, Start}]);
get_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) ->
case [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)] of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_users(Server, _) ->
get_users(Server, []).
convert_to_binary(Fields) ->
ejabberd_config:convert_table_to_binary(
passwd, Fields, set,
fun(#passwd{us = {U, _}}) -> U end,
fun(#passwd{us = {U, S}, password = Pass} = R) ->
NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
NewPass = case Pass of
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt} ->
Pass#scram{
storedkey = iolist_to_binary(StoredKey),
serverkey = iolist_to_binary(ServerKey),
salt = iolist_to_binary(Salt)};
_ ->
iolist_to_binary(Pass)
end,
R#passwd{us = NewUS, password = NewPass}
end).
count_users(Server, []) ->
case mnesia:dirty_select(
reg_users_counter,
[{#reg_users_counter{vhost = Server, count = '$1'},
[], ['$1']}]) of
[Count] -> Count;
_ -> 0
end;
count_users(Server, [{prefix, Prefix}]) when is_binary(Prefix) ->
Set = [{U, S} || {U, S} <- get_users(Server, []), str:prefix(Prefix, U)],
length(Set);
count_users(Server, _) ->
count_users(Server, []).
%%%
%%% SCRAM
%%%
%% The passwords are stored scrammed in the table either if the option says so,
%% or if at least the first password is scrammed.
is_scrammed() ->
OptionScram = is_option_scram(),
FirstElement = mnesia:dirty_read(passwd,
mnesia:dirty_first(passwd)),
case {OptionScram, FirstElement} of
{true, _} -> true;
{false, [#passwd{password = Scram}]}
when is_record(Scram, scram) ->
true;
_ -> false
end.
is_option_scram() ->
scram ==
ejabberd_config:get_option({auth_password_format, ?MYNAME},
fun(V) -> V end).
maybe_alert_password_scrammed_without_option() ->
case is_scrammed() andalso not is_option_scram() of
true ->
?ERROR_MSG("Some passwords were stored in the database "
"as SCRAM, but 'auth_password_format' "
"is not configured 'scram'. The option "
"will now be considered to be 'scram'.",
[]);
false -> ok
end.
maybe_scram_passwords() ->
case is_scrammed() of
true -> scram_passwords();
false -> ok
end.
scram_passwords() ->
?INFO_MSG("Converting the stored passwords into "
"SCRAM bits",
[]),
Fun = fun (#passwd{us = {U, S}, password = Password} = P)
when is_binary(Password) ->
case jid:resourceprep(Password) of
error ->
?ERROR_MSG(
"SASLprep failed for "
"password of user ~s@~s",
[U, S]),
P;
_ ->
Scram = password_to_scram(Password),
P#passwd{password = Scram}
end;
(P) ->
P
end,
Fields = record_info(fields, passwd),
mnesia:transform_table(passwd, Fun, Fields).
password_to_scram(Password) ->
password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = misc:encode_base64(StoredKey),
serverkey = misc:encode_base64(ServerKey),
salt = misc:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid(Password, Scram) ->
case jid:resourceprep(Password) of
error ->
false;
get_password(User, Server) ->
case mnesia:dirty_read(passwd, {User, Server}) of
[#passwd{password = Password}] ->
{ok, Password};
_ ->
IterationCount = Scram#scram.iterationcount,
Salt = misc:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
misc:decode_base64(Scram#scram.storedkey) == StoredKey
error
end.
remove_user(User, Server) ->
US = {User, Server},
F = fun () ->
mnesia:delete({passwd, US}),
mnesia:dirty_update_counter(reg_users_counter, Server, -1),
ok
end,
case mnesia:transaction(F) of
{atomic, ok} ->
ok;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{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
scram ->
?INFO_MSG("Passwords in Mnesia table 'passwd' "
"will be SCRAM'ed", []),
true;
plain ->
false
end;
is_record(Pass, scram) ->
case store_type(S) of
scram ->
false;
plain ->
?WARNING_MSG("Some passwords were stored in the database "
"as SCRAM, but 'auth_password_format' "
"is not configured as 'scram': some "
"authentication mechanisms such as DIGEST-MD5 "
"would *fail*", []),
false
end;
is_list(U) orelse is_list(S) orelse is_list(Pass) ->
?INFO_MSG("Mnesia table 'passwd' will be converted to binary", []),
true
end.
transform(#passwd{us = {U, S}, password = Pass} = R)
when is_list(U) orelse is_list(S) orelse is_list(Pass) ->
NewUS = {iolist_to_binary(U), iolist_to_binary(S)},
NewPass = case Pass of
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt} ->
Pass#scram{
storedkey = iolist_to_binary(StoredKey),
serverkey = iolist_to_binary(ServerKey),
salt = iolist_to_binary(Salt)};
_ ->
iolist_to_binary(Pass)
end,
transform(R#passwd{us = NewUS, password = NewPass});
transform(#passwd{us = {U, S}, password = Password} = P)
when is_binary(Password) ->
case store_type(S) of
scram ->
case jid:resourceprep(Password) of
error ->
?ERROR_MSG("SASLprep failed for password of user ~s@~s",
[U, S]),
P;
_ ->
Scram = ejabberd_auth:password_to_scram(Password),
P#passwd{password = Scram}
end;
plain ->
P
end;
transform(#passwd{password = Password} = P)
when is_record(Password, scram) ->
P.
export(_Server) ->
[{passwd,
fun(Host, #passwd{us = {LUser, LServer}, password = Password})
@@ -525,6 +298,3 @@ export(_Server) ->
import(LServer, [LUser, Password, _TimeStamp]) ->
mnesia:dirty_write(
#passwd{us = {LUser, LServer}, password = Password}).
opt_type(auth_password_format) -> fun (V) -> V end;
opt_type(_) -> [auth_password_format].
+12 -52
View File
@@ -30,14 +30,8 @@
-behaviour(ejabberd_auth).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, plain_password_required/0,
-export([start/1, stop/1, check_password/4,
user_exists/2, store_type/1, plain_password_required/1,
opt_type/1]).
start(_Host) ->
@@ -46,13 +40,6 @@ start(_Host) ->
stop(_Host) ->
ok.
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
check_password(User, AuthzId, Server, Password).
check_password(User, AuthzId, Host, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
@@ -70,26 +57,7 @@ check_password(User, AuthzId, Host, Password) ->
end
end.
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
dirty_get_registered_users() -> [].
get_vh_registered_users(_Host) -> [].
get_vh_registered_users(_Host, _) -> [].
get_vh_registered_users_number(_Host) -> 0.
get_vh_registered_users_number(_Host, _) -> 0.
get_password(_User, _Server) -> false.
get_password_s(_User, _Server) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error}
%% TODO: Improve this function to return an error instead of 'false' when connection to PAM failed
is_user_exists(User, Host) ->
user_exists(User, Host) ->
Service = get_pam_service(Host),
UserInfo = case get_pam_userinfotype(Host) of
username -> User;
@@ -97,34 +65,26 @@ is_user_exists(User, Host) ->
end,
case catch epam:acct_mgmt(Service, UserInfo) of
true -> true;
_ -> false
false -> false;
_Err -> {error, db_failure}
end.
remove_user(_User, _Server) -> {error, not_allowed}.
plain_password_required(_) -> true.
remove_user(_User, _Server, _Password) -> not_allowed.
plain_password_required() -> true.
store_type() -> external.
store_type(_) -> external.
%%====================================================================
%% Internal functions
%%====================================================================
get_pam_service(Host) ->
ejabberd_config:get_option(
{pam_service, Host},
fun iolist_to_binary/1,
<<"ejabberd">>).
ejabberd_config:get_option({pam_service, Host}, <<"ejabberd">>).
get_pam_userinfotype(Host) ->
ejabberd_config:get_option(
{pam_userinfotype, Host},
fun(username) -> username;
(jid) -> jid
end,
username).
ejabberd_config:get_option({pam_userinfotype, Host}, username).
-spec opt_type(pam_service) -> fun((binary()) -> binary());
(pam_userinfotype) -> fun((username | jid) -> username | jid);
(atom()) -> [atom()].
opt_type(pam_service) -> fun iolist_to_binary/1;
opt_type(pam_userinfotype) ->
fun (username) -> username;
+33 -238
View File
@@ -27,22 +27,15 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
%% External exports
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, export/1, import/2,
plain_password_required/0, opt_type/1]).
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2,
get_password/2, remove_user/2, store_type/1, export/1, import/2,
plain_password_required/1]).
-export([passwd_schema/0]).
-include("ejabberd.hrl").
@@ -51,260 +44,65 @@
-record(passwd, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
password = <<"">> :: binary() | scram() | '_'}).
-define(SALT_LENGTH, 16).
start(_Host) ->
ok.
stop(_Host) ->
ok.
plain_password_required() ->
case is_scrammed() of
false -> false;
true -> true
end.
plain_password_required(Server) ->
store_type(Server) == scram.
store_type() ->
case is_scrammed() of
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
end.
store_type(Server) ->
ejabberd_auth:password_format(Server).
passwd_schema() ->
{record_info(fields, passwd), #passwd{}}.
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}} when is_binary(Password) ->
Password /= <<"">>;
{ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
is_password_scram_valid(Password, Scram);
_ ->
false
end
end.
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
Passwd = misc:decode_base64(Scram#scram.storedkey),
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
_ -> false
end
end.
set_password(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
LPassword == error ->
{error, invalid_password};
true ->
Password2 = case is_scrammed() and is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
ok = ejabberd_riak:put(#passwd{us = US, password = Password2},
passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}])
ejabberd_riak:put(#passwd{us = {User, Server}, password = Password},
passwd_schema(),
[{'2i', [{<<"host">>, Server}]}]).
try_register(User, Server, Password) ->
US = {User, Server},
case ejabberd_riak:get(passwd, passwd_schema(), US) of
{error, notfound} ->
ejabberd_riak:put(#passwd{us = US, password = Password},
passwd_schema(),
[{'2i', [{<<"host">>, Server}]}]);
{ok, _} ->
{error, exists};
{error, _} = Err ->
Err
end.
try_register(User, Server, PasswordList) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Password = if is_list(PasswordList); is_binary(PasswordList) ->
iolist_to_binary(PasswordList);
true -> PasswordList
end,
LPassword = jid:resourceprep(Password),
US = {LUser, LServer},
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
LPassword == error and not is_record(Password, scram) ->
{error, invalid_password};
true ->
case ejabberd_riak:get(passwd, passwd_schema(), US) of
{error, notfound} ->
Password2 = case is_scrammed() and
is_binary(Password)
of
true -> password_to_scram(Password);
false -> Password
end,
{atomic, ejabberd_riak:put(
#passwd{us = US,
password = Password2},
passwd_schema(),
[{'2i', [{<<"host">>, LServer}]}])};
{ok, _} ->
exists;
Err ->
{atomic, Err}
end
end.
dirty_get_registered_users() ->
lists:flatmap(
fun(Server) ->
get_vh_registered_users(Server)
end, ejabberd_config:get_vh_by_auth_method(riak)).
get_vh_registered_users(Server) ->
LServer = jid:nameprep(Server),
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, LServer) of
get_users(Server, _) ->
case ejabberd_riak:get_keys_by_index(passwd, <<"host">>, Server) of
{ok, Users} ->
Users;
_ ->
[]
end.
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
LServer = jid:nameprep(Server),
case ejabberd_riak:count_by_index(passwd, <<"host">>, LServer) of
count_users(Server, _) ->
case ejabberd_riak:count_by_index(passwd, <<"host">>, Server) of
{ok, N} ->
N;
_ ->
0
end.
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
get_password(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
Password;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
{misc:decode_base64(Scram#scram.storedkey),
misc:decode_base64(Scram#scram.serverkey),
misc:decode_base64(Scram#scram.salt),
Scram#scram.iterationcount};
_ -> false
end.
get_password_s(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
Password;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
<<"">>;
_ -> <<"">>
end.
is_user_exists(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{error, notfound} -> false;
{ok, _} -> true;
Err -> Err
case ejabberd_riak:get(passwd, passwd_schema(), {User, Server}) of
{ok, Password} ->
{ok, Password};
{error, _} ->
error
end.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok.
remove_user(User, Server, Password) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
{ok, #passwd{password = Password}}
when is_binary(Password) ->
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok;
{ok, #passwd{password = Scram}}
when is_record(Scram, scram) ->
case is_password_scram_valid(Password, Scram) of
true ->
ejabberd_riak:delete(passwd, {LUser, LServer}),
ok;
false -> not_allowed
end;
_ -> not_exists
end.
%%%
%%% SCRAM
%%%
is_scrammed() ->
scram ==
ejabberd_config:get_option({auth_password_format, ?MYNAME},
fun(V) -> V end).
password_to_scram(Password) ->
password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = misc:encode_base64(StoredKey),
serverkey = misc:encode_base64(ServerKey),
salt = misc:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid(Password, Scram) ->
case jid:resourceprep(Password) of
error ->
false;
_ ->
IterationCount = Scram#scram.iterationcount,
Salt = misc:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
misc:decode_base64(Scram#scram.storedkey) == StoredKey
end.
ejabberd_riak:delete(passwd, {User, Server}).
export(_Server) ->
[{passwd,
@@ -320,6 +118,3 @@ export(_Server) ->
import(LServer, [LUser, Password, _TimeStamp]) ->
Passwd = #passwd{us = {LUser, LServer}, password = Password},
ejabberd_riak:put(Passwd, passwd_schema(), [{'2i', [{<<"host">>, LServer}]}]).
opt_type(auth_password_format) -> fun (V) -> V end;
opt_type(_) -> [auth_password_format].
+172 -391
View File
@@ -27,20 +27,14 @@
-compile([{parse_transform, ejabberd_sql_pt}]).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(ejabberd_auth).
-behaviour(ejabberd_config).
-export([start/1, stop/1, set_password/3, check_password/4,
check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
remove_user/3, store_type/0, plain_password_required/0,
-export([start/1, stop/1, set_password/3, try_register/3,
get_users/2, count_users/2, get_password/2,
remove_user/2, store_type/1, plain_password_required/1,
convert_to_scram/1, opt_type/1]).
-include("ejabberd.hrl").
@@ -56,399 +50,84 @@ start(_Host) -> ok.
stop(_Host) -> ok.
plain_password_required() ->
case is_scrammed() of
false -> false;
true -> true
end.
plain_password_required(Server) ->
store_type(Server) == scram.
store_type() ->
case is_scrammed() of
false -> plain; %% allows: PLAIN DIGEST-MD5 SCRAM
true -> scram %% allows: PLAIN SCRAM
end.
store_type(Server) ->
ejabberd_auth:password_format(Server).
%% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error}
check_password(User, AuthzId, Server, Password) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
case is_scrammed() of
true ->
try sql_queries:get_password_scram(LServer, LUser) of
{selected,
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
Scram =
#scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
iterationcount = IterationCount},
is_password_scram_valid_stored(Password, Scram, LUser, LServer);
{selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end;
false ->
try sql_queries:get_password(LServer, LUser) of
{selected, [{Password}]} ->
Password /= <<"">>;
{selected, [{_Password2}]} ->
false; %% Password is not correct
{selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end
end
end
end.
%% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
if AuthzId /= <<>> andalso AuthzId /= User ->
false;
true ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
case is_scrammed() of
false ->
try sql_queries:get_password(LServer, LUser) of
%% Account exists, check if password is valid
{selected, [{Passwd}]} ->
DigRes = if Digest /= <<"">> ->
Digest == DigestGen(Passwd);
true -> false
end,
if DigRes -> true;
true -> (Passwd == Password) and (Password /= <<"">>)
end;
{selected, []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
catch
_:_ ->
false %% Typical error is database not accessible
end;
true ->
false
end
end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
set_password(User, Server, Password) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
LPassword = jid:resourceprep(Password),
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
LPassword == error ->
{error, invalid_password};
true ->
case is_scrammed() of
true ->
Scram = password_to_scram(Password),
case catch sql_queries:set_password_scram_t(
LServer,
LUser,
Scram#scram.storedkey,
Scram#scram.serverkey,
Scram#scram.salt,
Scram#scram.iterationcount
)
of
{atomic, ok} -> ok;
Other -> {error, Other}
end;
false ->
case catch sql_queries:set_password_t(LServer,
LUser, Password)
of
{atomic, ok} -> ok;
Other -> {error, Other}
end
end
F = fun() ->
if is_record(Password, scram) ->
set_password_scram_t(
User,
Password#scram.storedkey, Password#scram.serverkey,
Password#scram.salt, Password#scram.iterationcount);
true ->
set_password_t(User, Password)
end
end,
case ejabberd_sql:sql_transaction(Server, F) of
{atomic, _} ->
ok;
{aborted, Reason} ->
?ERROR_MSG("failed to write to SQL table: ~p", [Reason]),
{error, db_failure}
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid}
try_register(User, Server, Password) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
LPassword = jid:resourceprep(Password),
if (LUser == error) or (LServer == error) ->
{error, invalid_jid};
(LUser == <<>>) or (LServer == <<>>) ->
{error, invalid_jid};
LPassword == error and not is_record(Password, scram) ->
{error, invalid_password};
true ->
case is_scrammed() of
true ->
Scram = case is_record(Password, scram) of
true -> Password;
false -> password_to_scram(Password)
end,
case catch sql_queries:add_user_scram(
LServer,
LUser,
Scram#scram.storedkey,
Scram#scram.serverkey,
Scram#scram.salt,
Scram#scram.iterationcount
) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end;
false ->
case catch sql_queries:add_user(LServer, LUser,
Password) of
{updated, 1} -> {atomic, ok};
_ -> {atomic, exists}
end
end
Res = if is_record(Password, scram) ->
add_user_scram(
Server, User,
Password#scram.storedkey, Password#scram.serverkey,
Password#scram.salt, Password#scram.iterationcount);
true ->
add_user(Server, User, Password)
end,
case Res of
{updated, 1} -> ok;
_ -> {error, exists}
end.
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(sql),
lists:flatmap(fun (Server) ->
get_vh_registered_users(Server)
end,
Servers).
get_vh_registered_users(Server) ->
case jid:nameprep(Server) of
error -> [];
<<>> -> [];
LServer ->
case catch sql_queries:list_users(LServer) of
{selected, Res} ->
[{U, LServer} || {U} <- Res];
_ -> []
end
get_users(Server, Opts) ->
case list_users(Server, Opts) of
{selected, Res} ->
[{U, Server} || {U} <- Res];
_ -> []
end.
get_vh_registered_users(Server, Opts) ->
case jid:nameprep(Server) of
error -> [];
<<>> -> [];
LServer ->
case catch sql_queries:list_users(LServer, Opts) of
{selected, Res} ->
[{U, LServer} || {U} <- Res];
_ -> []
end
end.
get_vh_registered_users_number(Server) ->
case jid:nameprep(Server) of
error -> 0;
<<>> -> 0;
LServer ->
case catch sql_queries:users_number(LServer) of
{selected, [{Res}]} ->
Res;
_ -> 0
end
end.
get_vh_registered_users_number(Server, Opts) ->
case jid:nameprep(Server) of
error -> 0;
<<>> -> 0;
LServer ->
case catch sql_queries:users_number(LServer, Opts) of
{selected, [{Res}]} ->
Res;
_Other -> 0
end
count_users(Server, Opts) ->
case users_number(Server, Opts) of
{selected, [{Res}]} ->
Res;
_Other -> 0
end.
get_password(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
case is_scrammed() of
true ->
case catch sql_queries:get_password_scram(
LServer, LUser) of
{selected,
[{StoredKey, ServerKey, Salt, IterationCount}]} ->
{misc:decode_base64(StoredKey),
misc:decode_base64(ServerKey),
misc:decode_base64(Salt),
IterationCount};
_ -> false
end;
false ->
case catch sql_queries:get_password(LServer, LUser)
of
{selected, [{Password}]} -> Password;
_ -> false
end
end
case get_password_scram(Server, User) of
{selected, [{Password, <<>>, <<>>, 0}]} ->
{ok, Password};
{selected, [{StoredKey, ServerKey, Salt, IterationCount}]} ->
{ok, #scram{storedkey = StoredKey,
serverkey = ServerKey,
salt = Salt,
iterationcount = IterationCount}};
{selected, []} ->
error;
Err ->
?ERROR_MSG("Failed to read password for user ~s@~s: ~p",
[User, Server, Err]),
error
end.
get_password_s(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
<<"">>;
(LUser == <<>>) or (LServer == <<>>) ->
<<"">>;
true ->
case is_scrammed() of
false ->
case catch sql_queries:get_password(LServer, LUser) of
{selected, [{Password}]} -> Password;
_ -> <<"">>
end;
true -> <<"">>
end
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
false;
(LUser == <<>>) or (LServer == <<>>) ->
false;
true ->
try sql_queries:get_password(LServer, LUser) of
{selected, [{_Password}]} ->
true; %% Account exists
{selected, []} ->
false; %% Account does not exist
{error, Error} -> {error, Error}
catch
_:B -> {error, B}
end
end.
%% @spec (User, Server) -> ok | error
%% @doc Remove user.
%% Note: it may return ok even if there was some problem removing the user.
remove_user(User, Server) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
error;
(LUser == <<>>) or (LServer == <<>>) ->
error;
true ->
catch sql_queries:del_user(LServer, LUser),
ok
end.
%% @spec (User, Server, Password) -> ok | error | not_exists | not_allowed
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LServer = jid:nameprep(Server),
LUser = jid:nodeprep(User),
if (LUser == error) or (LServer == error) ->
error;
(LUser == <<>>) or (LServer == <<>>) ->
error;
true ->
case is_scrammed() of
true ->
case check_password(User, <<"">>, Server, Password) of
true ->
remove_user(User, Server),
ok;
false -> not_allowed
end;
false ->
F = fun () ->
Result = sql_queries:del_user_return_password(
LServer, LUser, Password),
case Result of
{selected, [{Password}]} -> ok;
{selected, []} -> not_exists;
_ -> not_allowed
end
end,
{atomic, Result} = sql_queries:sql_transaction(
LServer, F),
Result
end
end.
%%%
%%% SCRAM
%%%
is_scrammed() ->
scram ==
ejabberd_config:get_option({auth_password_format, ?MYNAME},
fun(V) -> V end).
password_to_scram(Password) ->
password_to_scram(Password,
?SCRAM_DEFAULT_ITERATION_COUNT).
password_to_scram(Password, IterationCount) ->
Salt = randoms:bytes(?SALT_LENGTH),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
ServerKey = scram:server_key(SaltedPassword),
#scram{storedkey = misc:encode_base64(StoredKey),
serverkey = misc:encode_base64(ServerKey),
salt = misc:encode_base64(Salt),
iterationcount = IterationCount}.
is_password_scram_valid_stored(Pass, {scram,Pass,<<>>,<<>>,0}, LUser, LServer) ->
?INFO_MSG("Apparently, SQL auth method and scram password formatting are "
"enabled, but the password of user '~s' in the 'users' table is not "
"scrammed. You may want to execute this command: "
"ejabberdctl convert_to_scram ~s", [LUser, LServer]),
false;
is_password_scram_valid_stored(Password, Scram, _, _) ->
is_password_scram_valid(Password, Scram).
is_password_scram_valid(Password, Scram) ->
case jid:resourceprep(Password) of
error ->
false;
_ ->
IterationCount = Scram#scram.iterationcount,
Salt = misc:decode_base64(Scram#scram.salt),
SaltedPassword = scram:salted_password(Password, Salt,
IterationCount),
StoredKey =
scram:stored_key(scram:client_key(SaltedPassword)),
misc:decode_base64(Scram#scram.storedkey) == StoredKey
case del_user(Server, User) of
{updated, _} ->
ok;
Err ->
?ERROR_MSG("failed to delete user ~s@~s: ~p",
[User, Server, Err]),
{error, db_failure}
end.
-define(BATCH_SIZE, 1000).
@@ -463,6 +142,105 @@ set_password_scram_t(LUser,
"salt=%(Salt)s",
"iterationcount=%(IterationCount)d"]).
set_password_t(LUser, Password) ->
?SQL_UPSERT_T(
"users",
["!username=%(LUser)s",
"password=%(Password)s"]).
get_password_scram(LServer, LUser) ->
ejabberd_sql:sql_query(
LServer,
?SQL("select @(password)s, @(serverkey)s, @(salt)s, @(iterationcount)d"
" from users"
" where username=%(LUser)s")).
add_user_scram(LServer, LUser,
StoredKey, ServerKey, Salt, IterationCount) ->
ejabberd_sql:sql_query(
LServer,
?SQL("insert into users(username, password, serverkey, salt, "
"iterationcount) "
"values (%(LUser)s, %(StoredKey)s, %(ServerKey)s,"
" %(Salt)s, %(IterationCount)d)")).
add_user(LServer, LUser, Password) ->
ejabberd_sql:sql_query(
LServer,
?SQL("insert into users(username, password) "
"values (%(LUser)s, %(Password)s)")).
del_user(LServer, LUser) ->
ejabberd_sql:sql_query(
LServer,
?SQL("delete from users where username=%(LUser)s")).
list_users(LServer, []) ->
ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from users"));
list_users(LServer, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
list_users(LServer,
[{limit, End - Start + 1}, {offset, Start - 1}]);
list_users(LServer,
[{prefix, Prefix}, {from, Start}, {to, End}])
when is_binary(Prefix) and is_integer(Start) and
is_integer(End) ->
list_users(LServer,
[{prefix, Prefix}, {limit, End - Start + 1},
{offset, Start - 1}]);
list_users(LServer, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from users "
"order by username "
"limit %(Limit)d offset %(Offset)d"));
list_users(LServer,
[{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_binary(Prefix) and is_integer(Limit) and
is_integer(Offset) ->
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query(
LServer,
?SQL("select @(username)s from users "
"where username like %(SPrefix2)s escape '^' "
"order by username "
"limit %(Limit)d offset %(Offset)d")).
users_number(LServer) ->
ejabberd_sql:sql_query(
LServer,
fun(pgsql, _) ->
case
ejabberd_config:get_option(
{pgsql_users_number_estimate, LServer}, false) of
true ->
ejabberd_sql:sql_query_t(
?SQL("select @(reltuples :: bigint)d from pg_class"
" where oid = 'users'::regclass::oid"));
_ ->
ejabberd_sql:sql_query_t(
?SQL("select @(count(*))d from users"))
end;
(_Type, _) ->
ejabberd_sql:sql_query_t(
?SQL("select @(count(*))d from users"))
end).
users_number(LServer, [{prefix, Prefix}])
when is_binary(Prefix) ->
SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix),
SPrefix2 = <<SPrefix/binary, $%>>,
ejabberd_sql:sql_query(
LServer,
?SQL("select @(count(*))d from users "
"where username like %(SPrefix2)s escape '^'"));
users_number(LServer, []) ->
users_number(LServer).
convert_to_scram(Server) ->
LServer = jid:nameprep(Server),
if
@@ -489,7 +267,7 @@ convert_to_scram(Server) ->
"password of user ~s@~s",
[LUser, LServer]);
_ ->
Scram = password_to_scram(Password),
Scram = ejabberd_auth:password_to_scram(Password),
set_password_scram_t(
LUser,
Scram#scram.storedkey,
@@ -502,7 +280,7 @@ convert_to_scram(Server) ->
Err -> {bad_reply, Err}
end
end,
case sql_queries:sql_transaction(LServer, F) of
case ejabberd_sql:sql_transaction(LServer, F) of
{atomic, ok} -> ok;
{atomic, continue} -> convert_to_scram(Server);
{atomic, Error} -> {error, Error};
@@ -510,5 +288,8 @@ convert_to_scram(Server) ->
end
end.
opt_type(auth_password_format) -> fun (V) -> V end;
opt_type(_) -> [auth_password_format].
-spec opt_type(pgsql_users_number_estimate) -> fun((boolean()) -> boolean());
(atom()) -> [atom()].
opt_type(pgsql_users_number_estimate) ->
fun (V) when is_boolean(V) -> V end;
opt_type(_) -> [pgsql_users_number_estimate].
+23 -35
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);
@@ -299,10 +297,7 @@ init([#body{attrs = Attrs}, IP, SID]) ->
XMPPVer = get_attr('xmpp:version', Attrs),
XMPPDomain = get_attr(to, Attrs),
{InBuf, Opts} = case gen_mod:get_module_opt(
XMPPDomain,
mod_bosh, prebind,
fun(B) when is_boolean(B) -> B end,
false) of
XMPPDomain, mod_bosh, prebind, false) of
true ->
JID = make_random_jid(XMPPDomain),
{buf_new(XMPPDomain), [{jid, JID} | Opts2]};
@@ -315,12 +310,9 @@ init([#body{attrs = Attrs}, IP, SID]) ->
Opts),
Inactivity = gen_mod:get_module_opt(XMPPDomain,
mod_bosh, max_inactivity,
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_INACTIVITY),
MaxConcat = gen_mod:get_module_opt(XMPPDomain, mod_bosh, max_concat,
fun(unlimited) -> unlimited;
(N) when is_integer(N), N>0 -> N
end, unlimited),
unlimited),
ShapedReceivers = buf_new(XMPPDomain, ?MAX_SHAPED_REQUESTS_QUEUE_LEN),
State = #state{host = XMPPDomain, sid = SID, ip = IP,
xmpp_ver = XMPPVer, el_ibuf = InBuf,
@@ -366,7 +358,6 @@ wait_for_session(#body{attrs = Attrs} = Req, From,
end,
MaxPause = gen_mod:get_module_opt(State#state.host,
mod_bosh, max_pause,
fun(I) when is_integer(I), I>0 -> I end,
?DEFAULT_MAXPAUSE),
Resp = #body{attrs =
[{sid, State#state.sid}, {wait, Wait},
@@ -578,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,
@@ -605,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, _}, _} ->
@@ -637,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),
@@ -651,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}.
@@ -660,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;
@@ -683,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.
@@ -722,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
@@ -1039,12 +1030,9 @@ buf_new(Host) ->
buf_new(Host, unlimited).
buf_new(Host, Limit) ->
QueueType = case gen_mod:get_module_opt(
Host, mod_bosh, queue_type,
mod_bosh:mod_opt_type(queue_type)) of
undefined -> ejabberd_config:default_queue_type(Host);
T -> T
end,
QueueType = gen_mod:get_module_opt(
Host, mod_bosh, queue_type,
ejabberd_config:default_queue_type(Host)),
p1_queue:new(QueueType, Limit).
buf_in(Xs, Buf) ->
@@ -1063,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) ->
+272 -197
View File
@@ -29,7 +29,7 @@
%% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0]).
%% ejabberd_config callbacks
-export([opt_type/1, transform_listen_option/2]).
-export([opt_type/1, listen_opt_type/1, transform_listen_option/2]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@@ -46,14 +46,15 @@
reject_unauthenticated_packet/2, process_closed/2,
process_terminated/2, process_info/2]).
%% API
-export([get_presence/1, get_subscription/2, get_subscribed/1,
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]).
-include("ejabberd.hrl").
-include("xmpp.hrl").
-include("logger.hrl").
-include("mod_roster.hrl").
-define(SETS, gb_sets).
@@ -66,7 +67,10 @@
start(SockData, Opts) ->
case proplists:get_value(supervisor, Opts, true) of
true ->
supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]);
case supervisor:start_child(ejabberd_c2s_sup, [SockData, Opts]) of
{ok, undefined} -> ignore;
Res -> Res
end;
_ ->
xmpp_stream_in:start(?MODULE, [SockData, Opts],
ejabberd_config:fsm_limit_opts(Opts))
@@ -86,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).
@@ -93,33 +101,27 @@ reply(Ref, Reply) ->
get_presence(Ref) ->
call(Ref, get_presence, 1000).
-spec get_subscription(jid() | ljid(), state()) -> both | from | to | none.
get_subscription(#jid{} = From, State) ->
get_subscription(jid:tolower(From), State);
get_subscription(LFrom, #{pres_f := PresF, pres_t := PresT}) ->
LBFrom = jid:remove_resource(LFrom),
F = ?SETS:is_element(LFrom, PresF) orelse ?SETS:is_element(LBFrom, PresF),
T = ?SETS:is_element(LFrom, PresT) orelse ?SETS:is_element(LBFrom, PresT),
if F and T -> both;
F -> from;
T -> to;
true -> none
end.
-spec set_presence(pid(), presence()) -> ok.
set_presence(Ref, Pres) ->
call(Ref, {set_presence, Pres}, 1000).
-spec get_subscribed(pid()) -> [ljid()].
%% Return list of all available resources of contacts
get_subscribed(Ref) ->
call(Ref, get_subscribed, 1000).
-spec resend_presence(pid()) -> ok.
resend_presence(Pid) ->
resend_presence(Pid, undefined).
-spec resend_presence(pid(), jid() | undefined) -> ok.
resend_presence(Pid, To) ->
route(Pid, {resend_presence, To}).
-spec close(pid()) -> ok;
(state()) -> state().
close(Ref) ->
xmpp_stream_in:close(Ref).
-spec close(pid(), boolean()) -> ok;
(state(), boolean()) -> state().
close(Ref, SendTrailer) ->
xmpp_stream_in:close(Ref, SendTrailer).
-spec close(pid(), atom()) -> ok;
(state(), atom()) -> state().
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
-spec stop(pid()) -> ok;
(state()) -> no_return().
@@ -183,8 +185,7 @@ host_down(Host) ->
copy_state(#{owner := Owner} = NewState,
#{jid := JID, resource := Resource, sid := {Time, _},
auth_module := AuthModule, lserver := LServer,
pres_t := PresT, pres_a := PresA,
pres_f := PresF} = OldState) ->
pres_a := PresA} = OldState) ->
State1 = case OldState of
#{pres_last := Pres, pres_timestamp := PresTS} ->
NewState#{pres_last => Pres, pres_timestamp => PresTS};
@@ -196,8 +197,7 @@ copy_state(#{owner := Owner} = NewState,
conn => Conn,
sid => {Time, Owner},
auth_module => AuthModule,
pres_t => PresT, pres_a => PresA,
pres_f => PresF},
pres_a => PresA},
ejabberd_hooks:run_fold(c2s_copy_session, LServer, State2, [OldState]).
-spec open_session(state()) -> {ok, state()} | state().
@@ -238,10 +238,17 @@ process_info(#{lserver := LServer} = State, {route, Packet}) ->
true ->
State1
end;
process_info(State, force_update_presence) ->
process_info(#{jid := JID} = State, {resend_presence, To}) ->
case maps:get(pres_last, State, error) of
error -> State;
Pres -> process_self_presence(State, Pres)
Pres when To == undefined ->
process_self_presence(State, Pres);
Pres when To#jid.luser == JID#jid.luser andalso
To#jid.lserver == JID#jid.lserver andalso
To#jid.lresource == <<"">> ->
process_self_presence(State, Pres);
Pres ->
process_presence_out(State, xmpp:set_to(Pres, To))
end;
process_info(State, Info) ->
?WARNING_MSG("got unexpected info: ~p", [Info]),
@@ -258,7 +265,8 @@ reject_unauthenticated_packet(State, _Pkt) ->
process_closed(State, Reason) ->
stop(State#{stop_reason => Reason}).
process_terminated(#{sockmod := SockMod, socket := Socket, jid := JID} = State,
process_terminated(#{sid := SID, sockmod := SockMod, socket := Socket,
jid := JID, user := U, server := S, resource := R} = State,
Reason) ->
Status = format_reason(State, Reason),
?INFO_MSG("(~s) Closing c2s session for ~s: ~s",
@@ -269,8 +277,11 @@ process_terminated(#{sockmod := SockMod, socket := Socket, jid := JID} = State,
status = xmpp:mk_text(Status),
from = JID,
to = jid:remove_resource(JID)},
ejabberd_sm:close_session_unset_presence(SID, U, S, R,
Status),
broadcast_presence_unavailable(State, Pres);
false ->
ejabberd_sm:close_session(SID, U, S, R),
State
end,
bounce_message_queue(),
@@ -286,48 +297,45 @@ process_terminated(State, _Reason) ->
%%%===================================================================
%%% xmpp_stream_in callbacks
%%%===================================================================
tls_options(#{lserver := LServer, tls_options := DefaultOpts}) ->
TLSOpts1 = case ejabberd_config:get_option(
{c2s_certfile, LServer},
fun iolist_to_binary/1,
ejabberd_config:get_option(
{domain_certfile, LServer},
fun iolist_to_binary/1)) 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},
fun iolist_to_binary/1) of
{c2s_ciphers, LServer}) of
undefined -> TLSOpts1;
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
{ciphers, Ciphers})
end,
TLSOpts3 = case ejabberd_config:get_option(
{c2s_protocol_options, LServer},
fun (Options) -> str:join(Options, <<$|>>) end) of
{c2s_protocol_options, LServer}) of
undefined -> TLSOpts2;
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
{protocol_options, ProtoOpts})
end,
TLSOpts4 = case ejabberd_config:get_option(
{c2s_dhfile, LServer},
fun iolist_to_binary/1) of
{c2s_dhfile, LServer}) of
undefined -> TLSOpts3;
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
{dhfile, DHFile})
end,
TLSOpts5 = case ejabberd_config:get_option(
{c2s_cafile, LServer},
fun iolist_to_binary/1) of
{c2s_cafile, LServer}) of
undefined -> TLSOpts4;
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
{cafile, CAFile})
end,
case ejabberd_config:get_option(
{c2s_tls_compression, LServer},
fun(B) when is_boolean(B) -> B end) of
case ejabberd_config:get_option({c2s_tls_compression, LServer}) of
undefined -> TLSOpts5;
false -> [compression_none | TLSOpts5];
true -> lists:delete(compression_none, TLSOpts5)
@@ -356,13 +364,7 @@ authenticated_stream_features(#{lserver := LServer}) ->
ejabberd_hooks:run_fold(c2s_post_auth_features, LServer, [], [LServer]).
sasl_mechanisms(Mechs, #{lserver := LServer}) ->
Mechs1 = ejabberd_config:get_option(
{disable_sasl_mechanisms, LServer},
fun(V) when is_list(V) ->
lists:map(fun(M) -> str:to_upper(M) end, V);
(V) ->
[str:to_upper(V)]
end, []),
Mechs1 = ejabberd_config:get_option({disable_sasl_mechanisms, LServer}, []),
Mechs2 = case ejabberd_auth_anonymous:is_sasl_anonymous_enabled(LServer) of
true -> Mechs1;
false -> [<<"ANONYMOUS">>|Mechs1]
@@ -400,20 +402,16 @@ bind(R, #{user := U, server := S, access := Access, lang := Lang,
allow ->
State1 = open_session(State#{resource => Resource,
sid => ejabberd_sm:make_sid()}),
LBJID = jid:remove_resource(jid:tolower(JID)),
PresF = ?SETS:add_element(LBJID, maps:get(pres_f, State1)),
PresT = ?SETS:add_element(LBJID, maps:get(pres_t, State1)),
State2 = State1#{pres_f => PresF, pres_t => PresT},
State3 = ejabberd_hooks:run_fold(
c2s_session_opened, LServer, State2, []),
State2 = ejabberd_hooks:run_fold(
c2s_session_opened, LServer, State1, []),
?INFO_MSG("(~s) Opened c2s session for ~s",
[SockMod:pp(Socket), jid:encode(JID)]),
{ok, State3};
{ok, State2};
deny ->
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.
@@ -500,36 +498,29 @@ handle_send(Pkt, Result, #{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(c2s_handle_send, LServer, State, [Pkt, Result]).
init([State, Opts]) ->
Access = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1, all),
Shaper = gen_mod:get_opt(shaper, Opts, fun acl:shaper_rules_validator/1, none),
Access = proplists:get_value(access, Opts, all),
Shaper = proplists:get_value(shaper, Opts, none),
TLSOpts1 = lists:filter(
fun({certfile, _}) -> true;
({ciphers, _}) -> true;
({dhfile, _}) -> true;
({cafile, _}) -> true;
({protocol_options, _}) -> true;
(_) -> false
end, Opts),
TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
false -> TLSOpts1;
{_, OptString} ->
ProtoOpts = str:join(OptString, <<$|>>),
[{protocol_options, ProtoOpts}|TLSOpts1]
end,
TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
false -> [compression_none | TLSOpts2];
true -> TLSOpts2
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
false -> [compression_none | TLSOpts1];
true -> TLSOpts1
end,
TLSEnabled = proplists:get_bool(starttls, Opts),
TLSRequired = proplists:get_bool(starttls_required, Opts),
TLSVerify = proplists:get_bool(tls_verify, Opts),
Zlib = proplists:get_bool(zlib, Opts),
State1 = State#{tls_options => TLSOpts3,
State1 = State#{tls_options => TLSOpts2,
tls_required => TLSRequired,
tls_enabled => TLSEnabled,
tls_verify => TLSVerify,
pres_a => ?SETS:new(),
pres_f => ?SETS:new(),
pres_t => ?SETS:new(),
zlib => Zlib,
lang => ?MYLANG,
server => ?MYNAME,
@@ -547,9 +538,9 @@ handle_call(get_presence, From, #{jid := JID} = State) ->
end,
reply(From, Pres),
State;
handle_call(get_subscribed, From, #{pres_f := PresF} = State) ->
reply(From, ?SETS:to_list(PresF)),
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]).
@@ -560,17 +551,6 @@ handle_cast(Msg, #{lserver := LServer} = State) ->
handle_info(Info, #{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(c2s_handle_info, LServer, State, [Info]).
terminate(Reason, #{sid := SID,
user := U, server := S, resource := R,
lserver := LServer} = State) ->
case maps:is_key(pres_last, State) of
true ->
Status = format_reason(State, Reason),
ejabberd_sm:close_session_unset_presence(SID, U, S, R, Status);
false ->
ejabberd_sm:close_session(SID, U, S, R)
end,
ejabberd_hooks:run_fold(c2s_terminated, LServer, State, [Reason]);
terminate(Reason, #{lserver := LServer} = State) ->
ejabberd_hooks:run_fold(c2s_terminated, LServer, State, [Reason]).
@@ -615,36 +595,36 @@ process_message_in(State, #message{type = T} = Msg) ->
-spec process_presence_in(state(), presence()) -> {boolean(), state()}.
process_presence_in(#{lserver := LServer, pres_a := PresA} = State0,
#presence{from = From, to = To, type = T} = Pres) ->
#presence{from = From, type = T} = Pres) ->
State = ejabberd_hooks:run_fold(c2s_presence_in, LServer, State0, [Pres]),
case T of
probe ->
NewState = add_to_pres_a(State, From),
route_probe_reply(From, To, NewState),
{false, NewState};
route_probe_reply(From, State),
{false, State};
error ->
A = ?SETS:del_element(jid:tolower(From), PresA),
{true, State#{pres_a => A}};
_ ->
case privacy_check_packet(State, Pres, in) of
allow ->
NewState = add_to_pres_a(State, From),
{true, NewState};
{true, State};
deny ->
{false, State}
end
end.
-spec route_probe_reply(jid(), jid(), state()) -> ok.
route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
pres_last := LastPres,
pres_timestamp := TS} = State) ->
LFrom = jid:tolower(From),
LBFrom = jid:remove_resource(LFrom),
case ?SETS:is_element(LFrom, PresF)
orelse ?SETS:is_element(LBFrom, PresF) of
true ->
%% To is my JID
-spec route_probe_reply(jid(), state()) -> ok.
route_probe_reply(From, #{jid := To,
pres_last := LastPres,
pres_timestamp := TS} = State) ->
{LUser, LServer, LResource} = jid:tolower(To),
IsAnotherResource = case jid:tolower(From) of
{LUser, LServer, R} when R /= LResource -> true;
_ -> false
end,
Subscription = get_subscription(To, From),
if IsAnotherResource orelse
Subscription == both orelse Subscription == from ->
Packet = xmpp_util:add_delay_info(LastPres, To, TS),
case privacy_check_packet(State, Packet, out) of
deny ->
@@ -653,19 +633,12 @@ route_probe_reply(From, To, #{lserver := LServer, pres_f := PresF,
ejabberd_hooks:run(presence_probe_hook,
LServer,
[From, To, self()]),
%% Don't route a presence probe to oneself
case From == To of
false ->
ejabberd_router:route(
xmpp:set_from_to(Packet, To, From));
true ->
ok
end
ejabberd_router:route(xmpp:set_from_to(Packet, To, From))
end;
false ->
true ->
ok
end;
route_probe_reply(_, _, _) ->
route_probe_reply(_, _) ->
ok.
-spec process_presence_out(state(), presence()) -> state().
@@ -681,13 +654,11 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
send_error(State, Pres, Err);
allow when Type == subscribe; Type == subscribed;
Type == unsubscribe; Type == unsubscribed ->
Access = gen_mod:get_module_opt(LServer, mod_roster, access,
fun(A) when is_atom(A) -> A end,
all),
Access = gen_mod:get_module_opt(LServer, mod_roster, access, all),
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 ->
@@ -703,11 +674,22 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
State;
allow ->
ejabberd_router:route(Pres),
A = case Type of
available -> ?SETS:add_element(LTo, PresA);
unavailable -> ?SETS:del_element(LTo, PresA)
end,
State#{pres_a => A}
LBareTo = jid:remove_resource(LTo),
LBareFrom = jid:remove_resource(jid:tolower(From)),
if LBareTo /= LBareFrom ->
Subscription = get_subscription(From, To),
if Subscription /= both andalso Subscription /= from ->
A = case Type of
available -> ?SETS:add_element(LTo, PresA);
unavailable -> ?SETS:del_element(LTo, PresA)
end,
State#{pres_a => A};
true ->
State
end;
true ->
State
end
end.
-spec process_self_presence(state(), presence()) -> state().
@@ -744,24 +726,81 @@ update_priority(#{ip := IP, conn := Conn, auth_module := AuthMod,
ejabberd_sm:set_presence(SID, U, S, R, Priority, Pres, Info).
-spec broadcast_presence_unavailable(state(), presence()) -> state().
broadcast_presence_unavailable(#{pres_a := PresA} = State, Pres) ->
JIDs = filter_blocked(State, Pres, PresA),
broadcast_presence_unavailable(#{jid := JID, pres_a := PresA} = State, Pres) ->
#jid{luser = LUser, lserver = LServer} = JID,
BareJID = jid:remove_resource(JID),
Items1 = ejabberd_hooks:run_fold(roster_get, LServer,
[], [{LUser, LServer}]),
Items2 = ?SETS:fold(
fun(LJID, Items) ->
[#roster{jid = LJID, subscription = from}|Items]
end, Items1, PresA),
JIDs = lists:foldl(
fun(#roster{jid = LJID, subscription = Sub}, Tos)
when Sub == both orelse Sub == from ->
To = jid:make(LJID),
P = xmpp:set_to(Pres, jid:make(LJID)),
case privacy_check_packet(State, P, out) of
allow -> [To|Tos];
deny -> Tos
end;
(_, Tos) ->
Tos
end, [BareJID], Items2),
route_multiple(State, JIDs, Pres),
State#{pres_a => ?SETS:new()}.
-spec broadcast_presence_available(state(), presence(), boolean()) -> state().
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF,
pres_t := PresT, jid := JID} = State,
broadcast_presence_available(#{jid := JID} = State,
Pres, _FromUnavailable = true) ->
Probe = #presence{from = JID, type = probe},
TJIDs = filter_blocked(State, Probe, PresT),
FJIDs = filter_blocked(State, Pres, PresF),
#jid{luser = LUser, lserver = LServer} = JID,
BareJID = jid:remove_resource(JID),
Items = ejabberd_hooks:run_fold(roster_get, LServer,
[], [{LUser, LServer}]),
{FJIDs, TJIDs} =
lists:foldl(
fun(#roster{jid = LJID, subscription = Sub}, {F, T}) ->
To = jid:make(LJID),
F1 = if Sub == both orelse Sub == from ->
Pres1 = xmpp:set_to(Pres, To),
case privacy_check_packet(State, Pres1, out) of
allow -> [To|F];
deny -> F
end;
true -> F
end,
T1 = if Sub == both orelse Sub == to ->
Probe1 = xmpp:set_to(Probe, To),
case privacy_check_packet(State, Probe1, out) of
allow -> [To|T];
deny -> T
end;
true -> T
end,
{F1, T1}
end, {[BareJID], [BareJID]}, Items),
route_multiple(State, TJIDs, Probe),
route_multiple(State, FJIDs, Pres),
State#{pres_a => ?SETS:union(PresA, PresF)};
broadcast_presence_available(#{pres_a := PresA, pres_f := PresF} = State,
State;
broadcast_presence_available(#{jid := JID} = State,
Pres, _FromUnavailable = false) ->
JIDs = filter_blocked(State, Pres, ?SETS:intersection(PresA, PresF)),
#jid{luser = LUser, lserver = LServer} = JID,
BareJID = jid:remove_resource(JID),
Items = ejabberd_hooks:run_fold(
roster_get, LServer, [], [{LUser, LServer}]),
JIDs = lists:foldl(
fun(#roster{jid = LJID, subscription = Sub}, Tos)
when Sub == both orelse Sub == from ->
To = jid:make(LJID),
P = xmpp:set_to(Pres, jid:make(LJID)),
case privacy_check_packet(State, P, out) of
allow -> [To|Tos];
deny -> Tos
end;
(_, Tos) ->
Tos
end, [BareJID], Items),
route_multiple(State, JIDs, Pres),
State.
@@ -789,45 +828,32 @@ get_priority_from_presence(#presence{priority = Prio}) ->
_ -> Prio
end.
-spec filter_blocked(state(), presence(), ?SETS:set()) -> [jid()].
filter_blocked(#{jid := From} = State, Pres, LJIDSet) ->
?SETS:fold(
fun(LJID, Acc) ->
To = jid:make(LJID),
Pkt = xmpp:set_from_to(Pres, From, To),
case privacy_check_packet(State, Pkt, out) of
allow -> [To|Acc];
deny -> Acc
end
end, [], LJIDSet).
-spec route_multiple(state(), [jid()], stanza()) -> ok.
route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
From = xmpp:get_from(Pkt),
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
{Subscription, _} = ejabberd_hooks:run_fold(
roster_get_jid_info, LServer, {none, []},
[LUser, LServer, JID]),
Subscription.
-spec resource_conflict_action(binary(), binary(), binary()) ->
{accept_resource, binary()} | closenew.
resource_conflict_action(U, S, R) ->
OptionRaw = case ejabberd_sm:is_existing_resource(U, S, R) of
true ->
ejabberd_config:get_option(
{resource_conflict, S},
fun(setresource) -> setresource;
(closeold) -> closeold;
(closenew) -> closenew;
(acceptnew) -> acceptnew
end);
{resource_conflict, S}, acceptnew);
false ->
acceptnew
end,
Option = case OptionRaw of
setresource -> setresource;
closeold ->
acceptnew; %% ejabberd_sm will close old session
closeold -> acceptnew; %% ejabberd_sm will close old session
closenew -> closenew;
acceptnew -> acceptnew;
_ -> acceptnew %% default ejabberd behavior
acceptnew -> acceptnew
end,
case Option of
acceptnew -> {accept_resource, R};
@@ -867,13 +893,17 @@ get_conn_type(State) ->
-spec fix_from_to(xmpp_element(), state()) -> stanza().
fix_from_to(Pkt, #{jid := JID}) when ?is_stanza(Pkt) ->
#jid{luser = U, lserver = S, lresource = R} = JID,
From = xmpp:get_from(Pkt),
From1 = case jid:tolower(From) of
{U, S, R} -> JID;
{U, S, _} -> jid:replace_resource(JID, From#jid.resource);
_ -> From
end,
xmpp:set_from_to(Pkt, From1, JID);
case xmpp:get_from(Pkt) of
undefined ->
Pkt;
From ->
From1 = case jid:tolower(From) of
{U, S, R} -> JID;
{U, S, _} -> jid:replace_resource(JID, From#jid.resource);
_ -> From
end,
xmpp:set_from_to(Pkt, From1, JID)
end;
fix_from_to(Pkt, _State) ->
Pkt.
@@ -886,30 +916,6 @@ change_shaper(#{shaper := ShaperName, ip := IP, lserver := LServer,
LServer),
xmpp_stream_in:change_shaper(State, Shaper).
-spec add_to_pres_a(state(), jid()) -> state().
add_to_pres_a(#{pres_a := PresA, pres_f := PresF} = State, From) ->
LFrom = jid:tolower(From),
LBFrom = jid:remove_resource(LFrom),
case (?SETS):is_element(LFrom, PresA) orelse
(?SETS):is_element(LBFrom, PresA) of
true ->
State;
false ->
case (?SETS):is_element(LFrom, PresF) of
true ->
A = (?SETS):add_element(LFrom, PresA),
State#{pres_a => A};
false ->
case (?SETS):is_element(LBFrom, PresF) of
true ->
A = (?SETS):add_element(LBFrom, PresA),
State#{pres_a => A};
false ->
State
end
end
end.
-spec format_reason(state(), term()) -> binary().
format_reason(#{stop_reason := Reason}, _) ->
xmpp_stream_in:format_error(Reason);
@@ -925,11 +931,20 @@ format_reason(_, _) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
opt_type(domain_certfile) -> fun iolist_to_binary/1;
opt_type(c2s_certfile) -> fun iolist_to_binary/1;
-type resource_conflict() :: setresource | closeold | closenew | acceptnew.
-spec opt_type(c2s_certfile) -> fun((binary()) -> binary());
(c2s_ciphers) -> fun((binary()) -> binary());
(c2s_dhfile) -> fun((binary()) -> binary());
(c2s_cafile) -> fun((binary()) -> binary());
(c2s_protocol_options) -> fun(([binary()]) -> binary());
(c2s_tls_compression) -> fun((boolean()) -> boolean());
(resource_conflict) -> fun((resource_conflict()) -> resource_conflict());
(disable_sasl_mechanisms) -> fun((binary() | [binary()]) -> [binary()]);
(atom()) -> [atom()].
opt_type(c2s_certfile) -> fun misc:try_read_file/1;
opt_type(c2s_ciphers) -> fun iolist_to_binary/1;
opt_type(c2s_dhfile) -> fun iolist_to_binary/1;
opt_type(c2s_cafile) -> fun iolist_to_binary/1;
opt_type(c2s_dhfile) -> fun misc:try_read_file/1;
opt_type(c2s_cafile) -> fun misc:try_read_file/1;
opt_type(c2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(c2s_tls_compression) ->
@@ -948,6 +963,66 @@ opt_type(disable_sasl_mechanisms) ->
(V) -> [str:to_upper(V)]
end;
opt_type(_) ->
[domain_certfile, c2s_certfile, c2s_ciphers, c2s_cafile,
[c2s_certfile, c2s_ciphers, c2s_cafile,
c2s_protocol_options, c2s_tls_compression, resource_conflict,
disable_sasl_mechanisms].
-spec listen_opt_type(access) -> fun((any()) -> any());
(shaper) -> fun((any()) -> any());
(certfile) -> fun((binary()) -> binary());
(ciphers) -> fun((binary()) -> binary());
(dhfile) -> fun((binary()) -> binary());
(cafile) -> fun((binary()) -> binary());
(protocol_options) -> fun(([binary()]) -> binary());
(tls_compression) -> fun((boolean()) -> boolean());
(tls) -> fun((boolean()) -> boolean());
(starttls) -> fun((boolean()) -> boolean());
(tls_verify) -> fun((boolean()) -> boolean());
(zlib) -> fun((boolean()) -> boolean());
(supervisor) -> fun((boolean()) -> boolean());
(max_stanza_size) -> fun((timeout()) -> timeout());
(max_fsm_queue) -> fun((timeout()) -> timeout());
(stream_management) -> fun((boolean()) -> boolean());
(atom()) -> [atom()].
listen_opt_type(access) -> fun acl:access_rules_validator/1;
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
listen_opt_type(certfile) ->
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(ciphers) -> opt_type(c2s_ciphers);
listen_opt_type(dhfile) -> opt_type(c2s_dhfile);
listen_opt_type(cafile) -> opt_type(c2s_cafile);
listen_opt_type(protocol_options) -> opt_type(c2s_protocol_options);
listen_opt_type(tls_compression) -> opt_type(c2s_tls_compression);
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(starttls_required) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(tls_verify) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(zlib) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(max_stanza_size) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
listen_opt_type(max_fsm_queue) ->
fun(I) when is_integer(I), I>0 -> I end;
%% The following hack should be removed in future releases: it is intended
%% for backward compatibility with ejabberd 17.01 or older
listen_opt_type(stream_management) ->
?WARNING_MSG("listening option 'stream_management' is deprecated: "
"use mod_stream_mgmt module", []),
fun(B) when is_boolean(B) -> B end;
listen_opt_type(O) ->
case mod_stream_mgmt:mod_opt_type(O) of
L when is_list(L) ->
[access, shaper, certfile, ciphers, dhfile, cafile,
protocol_options, tls, tls_compression, starttls,
starttls_required, tls_verify, zlib, max_fsm_queue];
VFun ->
?WARNING_MSG("listening option '~s' is deprecated: use '~s' "
"option from mod_stream_mgmt module", [O, O]),
VFun
end.
+7 -14
View File
@@ -26,24 +26,20 @@
-module(ejabberd_c2s_config).
-behaviour(ejabberd_config).
-author('mremond@process-one.net').
-export([get_c2s_limits/0, opt_type/1]).
-export([get_c2s_limits/0]).
%% Get first c2s configuration limitations to apply it to other c2s
%% connectors.
get_c2s_limits() ->
case ejabberd_config:get_option(listen, fun(V) -> V end) of
undefined -> [];
C2SFirstListen ->
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
false -> [];
{value, {_Port, ejabberd_c2s, Opts}} ->
select_opts_values(Opts)
end
C2SFirstListen = ejabberd_config:get_option(listen, []),
case lists:keysearch(ejabberd_c2s, 2, C2SFirstListen) of
false -> [];
{value, {_Port, ejabberd_c2s, Opts}} ->
select_opts_values(Opts)
end.
%% Only get access, shaper and max_stanza_size values
select_opts_values(Opts) ->
@@ -65,6 +61,3 @@ select_opts_values([{max_stanza_size, Value} | Opts],
[{max_stanza_size, Value} | SelectedValues]);
select_opts_values([_Opt | Opts], SelectedValues) ->
select_opts_values(Opts, SelectedValues).
opt_type(listen) -> fun (V) -> V end;
opt_type(_) -> [listen].
+20 -32
View File
@@ -350,12 +350,7 @@ do_create_image(Key) ->
end.
get_prog_name() ->
case ejabberd_config:get_option(
captcha_cmd,
fun(FileName) ->
F = iolist_to_binary(FileName),
if F /= <<"">> -> F end
end) of
case ejabberd_config:get_option(captcha_cmd) of
undefined ->
?DEBUG("The option captcha_cmd is not configured, "
"but some module wants to use the CAPTCHA "
@@ -367,10 +362,7 @@ get_prog_name() ->
end.
get_url(Str) ->
CaptchaHost = ejabberd_config:get_option(
captcha_host,
fun iolist_to_binary/1,
<<"">>),
CaptchaHost = ejabberd_config:get_option(captcha_host, <<"">>),
case str:tokens(CaptchaHost, <<":">>) of
[Host] ->
<<"http://", Host/binary, "/captcha/", Str/binary>>;
@@ -395,39 +387,32 @@ get_transfer_protocol(PortString) ->
get_captcha_transfer_protocol(PortListeners).
get_port_listeners(PortNumber) ->
AllListeners = ejabberd_config:get_option(listen, fun(V) -> V end),
lists:filter(fun (Listener) when is_list(Listener) ->
case proplists:get_value(port, Listener) of
PortNumber -> true;
_ -> false
end;
(_) -> false
end,
AllListeners).
AllListeners = ejabberd_config:get_option(listen, []),
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).
is_limited(undefined) -> false;
is_limited(Limiter) ->
case ejabberd_config:get_option(
captcha_limit,
fun(I) when is_integer(I), I > 0 -> I end) of
case ejabberd_config:get_option(captcha_limit) of
undefined -> false;
Int ->
case catch gen_server:call(?MODULE,
@@ -538,6 +523,10 @@ clean_treap(Treap, CleanPriority) ->
now_priority() ->
-p1_time_compat:system_time(micro_seconds).
-spec opt_type(captcha_cmd) -> fun((binary()) -> binary());
(captcha_host) -> fun((binary()) -> binary());
(captcha_limit) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
opt_type(captcha_cmd) ->
fun (FileName) ->
F = iolist_to_binary(FileName), if F /= <<"">> -> F end
@@ -545,6 +534,5 @@ opt_type(captcha_cmd) ->
opt_type(captcha_host) -> fun iolist_to_binary/1;
opt_type(captcha_limit) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(listen) -> fun (V) -> V end;
opt_type(_) ->
[captcha_cmd, captcha_host, captcha_limit, listen].
[captcha_cmd, captcha_host, captcha_limit].
+152 -85
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,119 +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]).
-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.
%%%===================================================================
%%% 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().
+9 -285
View File
@@ -211,6 +211,7 @@
-author('badlop@process-one.net').
-behaviour(gen_server).
-behaviour(ejabberd_config).
-define(DEFAULT_VERSION, 1000000).
@@ -220,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,
@@ -229,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,
@@ -297,7 +292,6 @@ init([]) ->
{local_content, true},
{attributes, record_info(fields, ejabberd_commands)},
{type, bag}]),
mnesia:add_table_copy(ejabberd_commands, node(), ram_copies),
register_commands(get_commands_spec()),
ejabberd_access_permissions:register_permission_addon(?MODULE, fun permission_addon/0),
{ok, #state{}}.
@@ -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, fun(A) -> A end, 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,87 +556,10 @@ 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) ->
Opts0 = ejabberd_config:get_option(
commands,
fun(V) when is_list(V) -> V end,
[]),
Opts0 = ejabberd_config:get_option(commands, []),
Opts = lists:map(fun(V) when is_tuple(V) -> [V]; (V) -> V end, Opts0),
CommandsList = list_commands_policy(Version),
OpenCmds = [N || {N, _, _, open} <- CommandsList],
@@ -857,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) ->
@@ -877,10 +603,7 @@ is_admin(Name, Auth, Extra) ->
_ ->
{Extra, global}
end,
AdminAccess = ejabberd_config:get_option(
commands_admin_access,
fun(V) -> V end,
none),
AdminAccess = ejabberd_config:get_option(commands_admin_access, none),
case acl:access_matches(AdminAccess, ACLInfo, Server) of
allow ->
case catch check_auth(get_command_definition(Name), Auth) of
@@ -894,11 +617,12 @@ is_admin(Name, Auth, Extra) ->
permission_addon() ->
[{<<"'commands' option compatibility shim">>,
{[],
[{access, ejabberd_config:get_option(commands_admin_access,
fun(V) -> V end,
none)}],
[{access, ejabberd_config:get_option(commands_admin_access, none)}],
{get_exposed_commands(), []}}}].
-spec opt_type(commands_admin_access) -> fun((any()) -> any());
(commands) -> fun((list()) -> list());
(atom()) -> [atom()].
opt_type(commands_admin_access) -> fun acl:access_rules_validator/1;
opt_type(commands) ->
fun(V) when is_list(V) -> V end;
+85 -54
View File
@@ -69,9 +69,9 @@ list_join_with([El|Tail], M) ->
end, [El], Tail)).
md_tag(dt, V) ->
[<<"\n">>, V, <<"\n">>];
[<<"- ">>, V];
md_tag(dd, V) ->
[<<"\n: ">>, V, <<"\n">>];
[<<" : ">>, V, <<"\n">>];
md_tag(li, V) ->
[<<"- ">>, V, <<"\n">>];
md_tag(pre, V) ->
@@ -87,14 +87,6 @@ md_tag(strong, V) ->
md_tag(_, V) ->
V.
%% rescode_to_int(ok) ->
%% 0;
%% rescode_to_int(true) ->
%% 0;
%% rescode_to_int(_) ->
%% 1.
perl_gen({Name, integer}, Int, _Indent, HTMLOutput) ->
[?ARG(Name), ?OP_L(" => "), ?NUM(Int)];
perl_gen({Name, string}, Str, _Indent, HTMLOutput) ->
@@ -257,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">>;
@@ -324,55 +316,94 @@ gen_calls(#ejabberd_commands{args_example=Values, args=ArgsDesc,
case lists:member(<<"xmlrpc">>, Langs) of true -> ?TAG(li, ?TAG(pre, XML)); _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> ?TAG(li, ?TAG(pre, JSON)); _ -> [] end])];
true ->
[<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
<<"{: .code-samples-labels}\n">>,
case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"{: .code-samples-tabs}\n\n">>]
case Langs of
Val when length(Val) == 0 orelse length(Val) == 1 ->
[case lists:member(<<"java">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> [<<"\n">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> [<<"\n">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"\n\n">>];
_ ->
[<<"\n">>, case lists:member(<<"java">>, Langs) of true -> <<"* Java\n">>; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> <<"* Perl\n">>; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> <<"* XmlRPC\n">>; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> <<"* JSON\n">>; _ -> [] end,
<<"{: .code-samples-labels}\n">>,
case lists:member(<<"java">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Java), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"perl">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, Perl), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"xmlrpc">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, XML), <<"~~~\n">>]; _ -> [] end,
case lists:member(<<"json">>, Langs) of true -> [<<"\n* ">>, ?TAG(pre, JSON), <<"~~~\n">>]; _ -> [] end,
<<"{: .code-samples-tabs}\n\n">>]
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(format_type(Type))])];
gen_param(Name, Type, Desc, HTMLOutput) ->
[?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", lists:map(fun({AName, Type}) ->
[?TAG(li, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
?RAW(io_lib:format("~p", [Type]))])]
end, Args))];
_ ->
[?TAG(dl, "args-list", lists:map(fun({{AName, Type}, ADesc}) ->
[?TAG(dt, [?TAG_R(strong, atom_to_list(AName)), <<" :: ">>,
?RAW(io_lib:format("~p", [Type]))]),
?TAG(dd, ?RAW(ADesc))]
end, lists:zip(Args, ArgsDesc)))]
end,
ResultText = case ResultDesc of
none ->
[?RAW(io_lib:format("~p", [Result]))];
_ ->
[?TAG(dl, [
?TAG(dt, io_lib:format("~p", [Result])),
?TAG_R(dd, ResultDesc)])]
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,
?TAG(h2, <<"Result:">>),
ResultText,
?TAG(h2, <<"Examples:">>),
gen_calls(Cmd, HTMLOutput, Langs)].
[?TAG(h1, [?TAG(strong, atom_to_list(Name)), <<" - ">>, ?RAW(Desc)]),
?TAG(p, ?RAW(LDesc)),
?TAG(h2, <<"Arguments:">>), ArgsText,
?TAG(h2, <<"Result:">>), ResultText,
?TAG(h2, <<"Examples:">>), gen_calls(Cmd, HTMLOutput, Langs)]
catch
_:Ex ->
throw(iolist_to_binary(io_lib:format(
<<"Error when generating documentation for command '~p': ~p">>,
[Name, Ex])))
end.
find_commands_definitions() ->
case code:lib_dir(ejabberd, ebin) of
+228 -253
View File
@@ -27,33 +27,37 @@
-author('alexey@process-one.net').
-export([start/0, load_file/1, reload_file/0, read_file/1,
get_option/2, get_option/3, add_option/2, has_option/1,
get_vh_by_auth_method/1, is_file_readable/1,
get_version/0, get_myhosts/0, get_mylang/0,
get_option/1, get_option/2, add_option/2, has_option/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, convert_table_to_binary/5,
transform_options/1, collect_options/1,
prepare_opt_val/4, transform_options/1, collect_options/1,
convert_to_yaml/1, convert_to_yaml/2, v_db/2,
env_binary_to_list/2, opt_type/1, may_hide_data/1,
is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1,
default_db/1, default_db/2, default_ram_db/1, default_ram_db/2,
default_queue_type/1, queue_dir/0, fsm_limit_opts/1]).
default_queue_type/1, queue_dir/0, fsm_limit_opts/1,
use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1]).
-export([start/2]).
%% The following functions are deprecated.
-export([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_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_global_option, 3}, {get_local_option, 3},
{get_option, 3}, {is_file_readable, 1}]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_config.hrl").
-include_lib("kernel/include/file.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
-callback opt_type(atom()) -> function() | [atom()].
@@ -65,8 +69,10 @@
%% @type macro_value() = term().
start() ->
mnesia_init(),
ConfigFile = get_ejabberd_config_path(),
?INFO_MSG("Loading configuration from ~s", [ConfigFile]),
p1_options:start_link(ejabberd_options),
p1_options:start_link(ejabberd_db_modules),
State1 = load_file(ConfigFile),
UnixTime = p1_time_compat:system_time(seconds),
SharedKey = case erlang:get_cookie() of
@@ -99,23 +105,11 @@ hosts_to_start(State) ->
%% At the moment, these functions are mainly used to setup unit tests.
-spec start(Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok.
start(Hosts, Opts) ->
mnesia_init(),
p1_options:start_link(ejabberd_options),
p1_options:start_link(ejabberd_db_modules),
set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})),
ok.
mnesia_init() ->
case catch mnesia:table_info(local_config, storage_type) of
disc_copies ->
mnesia:delete_table(local_config);
_ ->
ok
end,
ejabberd_mnesia:create(?MODULE, local_config,
[{ram_copies, [node()]},
{local_content, true},
{attributes, record_info(fields, local_config)}]),
mnesia:add_table_copy(local_config, node(), ram_copies).
%% @doc Get the filename of the ejabberd configuration file.
%% The filename can be specified with: erl -config "/path/to/ejabberd.yml".
%% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH.
@@ -186,7 +180,10 @@ read_file(File, Opts) ->
load_file(File) ->
State0 = read_file(File),
State1 = hosts_to_start(State0),
validate_opts(State1).
AllMods = get_modules(),
init_module_db_table(AllMods),
ModOpts = get_modules_with_options(AllMods),
validate_opts(State1, ModOpts).
-spec reload_file() -> ok.
@@ -315,8 +312,6 @@ consult(File) ->
case file:consult(File) of
{ok, Terms} ->
{ok, Terms};
{error, enoent} ->
{error, enoent};
{error, {LineNumber, erl_parse, _ParseMessage} = Reason} ->
{error, describe_config_problem(File, Reason, LineNumber)};
{error, Reason} ->
@@ -763,35 +758,21 @@ append_option({Opt, Host}, Val, State) ->
set_opts(State) ->
Opts = State#state.opts,
F = fun() ->
lists:foreach(
fun({node_start, _}) -> ok;
({shared_key, _}) -> ok;
(Key) -> mnesia:delete({local_config, Key})
end, mnesia:all_keys(local_config)),
lists:foreach(fun mnesia:write/1, Opts)
end,
case mnesia:transaction(F) of
{atomic, _} ->
set_log_level();
{aborted,{no_exists,Table}} ->
MnesiaDirectory = mnesia:system_info(directory),
?CRITICAL_MSG("Error reading Mnesia database spool files:~n"
"The Mnesia database couldn't read the spool file for the table '~p'.~n"
"ejabberd needs read and write access in the directory:~n ~s~n"
"Maybe the problem is a change in the computer hostname,~n"
"or a change in the Erlang node name, which is currently:~n ~p~n"
"Check the ejabberd guide for details about changing the~n"
"computer hostname or Erlang node name.~n",
[Table, MnesiaDirectory, node()]),
exit("Error reading Mnesia database")
end.
ets:select_delete(ejabberd_options,
ets:fun2ms(
fun({{node_start, _}, _}) -> false;
({{shared_key, _}, _}) -> false;
(_) -> true
end)),
lists:foreach(
fun(#local_config{key = {Opt, Host}, value = Val}) ->
p1_options:insert(ejabberd_options, Opt, Host, Val)
end, Opts),
p1_options:compile(ejabberd_options),
set_log_level().
set_log_level() ->
Level = ejabberd_config:get_option(
loglevel,
fun(P) when P>=0, P=<5 -> P end,
4),
Level = get_option(loglevel, 4),
ejabberd_logger:set(Level).
add_global_option(Opt, Val) ->
@@ -802,11 +783,9 @@ add_local_option(Opt, Val) ->
add_option(Opt, Val) when is_atom(Opt) ->
add_option({Opt, global}, Val);
add_option(Opt, Val) ->
mnesia:transaction(fun() ->
mnesia:write(#local_config{key = Opt,
value = Val})
end).
add_option({Opt, Host}, Val) ->
p1_options:insert(ejabberd_options, Opt, Host, Val),
p1_options:compile(ejabberd_options).
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any().
@@ -843,97 +822,126 @@ prepare_opt_val(Opt, Val, F, Default) ->
-spec get_global_option(any(), check_fun()) -> any().
get_global_option(Opt, F) ->
get_option(Opt, F, undefined).
get_global_option(Opt, _) ->
get_option(Opt, undefined).
-spec get_global_option(any(), check_fun(), any()) -> any().
get_global_option(Opt, F, Default) ->
get_option(Opt, F, Default).
get_global_option(Opt, _, Default) ->
get_option(Opt, Default).
-spec get_local_option(any(), check_fun()) -> any().
get_local_option(Opt, F) ->
get_option(Opt, F, undefined).
get_local_option(Opt, _) ->
get_option(Opt, undefined).
-spec get_local_option(any(), check_fun(), any()) -> any().
get_local_option(Opt, F, Default) ->
get_option(Opt, F, Default).
get_local_option(Opt, _, Default) ->
get_option(Opt, Default).
-spec get_option(any(), check_fun()) -> any().
get_option(Opt, F) ->
get_option(Opt, F, undefined).
-spec get_option(any()) -> any().
get_option(Opt) ->
get_option(Opt, undefined).
-spec get_option(any(), check_fun(), any()) -> any().
get_option(Opt, _, Default) ->
get_option(Opt, Default).
get_option(Opt, F, Default) when is_atom(Opt) ->
get_option({Opt, global}, F, Default);
get_option(Opt, F, Default) ->
case Opt of
{O, global} when is_atom(O) -> ok;
{O, H} when is_atom(O), is_binary(H) -> ok;
_ -> ?WARNING_MSG("Option ~p has invalid (outdated?) format. "
"This is likely a bug", [Opt])
end,
case ets:lookup(local_config, Opt) of
[#local_config{value = Val}] ->
prepare_opt_val(Opt, Val, F, Default);
_ ->
case Opt of
{Key, Host} when Host /= global ->
get_option({Key, global}, F, Default);
_ ->
Default
end
-spec get_option(any(), check_fun() | any()) -> any().
get_option(Opt, F) when is_function(F) ->
get_option(Opt, undefined);
get_option(Opt, Default) when is_atom(Opt) ->
get_option({Opt, global}, Default);
get_option(Opt, Default) ->
{Key, Host} = case Opt of
{O, global} when is_atom(O) -> Opt;
{O, H} when is_atom(O), is_binary(H) -> Opt;
_ ->
?WARNING_MSG("Option ~p has invalid (outdated?) "
"format. This is likely a bug", [Opt]),
{undefined, global}
end,
case ejabberd_options:is_known(Key) of
true ->
case ejabberd_options:Key(Host) of
{ok, Val} -> Val;
undefined -> Default
end;
false ->
Default
end.
-spec has_option(atom() | {atom(), global | binary()}) -> any().
has_option(Opt) ->
get_option(Opt, fun(_) -> true end, false).
get_option(Opt) /= undefined.
init_module_db_table(Modules) ->
catch ets:new(module_db, [named_table, public, bag,
{read_concurrency, true}]),
%% Dirty hack for mod_pubsub
ets:insert(module_db, {mod_pubsub, mnesia}),
ets:insert(module_db, {mod_pubsub, sql}),
p1_options:insert(ejabberd_db_modules, mod_pubsub, mnesia, true),
p1_options:insert(ejabberd_db_modules, mod_pubsub, sql, true),
lists:foreach(
fun(M) ->
case re:split(atom_to_list(M), "_", [{return, list}]) of
[_] ->
ok;
Parts ->
[Suffix|T] = lists:reverse(Parts),
BareMod = string:join(lists:reverse(T), "_"),
ets:insert(module_db, {list_to_atom(BareMod),
list_to_atom(Suffix)})
[H|T] = lists:reverse(Parts),
Suffix = list_to_atom(H),
BareMod = list_to_atom(string:join(lists:reverse(T), "_")),
case is_behaviour(BareMod, M) of
true ->
p1_options:insert(ejabberd_db_modules,
BareMod, Suffix, true);
false ->
ok
end
end
end, Modules).
end, Modules),
p1_options:compile(ejabberd_db_modules).
is_behaviour(Behav, Mod) ->
try Mod:module_info(attributes) of
[] ->
%% Stripped module?
true;
Attrs ->
lists:any(
fun({behaviour, L}) -> lists:member(Behav, L);
({behavior, L}) -> lists:member(Behav, L);
(_) -> false
end, Attrs)
catch _:_ ->
true
end.
-spec v_db(module(), atom()) -> atom().
v_db(Mod, internal) -> v_db(Mod, mnesia);
v_db(Mod, odbc) -> v_db(Mod, sql);
v_db(Mod, Type) ->
case ets:match_object(module_db, {Mod, Type}) of
[_|_] -> Type;
[] -> erlang:error(badarg)
case ejabberd_db_modules:is_known(Mod) of
true ->
case ejabberd_db_modules:Mod(Type) of
{ok, _} -> Type;
_ -> erlang:error(badarg)
end;
false ->
erlang:error(badarg)
end.
-spec v_dbs(module()) -> [atom()].
v_dbs(Mod) ->
lists:flatten(ets:match(module_db, {Mod, '$1'})).
ejabberd_db_modules:get_scope(Mod).
-spec v_dbs_mods(module()) -> [module()].
v_dbs_mods(Mod) ->
lists:map(fun([M]) ->
lists:map(fun(M) ->
binary_to_atom(<<(atom_to_binary(Mod, utf8))/binary, "_",
(atom_to_binary(M, utf8))/binary>>, utf8)
end, ets:match(module_db, {Mod, '$1'})).
end, v_dbs(Mod)).
-spec default_db(module()) -> atom().
default_db(Module) ->
@@ -953,7 +961,7 @@ default_ram_db(Host, Module) ->
-spec default_db(default_db | default_ram_db, binary() | global, module()) -> atom().
default_db(Opt, Host, Module) ->
case get_option({Opt, Host}, fun(T) when is_atom(T) -> T end) of
case get_option({Opt, Host}) of
undefined ->
mnesia;
DBType ->
@@ -967,37 +975,40 @@ default_db(Opt, Host, Module) ->
end
end.
get_modules_with_options() ->
get_modules() ->
{ok, Mods} = application:get_key(ejabberd, modules),
ExtMods = [Name || {Name, _Details} <- ext_mod:installed()],
AllMods = [?MODULE|ExtMods++Mods],
init_module_db_table(AllMods),
ExtMods ++ Mods.
get_modules_with_options(Modules) ->
lists:foldl(
fun(Mod, D) ->
case catch Mod:opt_type('') of
Opts when is_list(Opts) ->
lists:foldl(
fun(Opt, Acc) ->
dict:append(Opt, Mod, Acc)
end, D, Opts);
{'EXIT', {undef, _}} ->
case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of
true ->
try Mod:opt_type('') of
Opts when is_list(Opts) ->
lists:foldl(
fun(Opt, Acc) ->
dict:append(Opt, Mod, Acc)
end, D, Opts)
catch _:undef ->
D
end;
false ->
D
end
end, dict:new(), AllMods).
end, dict:new(), Modules).
validate_opts(#state{opts = Opts} = State) ->
ModOpts = get_modules_with_options(),
validate_opts(#state{opts = Opts} = State, ModOpts) ->
NewOpts = lists:filtermap(
fun(#local_config{key = {Opt, _Host}, value = Val} = In) ->
case dict:find(Opt, ModOpts) of
{ok, [Mod|_]} ->
VFun = Mod:opt_type(Opt),
try VFun(Val) of
_ ->
true
catch {replace_with, NewVal} ->
{true, In#local_config{value = NewVal}};
{invalid_syntax, Error} ->
NewVal ->
{true, In#local_config{value = NewVal}}
catch {invalid_syntax, Error} ->
?ERROR_MSG("ignoring option '~s' with "
"invalid value: ~p: ~s",
[Opt, Val, Error]),
@@ -1020,23 +1031,21 @@ validate_opts(#state{opts = Opts} = State) ->
%% Return the list of hosts with a given auth method
get_vh_by_auth_method(AuthMethod) ->
Cfgs = mnesia:dirty_match_object(local_config,
#local_config{key = {auth_method, '_'},
_ = '_'}),
lists:flatmap(
fun(#local_config{key = {auth_method, Host}, value = M}) ->
Methods = if not is_list(M) -> [M];
true -> M
end,
case lists:member(AuthMethod, Methods) of
true when Host == global ->
get_myhosts();
true ->
[Host];
false ->
[]
end
end, Cfgs).
Hosts = ejabberd_options:get_scope(auth_method),
get_vh_by_auth_method(AuthMethod, Hosts, []).
get_vh_by_auth_method(Method, [Host|Hosts], Result) ->
Methods = get_option({auth_method, Host}, []),
case lists:member(Method, Methods) of
true when Host == global ->
get_myhosts();
true ->
get_vh_by_auth_method(Method, Hosts, [Host|Result]);
false ->
get_vh_by_auth_method(Method, Hosts, Result)
end;
get_vh_by_auth_method(_, [], Result) ->
Result.
%% @spec (Path::string()) -> true | false
is_file_readable(Path) ->
@@ -1060,15 +1069,16 @@ get_version() ->
-spec get_myhosts() -> [binary()].
get_myhosts() ->
get_option(hosts, fun(V) -> V end).
get_option(hosts).
-spec get_mylang() -> binary().
get_mylang() ->
get_option(
language,
fun iolist_to_binary/1,
<<"en">>).
get_lang(global).
-spec get_lang(global | binary()) -> binary().
get_lang(Host) ->
get_option({language, Host}, <<"en">>).
replace_module(mod_announce_odbc) -> {mod_announce, sql};
replace_module(mod_blocking_odbc) -> {mod_blocking, sql};
@@ -1083,7 +1093,7 @@ replace_module(mod_roster_odbc) -> {mod_roster, sql};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, sql};
replace_module(mod_vcard_odbc) -> {mod_vcard, sql};
replace_module(mod_vcard_ldap) -> {mod_vcard, ldap};
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, sql};
replace_module(mod_vcard_xupdate_odbc) -> mod_vcard_xupdate;
replace_module(mod_pubsub_odbc) -> {mod_pubsub, sql};
replace_module(mod_http_bind) -> mod_bosh;
replace_module(Module) ->
@@ -1204,7 +1214,6 @@ transform_terms(Terms) ->
%% We could check all ejabberd beams, but this
%% slows down start-up procedure :(
Mods = [mod_register,
mod_last,
ejabberd_s2s,
ejabberd_listener,
ejabberd_sql_sup,
@@ -1313,6 +1322,10 @@ transform_options(Opt, Opts) when Opt == override_global;
Opt == override_acls ->
?WARNING_MSG("Ignoring '~s' option which has no effect anymore", [Opt]),
Opts;
transform_options({node_start, {_, _, _} = Now}, Opts) ->
?WARNING_MSG("Old 'node_start' format detected. This is still supported "
"but it is better to fix your config.", []),
[{node_start, now_to_seconds(Now)}|Opts];
transform_options({host_config, Host, HOpts}, Opts) ->
{AddOpts, HOpts1} =
lists:mapfoldl(
@@ -1336,94 +1349,6 @@ transform_options({include_config_file, _, _} = Opt, Opts) ->
transform_options(Opt, Opts) ->
[Opt|Opts].
-spec convert_table_to_binary(atom(), [atom()], atom(),
fun(), fun()) -> ok.
convert_table_to_binary(Tab, Fields, Type, DetectFun, ConvertFun) ->
case is_table_still_list(Tab, DetectFun) of
true ->
?INFO_MSG("Converting '~s' table from strings to binaries.", [Tab]),
TmpTab = list_to_atom(atom_to_list(Tab) ++ "_tmp_table"),
catch mnesia:delete_table(TmpTab),
case ejabberd_mnesia:create(?MODULE, TmpTab,
[{disc_only_copies, [node()]},
{type, Type},
{local_content, true},
{record_name, Tab},
{attributes, Fields}]) of
{atomic, ok} ->
mnesia:transform_table(Tab, ignore, Fields),
case mnesia:transaction(
fun() ->
mnesia:write_lock_table(TmpTab),
mnesia:foldl(
fun(R, _) ->
NewR = ConvertFun(R),
mnesia:dirty_write(TmpTab, NewR)
end, ok, Tab)
end) of
{atomic, ok} ->
mnesia:clear_table(Tab),
case mnesia:transaction(
fun() ->
mnesia:write_lock_table(Tab),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, TmpTab)
end) of
{atomic, ok} ->
mnesia:delete_table(TmpTab);
Err ->
report_and_stop(Tab, Err)
end;
Err ->
report_and_stop(Tab, Err)
end;
Err ->
report_and_stop(Tab, Err)
end;
false ->
ok
end.
is_table_still_list(Tab, DetectFun) ->
is_table_still_list(Tab, DetectFun, mnesia:dirty_first(Tab)).
is_table_still_list(_Tab, _DetectFun, '$end_of_table') ->
false;
is_table_still_list(Tab, DetectFun, Key) ->
Rs = mnesia:dirty_read(Tab, Key),
Res = lists:foldl(fun(_, true) ->
true;
(_, false) ->
false;
(R, _) ->
case DetectFun(R) of
'$next' ->
'$next';
El ->
is_list(El)
end
end, '$next', Rs),
case Res of
true ->
true;
false ->
false;
'$next' ->
is_table_still_list(Tab, DetectFun, mnesia:dirty_next(Tab, Key))
end.
report_and_stop(Tab, Err) ->
ErrTxt = lists:flatten(
io_lib:format(
"Failed to convert '~s' table to binary: ~p",
[Tab, Err])),
?CRITICAL_MSG(ErrTxt, []),
timer:sleep(1000),
halt(string:substr(ErrTxt, 1, 199)).
emit_deprecation_warning(Module, NewModule, DBType) ->
?WARNING_MSG("Module ~s is deprecated, use ~s with 'db_type: ~s'"
" instead", [Module, NewModule, DBType]).
@@ -1437,14 +1362,32 @@ emit_deprecation_warning(Module, NewModule) ->
[Module, NewModule])
end.
-spec now_to_seconds(erlang:timestamp()) -> non_neg_integer().
now_to_seconds({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
-spec opt_type(hide_sensitive_log_data) -> fun((boolean()) -> boolean());
(hosts) -> fun(([binary()]) -> [binary()]);
(language) -> fun((binary()) -> binary());
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
(default_db) -> fun((atom()) -> atom());
(default_ram_db) -> fun((atom()) -> atom());
(loglevel) -> fun((0..5) -> 0..5);
(queue_dir) -> fun((binary()) -> binary());
(queue_type) -> fun((ram | file) -> ram | file);
(use_cache) -> fun((boolean()) -> boolean());
(cache_size) -> fun((timeout()) -> timeout());
(cache_missed) -> fun((boolean()) -> boolean());
(cache_life_time) -> fun((timeout()) -> timeout());
(domain_certfile) -> fun((binary()) -> binary());
(shared_key) -> fun((binary()) -> binary());
(node_start) -> fun((non_neg_integer()) -> non_neg_integer());
(atom()) -> [atom()].
opt_type(hide_sensitive_log_data) ->
fun (H) when is_boolean(H) -> H end;
opt_type(hosts) ->
fun(L) when is_list(L) ->
lists:map(
fun(H) ->
iolist_to_binary(H)
end, L)
fun(L) ->
[iolist_to_binary(H) || H <- L]
end;
opt_type(language) ->
fun iolist_to_binary/1;
@@ -1460,18 +1403,35 @@ opt_type(queue_dir) ->
fun iolist_to_binary/1;
opt_type(queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(use_cache) ->
fun(B) when is_boolean(B) -> B end;
opt_type(cache_size) ->
fun(I) when is_integer(I), I>0 -> I;
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(cache_missed) ->
fun(B) when is_boolean(B) -> B end;
opt_type(cache_life_time) ->
fun(I) when is_integer(I), I>0 -> I;
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(domain_certfile) ->
fun misc:try_read_file/1;
opt_type(shared_key) ->
fun iolist_to_binary/1;
opt_type(node_start) ->
fun(I) when is_integer(I), I>=0 -> I end;
opt_type(_) ->
[hide_sensitive_log_data, hosts, language, max_fsm_queue,
default_db, default_ram_db, queue_type, queue_dir, loglevel].
default_db, default_ram_db, queue_type, queue_dir, loglevel,
use_cache, cache_size, cache_missed, cache_life_time,
domain_certfile, shared_key, node_start].
-spec may_hide_data(any()) -> any().
may_hide_data(Data) ->
case ejabberd_config:get_option(
hide_sensitive_log_data,
fun(false) -> false;
(true) -> true
end,
false) of
case get_option(hide_sensitive_log_data, false) of
false ->
Data;
true ->
@@ -1484,9 +1444,7 @@ fsm_limit_opts(Opts) ->
{_, I} when is_integer(I), I>0 ->
[{max_queue, I}];
false ->
case get_option(
max_fsm_queue,
fun(I) when is_integer(I), I>0 -> I end) of
case get_option(max_fsm_queue) of
undefined -> [];
N -> [{max_queue, N}]
end
@@ -1494,8 +1452,25 @@ fsm_limit_opts(Opts) ->
-spec queue_dir() -> binary() | undefined.
queue_dir() ->
get_option(queue_dir, opt_type(queue_dir)).
get_option(queue_dir).
-spec default_queue_type(binary()) -> ram | file.
default_queue_type(Host) ->
get_option({queue_type, Host}, opt_type(queue_type), ram).
get_option({queue_type, Host}, ram).
-spec use_cache(binary() | global) -> boolean().
use_cache(Host) ->
get_option({use_cache, Host}, true).
-spec cache_size(binary() | global) -> pos_integer() | infinity.
cache_size(Host) ->
get_option({cache_size, Host}, 1000).
-spec cache_missed(binary() | global) -> boolean().
cache_missed(Host) ->
get_option({cache_missed, Host}, true).
-spec cache_life_time(binary() | global) -> pos_integer() | infinity.
%% NOTE: the integer value returned is in *seconds*
cache_life_time(Host) ->
get_option({cache_life_time, Host}, 3600).
+3 -2
View File
@@ -289,8 +289,7 @@ process2(Args, AccessCommands, Auth, Version) ->
end.
get_accesscommands() ->
ejabberd_config:get_option(ejabberdctl_access_commands,
fun(V) when is_list(V) -> V end, []).
ejabberd_config:get_option(ejabberdctl_access_commands, []).
%%-----------------------------
%% Command calling
@@ -876,6 +875,8 @@ print(Format, Args) ->
%% ["aaaa bbb ccc"].
-spec opt_type(ejabberdctl_access_commands) -> fun((list()) -> list());
(atom()) -> [atom()].
opt_type(ejabberdctl_access_commands) ->
fun (V) when is_list(V) -> V end;
opt_type(_) -> [ejabberdctl_access_commands].
+44
View File
@@ -0,0 +1,44 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @doc
%%% This is a stub module which will be replaced during
%%% configuration load via p1_options:compile/1
%%% The only purpose of this file is to shut up xref/dialyzer
%%% @end
%%% Created : 27 Apr 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(ejabberd_db_modules).
%% API
-export([is_known/1, get_scope/1]).
%%%===================================================================
%%% API
%%%===================================================================
is_known(_) ->
false.
get_scope(_) ->
[].
%%%===================================================================
%%% Internal functions
%%%===================================================================
+80 -42
View File
@@ -32,7 +32,7 @@
%% External exports
-export([start/2, start_link/2, become_controller/1,
socket_type/0, receive_headers/1, url_encode/1,
transform_listen_option/2]).
transform_listen_option/2, listen_opt_type/1]).
-export([init/2, opt_type/1]).
@@ -100,23 +100,15 @@ init({SockMod, Socket}, Opts) ->
TLSOpts1 = lists:filter(fun ({certfile, _}) -> true;
({ciphers, _}) -> true;
({dhfile, _}) -> true;
({protocol_options, _}) -> true;
(_) -> false
end,
Opts),
TLSOpts2 = case lists:keysearch(protocol_options, 1, Opts) of
{value, {_, O}} ->
[_|ProtocolOptions] = lists:foldl(
fun(X, Acc) -> X ++ Acc end, [],
[["|" | binary_to_list(Opt)] || Opt <- O, is_binary(Opt)]
),
[{protocol_options, iolist_to_binary(ProtocolOptions)} | TLSOpts1];
_ -> TLSOpts1
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
false -> [compression_none | TLSOpts1];
true -> TLSOpts1
end,
TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
false -> [compression_none | TLSOpts2];
true -> TLSOpts2
end,
TLSOpts = [verify_none | TLSOpts3],
TLSOpts = [verify_none | TLSOpts2],
{SockMod1, Socket1} = if TLSEnabled ->
inet:setopts(Socket, [{recbuf, 8192}]),
{ok, TLSSocket} = fast_tls:tcp_to_tls(Socket,
@@ -144,33 +136,15 @@ init({SockMod, Socket}, Opts) ->
true -> [{[], ejabberd_xmlrpc}];
false -> []
end,
DefinedHandlers = gen_mod:get_opt(
request_handlers, Opts,
fun(Hs) ->
Hs1 = lists:map(fun
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
({Path, Mod}) -> {Path, Mod}
end, Hs),
Hs2 = [{str:tokens(
iolist_to_binary(Path), <<"/">>),
Mod} || {Path, Mod} <- Hs1],
[{Path,
case Mod of
mod_http_bind -> mod_bosh;
_ -> Mod
end} || {Path, Mod} <- Hs2]
end, []),
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
Admin ++ Bind ++ XMLRPC,
?DEBUG("S: ~p~n", [RequestHandlers]),
DefaultHost = gen_mod:get_opt(default_host, Opts, fun(A) -> A end, undefined),
DefaultHost = proplists:get_value(default_host, Opts),
{ok, RE} = re:compile(<<"^(?:\\[(.*?)\\]|(.*?))(?::(\\d+))?$">>),
CustomHeaders = gen_mod:get_opt(custom_headers, Opts,
fun expand_custom_headers/1,
[]),
CustomHeaders = proplists:get_value(custom_headers, Opts, []),
?INFO_MSG("started: ~p", [{SockMod1, Socket1}]),
State = #state{sockmod = SockMod1,
@@ -521,12 +495,7 @@ analyze_ip_xff(IP, [], _Host) -> IP;
analyze_ip_xff({IPLast, Port}, XFF, Host) ->
[ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++
[misc:ip_to_list(IPLast)],
TrustedProxies = ejabberd_config:get_option(
{trusted_proxies, Host},
fun(all) -> all;
(TPs) ->
[iolist_to_binary(TP) || TP <- TPs]
end, []),
TrustedProxies = ejabberd_config:get_option({trusted_proxies, Host}, []),
IPClient = case is_ipchain_trusted(ProxiesIPs,
TrustedProxies)
of
@@ -801,7 +770,9 @@ code_to_phrase(505) -> <<"HTTP Version Not Supported">>.
-spec parse_auth(binary()) -> {binary(), binary()} | {oauth, binary(), []} | undefined.
parse_auth(<<"Basic ", Auth64/binary>>) ->
Auth = misc:decode_base64(Auth64),
Auth = try base64:decode(Auth64)
catch _:badarg -> <<>>
end,
%% Auth should be a string with the format: user@server:password
%% Note that password can contain additional characters '@' and ':'
case str:chr(Auth, $:) of
@@ -930,7 +901,74 @@ transform_listen_option({request_handlers, Hs}, Opts) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
-spec opt_type(trusted_proxies) -> fun((all | [binary()]) -> all | [binary()]);
(atom()) -> [atom()].
opt_type(trusted_proxies) ->
fun (all) -> all;
(TPs) -> [iolist_to_binary(TP) || TP <- TPs] end;
opt_type(_) -> [trusted_proxies].
-spec listen_opt_type(tls) -> fun((boolean()) -> boolean());
(certfile) -> fun((binary()) -> binary());
(ciphers) -> fun((binary()) -> binary());
(dhfile) -> fun((binary()) -> binary());
(protocol_options) -> fun(([binary()]) -> binary());
(tls_compression) -> fun((boolean()) -> boolean());
(captcha) -> fun((boolean()) -> boolean());
(register) -> fun((boolean()) -> boolean());
(web_admin) -> fun((boolean()) -> boolean());
(http_bind) -> fun((boolean()) -> boolean());
(xmlrpc) -> fun((boolean()) -> boolean());
(request_handlers) -> fun(([{binary(), atom()}]) ->
[{binary(), atom()}]);
(default_host) -> fun((binary()) -> binary());
(custom_headers) -> fun(([{binary(), binary()}]) ->
[{binary(), binary()}]);
(atom()) -> [atom()].
listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(certfile) ->
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(ciphers) ->
fun iolist_to_binary/1;
listen_opt_type(dhfile) ->
fun misc:try_read_file/1;
listen_opt_type(protocol_options) ->
fun(Options) -> str:join(Options, <<"|">>) end;
listen_opt_type(tls_compression) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(captcha) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(register) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(web_admin) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(http_bind) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(xmlrpc) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(request_handlers) ->
fun(Hs) ->
Hs1 = lists:map(fun
({Mod, Path}) when is_atom(Mod) -> {Path, Mod};
({Path, Mod}) -> {Path, Mod}
end, Hs),
Hs2 = [{str:tokens(
iolist_to_binary(Path), <<"/">>),
Mod} || {Path, Mod} <- Hs1],
[{Path,
case Mod of
mod_http_bind -> mod_bosh;
_ -> Mod
end} || {Path, Mod} <- Hs2]
end;
listen_opt_type(default_host) ->
fun(A) -> A end;
listen_opt_type(custom_headers) ->
fun expand_custom_headers/1;
listen_opt_type(_) ->
%% TODO
fun(A) -> A end.
+16 -10
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,19 +75,25 @@
-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) ->
gen_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet}).
case catch p1_fsm:sync_send_all_state_event(FsmRef,
{send_xml, Packet},
15000)
of
{'EXIT', {timeout, _}} -> {error, timeout};
{'EXIT', _} -> {error, einval};
Res -> Res
end.
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.
@@ -99,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,
@@ -123,11 +129,9 @@ init([{#ws{ip = IP, http_opts = HOpts}, _} = WS]) ->
Opts = ejabberd_c2s_config:get_c2s_limits() ++ SOpts,
PingInterval = ejabberd_config:get_option(
{websocket_ping_interval, ?MYNAME},
fun(I) when is_integer(I), I>=0 -> I end,
?PING_INTERVAL) * 1000,
WSTimeout = ejabberd_config:get_option(
{websocket_timeout, ?MYNAME},
fun(I) when is_integer(I), I>0 -> I end,
?WEBSOCKET_TIMEOUT) * 1000,
Socket = {http_ws, self(), IP},
?DEBUG("Client connected through websocket ~p",
@@ -237,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) ->
@@ -249,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) ->
+64 -112
View File
@@ -49,24 +49,19 @@ init(_) ->
{ok, {{one_for_one, 10, 1}, listeners_childspec()}}.
listeners_childspec() ->
case ejabberd_config:get_option(listen, fun validate_cfg/1) of
undefined ->
[];
Ls ->
Specs = lists:map(
fun({Port, Module, Opts}) ->
maybe_start_sip(Module),
ets:insert(?MODULE, {Port, Module, Opts}),
{Port,
{?MODULE, start, [Port, Module, Opts]},
transient,
brutal_kill,
worker,
[?MODULE]}
end, Ls),
report_duplicated_portips(Ls),
Specs
end.
Ls = ejabberd_config:get_option(listen, []),
Specs = lists:map(
fun({Port, Module, Opts}) ->
ets:insert(?MODULE, {Port, Module, Opts}),
{Port,
{?MODULE, start, [Port, Module, Opts]},
transient,
brutal_kill,
worker,
[?MODULE]}
end, Ls),
report_duplicated_portips(Ls),
Specs.
start_listeners() ->
lists:foreach(
@@ -86,22 +81,16 @@ report_duplicated_portips(L) ->
end.
start(Port, Module, Opts) ->
NewOpts = validate_module_options(Module, Opts),
%% Check if the module is an ejabberd listener or an independent listener
case Module:socket_type() of
independent -> Module:start_listener(Port, Opts);
_ -> start_dependent(Port, Module, Opts)
independent -> Module:start_listener(Port, NewOpts);
_ -> start_dependent(Port, Module, NewOpts)
end.
%% @spec(Port, Module, Opts) -> {ok, Pid} | {error, ErrorMessage}
start_dependent(Port, Module, Opts) ->
try check_listener_options(Opts) of
ok ->
proc_lib:start_link(?MODULE, init, [Port, Module, Opts])
catch
throw:{error, Error} ->
?ERROR_MSG(Error, []),
{error, Error}
end.
proc_lib:start_link(?MODULE, init, [Port, Module, Opts]).
init(PortIP, Module, RawOpts) ->
{Port, IPT, IPS, IPV, Proto, OptsClean} = parse_listener_portip(PortIP, RawOpts),
@@ -120,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]),
@@ -145,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]),
@@ -313,7 +304,7 @@ accept(ListenSocket, Module, Opts, Interval) ->
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
PPort, inet_parse:ntoa(Addr), Port]);
_ ->
ok
gen_tcp:close(Socket)
end,
accept(ListenSocket, Module, Opts, NewInterval);
{error, Reason} ->
@@ -360,7 +351,6 @@ start_listener2(Port, Module, Opts) ->
%% It is only required to start the supervisor in some cases.
%% But it doesn't hurt to attempt to start it for any listener.
%% So, it's normal (and harmless) that in most cases this call returns: {error, {already_started, pid()}}
maybe_start_sip(Module),
start_listener_sup(Port, Module, Opts).
start_module_sup(_Port, Module) ->
@@ -384,7 +374,7 @@ start_listener_sup(Port, Module, Opts) ->
supervisor:start_child(?MODULE, ChildSpec).
stop_listeners() ->
Ports = ejabberd_config:get_option(listen, fun validate_cfg/1),
Ports = ejabberd_config:get_option(listen, []),
lists:foreach(
fun({PortIpNetp, Module, _Opts}) ->
delete_listener(PortIpNetp, Module)
@@ -408,17 +398,6 @@ add_listener(PortIP, Module, Opts) ->
PortIP1 = {Port, IPT, Proto},
case start_listener(PortIP1, Module, Opts) of
{ok, _Pid} ->
Ports = case ejabberd_config:get_option(
listen, fun validate_cfg/1) of
undefined ->
[];
Ls ->
Ls
end,
Ports1 = lists:keydelete(PortIP1, 1, Ports),
Ports2 = [{PortIP1, Module, Opts} | Ports1],
Ports3 = lists:map(fun transform_option/1, Ports2),
ejabberd_config:add_option(listen, Ports3),
ok;
{error, {already_started, _Pid}} ->
{error, {already_started, PortIP}};
@@ -440,29 +419,10 @@ delete_listener(PortIP, Module) ->
delete_listener(PortIP, Module, Opts) ->
{Port, IPT, _, _, Proto, _} = parse_listener_portip(PortIP, Opts),
PortIP1 = {Port, IPT, Proto},
Ports = case ejabberd_config:get_option(
listen, fun validate_cfg/1) of
undefined ->
[];
Ls ->
Ls
end,
Ports1 = lists:keydelete(PortIP1, 1, Ports),
Ports2 = lists:map(fun transform_option/1, Ports1),
ejabberd_config:add_option(listen, Ports2),
stop_listener(PortIP1, Module).
maybe_start_sip(esip_socket) ->
ejabberd:start_app(esip);
maybe_start_sip(_) ->
ok.
config_reloaded() ->
New = case ejabberd_config:get_option(listen, fun validate_cfg/1) of
undefined -> [];
Ls -> Ls
end,
New = ejabberd_config:get_option(listen, []),
Old = ets:tab2list(?MODULE),
lists:foreach(
fun({PortIP, Module, _Opts}) ->
@@ -491,48 +451,6 @@ config_reloaded() ->
%%%
%%% Check options
%%%
check_listener_options(Opts) ->
case includes_deprecated_ssl_option(Opts) of
false -> ok;
true ->
Error = "There is a problem with your ejabberd configuration file: "
"the option 'ssl' for listening sockets is no longer available."
" To get SSL encryption use the option 'tls'.",
throw({error, Error})
end,
case certfile_readable(Opts) of
true -> ok;
{false, Path} ->
ErrorText = "There is a problem in the configuration: "
"the specified file is not readable: ",
throw({error, ErrorText ++ Path})
end,
ok.
%% Parse the options of the socket,
%% and return if the deprecated option 'ssl' is included
%% @spec (Opts) -> true | false
includes_deprecated_ssl_option(Opts) ->
case lists:keysearch(ssl, 1, Opts) of
{value, {ssl, _SSLOpts}} ->
true;
_ ->
lists:member(ssl, Opts)
end.
%% @spec (Opts) -> true | {false, Path::string()}
certfile_readable(Opts) ->
case proplists:lookup(certfile, Opts) of
none -> true;
{certfile, Path} ->
PathS = binary_to_list(Path),
case ejabberd_config:is_file_readable(PathS) of
true -> true;
false -> {false, PathS}
end
end.
get_proto(Opts) ->
case proplists:get_value(proto, Opts) of
undefined ->
@@ -654,6 +572,47 @@ transform_options({listen, LOpts}, Opts) ->
transform_options(Opt, Opts) ->
[Opt|Opts].
-spec validate_module_options(module(), [{atom(), any()}]) -> [{atom(), any()}].
validate_module_options(Module, Opts) ->
try Module:listen_opt_type('') of
_ ->
lists:filtermap(
fun({Opt, Val}) ->
case validate_module_option(Module, Opt, Val) of
{ok, NewVal} -> {true, {Opt, NewVal}};
error -> false
end
end, Opts)
catch _:undef ->
?WARNING_MSG("module '~s' doesn't export listen_opt_type/1",
[Module]),
Opts
end.
-spec validate_module_option(module(), atom(), any()) -> {ok, any()} | error.
validate_module_option(Module, Opt, Val) ->
case Module:listen_opt_type(Opt) of
VFun when is_function(VFun) ->
try VFun(Val) of
NewVal -> {ok, NewVal}
catch {invalid_syntax, Error} ->
?ERROR_MSG("ignoring listen option '~s' with "
"invalid value: ~p: ~s",
[Opt, Val, Error]),
error;
_:_ ->
?ERROR_MSG("ignoring listen option '~s' with "
"invalid value: ~p",
[Opt, Val]),
error
end;
KnownOpts when is_list(KnownOpts) ->
?ERROR_MSG("unknown listen option '~s' for '~s' will be likely "
"ignored, available options are: ~s",
[Opt, Module, misc:join_atoms(KnownOpts, <<", ">>)]),
{ok, Val}
end.
-type transport() :: udp | tcp.
-type port_ip_transport() :: inet:port_number() |
{inet:port_number(), transport()} |
@@ -675,7 +634,7 @@ validate_cfg(L) ->
true = ?IS_TRANSPORT(T),
{{Port, IP, T}, Mod, Opts};
({module, Mod}, {Port, _, Opts}) ->
{Port, prepare_mod(Mod), Opts};
{Port, Mod, Opts};
(Opt, {Port, Mod, Opts}) ->
{Port, Mod, [Opt|Opts]}
end, {{5222, all_zero_ip(LOpts), tcp}, ejabberd_c2s, []}, LOpts)
@@ -694,13 +653,6 @@ prepare_ip(IP) when is_list(IP) ->
prepare_ip(IP) when is_binary(IP) ->
prepare_ip(binary_to_list(IP)).
prepare_mod(ejabberd_sip) ->
prepare_mod(sip);
prepare_mod(sip) ->
esip_socket;
prepare_mod(Mod) when is_atom(Mod) ->
Mod.
all_zero_ip(Opts) ->
case proplists:get_bool(inet6, Opts) of
true -> {0,0,0,0,0,0,0,0};
-1
View File
@@ -207,7 +207,6 @@ init([]) ->
ejabberd_mnesia:create(?MODULE, iq_response,
[{ram_copies, [node()]},
{attributes, record_info(fields, iq_response)}]),
mnesia:add_table_copy(iq_response, node(), ram_copies),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
+4 -1
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
@@ -160,7 +163,7 @@ reopen_log() ->
%% @spec () -> ok
rotate_log() ->
lager_crash_log ! rotate,
catch lager_crash_log ! rotate,
lists:foreach(
fun({lager_file_backend, File}) ->
whereis(lager_event) ! {rotate, File};
+380 -96
View File
@@ -30,130 +30,281 @@
-module(ejabberd_mnesia).
-author('christophe.romain@process-one.net').
-export([create/3, reset/2, update/2]).
-behaviour(gen_server).
-export([start/0, create/3, update/2, transform/2, transform/3,
dump_schema/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(STORAGE_TYPES, [disc_copies, disc_only_copies, ram_copies]).
-define(NEED_RESET, [local_content, type]).
-include("logger.hrl").
create(Module, Name, TabDef)
when is_atom(Module), is_atom(Name), is_list(TabDef) ->
Path = os:getenv("EJABBERD_SCHEMA_PATH"),
Schema = schema(Path, Module, Name, TabDef),
-record(state, {tables = #{} :: map(),
schema = [] :: [{atom(), [{atom(), any()}]}]}).
start() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec create(module(), atom(), list()) -> any().
create(Module, Name, TabDef) ->
gen_server:call(?MODULE, {create, Module, Name, TabDef},
%% Huge timeout is need to have enough
%% time to transform huge tables
timer:minutes(30)).
init([]) ->
ejabberd_config:env_binary_to_list(mnesia, dir),
MyNode = node(),
DbNodes = mnesia:system_info(db_nodes),
case lists:member(MyNode, DbNodes) of
true ->
case mnesia:system_info(extra_db_nodes) of
[] -> mnesia:create_schema([node()]);
_ -> ok
end,
ejabberd:start_app(mnesia, permanent),
?DEBUG("Waiting for Mnesia tables synchronization...", []),
mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity),
Schema = read_schema_file(),
{ok, #state{schema = Schema}};
false ->
?CRITICAL_MSG("Node name mismatch: I'm [~s], "
"the database is owned by ~p", [MyNode, DbNodes]),
?CRITICAL_MSG("Either set ERLANG_NODE in ejabberdctl.cfg "
"or change node name in Mnesia", []),
{stop, node_name_mismatch}
end.
handle_call({create, Module, Name, TabDef}, _From, State) ->
case maps:get(Name, State#state.tables, undefined) of
{TabDef, Result} ->
{reply, Result, State};
_ ->
Result = do_create(Module, Name, TabDef, State#state.schema),
Tables = maps:put(Name, {TabDef, Result}, State#state.tables),
{reply, Result, State#state{tables = Tables}}
end;
handle_call(_Request, _From, State) ->
{noreply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
do_create(Module, Name, TabDef, TabDefs) ->
code:ensure_loaded(Module),
Schema = schema(Name, TabDef, TabDefs),
{attributes, Attrs} = lists:keyfind(attributes, 1, Schema),
case catch mnesia:table_info(Name, attributes) of
{'EXIT', _} ->
mnesia_op(create_table, [Name, TabDef]);
create(Name, TabDef);
Attrs ->
case need_reset(Name, Schema) of
true -> reset(Name, Schema);
false -> update(Name, Attrs, Schema)
true ->
reset(Name, Schema);
false ->
case update(Name, Attrs, Schema) of
{atomic, ok} ->
transform(Module, Name, Attrs, Attrs);
Err ->
Err
end
end;
OldAttrs ->
Fun = case lists:member({transform,1}, Module:module_info(exports)) of
true -> fun(Old) -> Module:transform(Old) end;
false -> fun(Old) -> transform(OldAttrs, Attrs, Old) end
end,
mnesia_op(transform_table, [Name, Fun, Attrs])
transform(Module, Name, OldAttrs, Attrs)
end.
reset(Name, TabDef)
when is_atom(Name), is_list(TabDef) ->
reset(Name, TabDef) ->
?INFO_MSG("Deleting Mnesia table '~s'", [Name]),
mnesia_op(delete_table, [Name]),
mnesia_op(create_table, [Name, TabDef]).
create(Name, TabDef).
update(Name, TabDef)
when is_atom(Name), is_list(TabDef) ->
update(Name, TabDef) ->
{attributes, Attrs} = lists:keyfind(attributes, 1, TabDef),
update(Name, Attrs, TabDef).
update(Name, Attrs, TabDef) ->
Storage = case catch mnesia:table_info(Name, storage_type) of
{'EXIT', _} -> unknown;
Type -> Type
end,
NewStorage = lists:foldl(
fun({Key, _}, Acc) ->
case lists:member(Key, ?STORAGE_TYPES) of
true -> Key;
false -> Acc
end
end, Storage, TabDef),
R1 = [mnesia_op(change_table_copy_type, [Name, node(), NewStorage])
|| Storage=/=NewStorage],
CurIndexes = [lists:nth(N-1, Attrs) || N<-mnesia:table_info(Name, index)],
NewIndexes = proplists:get_value(index, TabDef, []),
R2 = [mnesia_op(del_table_index, [Name, Attr])
|| Attr <- CurIndexes--NewIndexes],
R3 = [mnesia_op(add_table_index, [Name, Attr])
|| Attr <- NewIndexes--CurIndexes],
lists:foldl(
fun({atomic, ok}, Acc) -> Acc;
(Error, _Acc) -> Error
end, {atomic, ok}, R1++R2++R3).
case change_table_copy_type(Name, TabDef) of
{atomic, ok} ->
CurrIndexes = [lists:nth(N-1, Attrs) ||
N <- mnesia:table_info(Name, index)],
NewIndexes = proplists:get_value(index, TabDef, []),
case delete_indexes(Name, CurrIndexes -- NewIndexes) of
{atomic, ok} ->
add_indexes(Name, NewIndexes -- CurrIndexes);
Err ->
Err
end;
Err ->
Err
end.
change_table_copy_type(Name, TabDef) ->
CurrType = mnesia:table_info(Name, storage_type),
NewType = case lists:filter(fun is_storage_type_option/1, TabDef) of
[{Type, _}|_] -> Type;
[] -> CurrType
end,
if NewType /= CurrType ->
?INFO_MSG("Changing Mnesia table '~s' from ~s to ~s",
[Name, CurrType, NewType]),
mnesia_op(change_table_copy_type, [Name, node(), NewType]);
true ->
{atomic, ok}
end.
delete_indexes(Name, [Index|Indexes]) ->
?INFO_MSG("Deleting index '~s' from Mnesia table '~s'", [Index, Name]),
case mnesia_op(del_table_index, [Name, Index]) of
{atomic, ok} ->
delete_indexes(Name, Indexes);
Err ->
Err
end;
delete_indexes(_Name, []) ->
{atomic, ok}.
add_indexes(Name, [Index|Indexes]) ->
?INFO_MSG("Adding index '~s' to Mnesia table '~s'", [Index, Name]),
case mnesia_op(add_table_index, [Name, Index]) of
{atomic, ok} ->
add_indexes(Name, Indexes);
Err ->
Err
end;
add_indexes(_Name, []) ->
{atomic, ok}.
%
% utilities
%
schema(false, Module, _Name, TabDef) ->
?DEBUG("No custom ~s schema path", [Module]),
TabDef;
schema(Path, Module, Name, TabDef) ->
File = filename:join(Path, atom_to_list(Module)++".mnesia"),
case parse(File) of
{ok, CustomDefs} ->
case lists:keyfind(Name, 1, CustomDefs) of
{Name, CustomDef} ->
?INFO_MSG("Using custom ~s schema for table ~s",
[Module, Name]),
merge(TabDef, CustomDef);
_ ->
TabDef
end;
schema(Name, Default, Schema) ->
case lists:keyfind(Name, 1, Schema) of
{_, Custom} ->
TabDefs = merge(Custom, Default),
?DEBUG("Using custom schema for table '~s': ~p",
[Name, TabDefs]),
TabDefs;
false ->
?DEBUG("No custom Mnesia schema for table '~s' found",
[Name]),
Default
end.
read_schema_file() ->
File = schema_path(),
case fast_yaml:decode_from_file(File, [plain_as_atom]) of
{ok, [Defs|_]} ->
?INFO_MSG("Using custom Mnesia schema from ~s", [File]),
lists:flatmap(
fun({Tab, Opts}) ->
case validate_schema_opts(File, Opts) of
{ok, NewOpts} ->
[{Tab, lists:ukeysort(1, NewOpts)}];
error ->
[]
end
end, Defs);
{ok, []} ->
?WARNING_MSG("Mnesia schema file ~s is empty", [File]),
[];
{error, enoent} ->
?DEBUG("No custom ~s schema path", [Module]),
TabDef;
{error, Error} ->
?ERROR_MSG("Can not use custom ~s schema for table ~s: ~p",
[Module, Name, Error]),
TabDef
?DEBUG("No custom Mnesia schema file found", []),
[];
{error, Reason} ->
?ERROR_MSG("Failed to read Mnesia schema file ~s: ~s",
[File, fast_yaml:format_error(Reason)]),
[]
end.
merge(TabDef, CustomDef) ->
{CustomKeys, _} = lists:unzip(CustomDef),
CleanDef = lists:foldl(
fun(Elem, Acc) ->
case lists:member(Elem, ?STORAGE_TYPES) of
true ->
lists:foldl(
fun(Key, CleanAcc) ->
lists:keydelete(Key, 1, CleanAcc)
end, Acc, ?STORAGE_TYPES);
false ->
Acc
end
end, TabDef, CustomKeys),
lists:ukeymerge(1,
lists:ukeysort(1, CustomDef),
lists:ukeysort(1, CleanDef)).
parse(File) ->
case file:consult(File) of
{ok, Terms} -> parse(Terms, []);
Error -> Error
validate_schema_opts(File, Opts) ->
try {ok, lists:map(
fun({storage_type, Type}) when Type == ram_copies;
Type == disc_copies;
Type == disc_only_copies ->
{Type, [node()]};
({storage_type, _} = Opt) ->
erlang:error({invalid_value, Opt});
({local_content, Bool}) when is_boolean(Bool) ->
{local_content, Bool};
({local_content, _} = Opt) ->
erlang:error({invalid_value, Opt});
({type, Type}) when Type == set;
Type == ordered_set;
Type == bag ->
{type, Type};
({type, _} = Opt) ->
erlang:error({invalid_value, Opt});
({attributes, Attrs} = Opt) ->
try lists:all(fun is_atom/1, Attrs) of
true -> {attributes, Attrs};
false -> erlang:error({invalid_value, Opt})
catch _:_ -> erlang:error({invalid_value, Opt})
end;
({index, Indexes} = Opt) ->
try lists:all(fun is_atom/1, Indexes) of
true -> {index, Indexes};
false -> erlang:error({invalid_value, Opt})
catch _:_ -> erlang:error({invalid_value, Opt})
end;
(Opt) ->
erlang:error({unknown_option, Opt})
end, Opts)}
catch _:{invalid_value, {Opt, Val}} ->
?ERROR_MSG("Mnesia schema ~s is incorrect: invalid value ~p of "
"option '~s'", [File, Val, Opt]),
error;
_:{unknown_option, Opt} ->
?ERROR_MSG("Mnesia schema ~s is incorrect: unknown option ~p",
[File, Opt]),
error
end.
parse([], Acc) ->
{ok, lists:reverse(Acc)};
parse([{Name, Storage, TabDef}|Tail], Acc)
when is_atom(Name), is_atom(Storage), is_list(TabDef) ->
NewDef = case lists:member(Storage, ?STORAGE_TYPES) of
true -> [{Storage, [node()]} | TabDef];
false -> TabDef
end,
parse(Tail, [{Name, NewDef} | Acc]);
parse([Other|_], _) ->
{error, {invalid, Other}}.
create(Name, TabDef) ->
?INFO_MSG("Creating Mnesia table '~s'", [Name]),
case mnesia_op(create_table, [Name, TabDef]) of
{atomic, ok} ->
add_table_copy(Name);
Err ->
Err
end.
%% The table MUST exist, otherwise the function would fail
add_table_copy(Name) ->
Type = mnesia:table_info(Name, storage_type),
Nodes = mnesia:table_info(Name, Type),
case lists:member(node(), Nodes) of
true ->
{atomic, ok};
false ->
mnesia_op(add_table_copy, [Name, node(), Type])
end.
merge(Custom, Default) ->
NewDefault = case lists:any(fun is_storage_type_option/1, Custom) of
true ->
lists:filter(
fun(O) ->
not is_storage_type_option(O)
end, Default);
false ->
Default
end,
lists:ukeymerge(1, Custom, lists:ukeysort(1, NewDefault)).
need_reset(Table, TabDef) ->
ValuesF = [mnesia:table_info(Table, Key) || Key <- ?NEED_RESET],
@@ -164,7 +315,61 @@ need_reset(Table, TabDef) ->
({_, _}, _) -> true
end, false, lists:zip(ValuesF, ValuesT)).
transform(OldAttrs, Attrs, Old) ->
transform(Module, Name) ->
try mnesia:table_info(Name, attributes) of
Attrs ->
transform(Module, Name, Attrs, Attrs)
catch _:{aborted, _} = Err ->
Err
end.
transform(Module, Name, NewAttrs) ->
try mnesia:table_info(Name, attributes) of
OldAttrs ->
transform(Module, Name, OldAttrs, NewAttrs)
catch _:{aborted, _} = Err ->
Err
end.
transform(Module, Name, Attrs, Attrs) ->
case need_transform(Module, Name) of
true ->
?INFO_MSG("Transforming table '~s', this may take a while", [Name]),
transform_table(Module, Name);
false ->
{atomic, ok}
end;
transform(Module, Name, OldAttrs, NewAttrs) ->
Fun = case erlang:function_exported(Module, transform, 1) of
true -> transform_fun(Module, Name);
false -> fun(Old) -> do_transform(OldAttrs, NewAttrs, Old) end
end,
mnesia_op(transform_table, [Name, Fun, NewAttrs]).
-spec need_transform(module(), atom()) -> boolean().
need_transform(Module, Name) ->
case erlang:function_exported(Module, need_transform, 1) of
true ->
do_need_transform(Module, Name, mnesia:dirty_first(Name));
false ->
false
end.
do_need_transform(_Module, _Name, '$end_of_table') ->
false;
do_need_transform(Module, Name, Key) ->
Objs = mnesia:dirty_read(Name, Key),
case lists:foldl(
fun(_, true) -> true;
(Obj, _) -> Module:need_transform(Obj)
end, undefined, Objs) of
true -> true;
false -> false;
_ ->
do_need_transform(Module, Name, mnesia:dirty_next(Name, Key))
end.
do_transform(OldAttrs, Attrs, Old) ->
[Name|OldValues] = tuple_to_list(Old),
Before = lists:zip(OldAttrs, OldValues),
After = lists:foldl(
@@ -177,6 +382,56 @@ transform(OldAttrs, Attrs, Old) ->
{Attrs, NewRecord} = lists:unzip(After),
list_to_tuple([Name|NewRecord]).
transform_fun(Module, Name) ->
fun(Obj) ->
try Module:transform(Obj)
catch E:R ->
StackTrace = erlang:get_stacktrace(),
?ERROR_MSG("Failed to transform Mnesia table ~s:~n"
"** Record: ~p~n"
"** Reason: ~p~n"
"** StackTrace: ~p",
[Name, Obj, R, StackTrace]),
erlang:raise(E, R, StackTrace)
end
end.
transform_table(Module, Name) ->
Type = mnesia:table_info(Name, type),
Attrs = mnesia:table_info(Name, attributes),
TmpTab = list_to_atom(atom_to_list(Name) ++ "_backup"),
StorageType = if Type == ordered_set -> disc_copies;
true -> disc_only_copies
end,
mnesia:create_table(TmpTab,
[{StorageType, [node()]},
{type, Type},
{local_content, true},
{record_name, Name},
{attributes, Attrs}]),
mnesia:clear_table(TmpTab),
Fun = transform_fun(Module, Name),
Res = mnesia_op(
transaction,
[fun() -> do_transform_table(Name, Fun, TmpTab, mnesia:first(Name)) end]),
mnesia:delete_table(TmpTab),
Res.
do_transform_table(Name, _Fun, TmpTab, '$end_of_table') ->
mnesia:foldl(
fun(Obj, _) ->
mnesia:write(Name, Obj, write)
end, ok, TmpTab);
do_transform_table(Name, Fun, TmpTab, Key) ->
Next = mnesia:next(Name, Key),
Objs = mnesia:read(Name, Key),
lists:foreach(
fun(Obj) ->
mnesia:write(TmpTab, Fun(Obj), write),
mnesia:delete_object(Obj)
end, Objs),
do_transform_table(Name, Fun, TmpTab, Next).
mnesia_op(Fun, Args) ->
case apply(mnesia, Fun, Args) of
{atomic, ok} ->
@@ -186,3 +441,32 @@ mnesia_op(Fun, Args) ->
[Fun, Args, Other]),
Other
end.
schema_path() ->
Dir = case os:getenv("EJABBERD_MNESIA_SCHEMA") of
false -> mnesia:system_info(directory);
Path -> Path
end,
filename:join(Dir, "ejabberd.schema").
is_storage_type_option({O, _}) ->
O == ram_copies orelse O == disc_copies orelse O == disc_only_copies.
dump_schema() ->
File = schema_path(),
Schema = lists:flatmap(
fun(schema) ->
[];
(Tab) ->
[{Tab, [{storage_type,
mnesia:table_info(Tab, storage_type)},
{local_content,
mnesia:table_info(Tab, local_content)}]}]
end, mnesia:system_info(tables)),
case file:write_file(File, [fast_yaml:encode(Schema), io_lib:nl()]) of
ok ->
io:format("Mnesia schema is written to ~s~n", [File]);
{error, Reason} ->
io:format("Failed to write Mnesia schema to ~s: ~s",
[File, file:format_error(Reason)])
end.
+88 -68
View File
File diff suppressed because one or more lines are too long
+16 -5
View File
@@ -25,31 +25,42 @@
%%%-------------------------------------------------------------------
-module(ejabberd_oauth_mnesia).
-behaviour(ejabberd_oauth).
-export([init/0,
store/1,
lookup/1,
clean/1]).
clean/1,
use_cache/0]).
-include("ejabberd_oauth.hrl").
init() ->
ejabberd_mnesia:create(?MODULE, oauth_token,
[{disc_copies, [node()]},
[{disc_only_copies, [node()]},
{attributes,
record_info(fields, oauth_token)}]),
mnesia:add_table_copy(oauth_token, node(), disc_copies),
ok.
use_cache() ->
case mnesia:table_info(oauth_token, storage_type) of
disc_only_copies ->
ejabberd_config:get_option(
oauth_use_cache,
ejabberd_config:use_cache(global));
_ ->
false
end.
store(R) ->
mnesia:dirty_write(R).
lookup(Token) ->
case catch mnesia:dirty_read(oauth_token, Token) of
[R] ->
R;
{ok, R};
_ ->
false
error
end.
clean(TS) ->
+12 -10
View File
@@ -25,6 +25,8 @@
%%%-------------------------------------------------------------------
-module(ejabberd_oauth_rest).
-behaviour(ejabberd_oauth).
-behaviour(ejabberd_config).
-export([init/0,
store/1,
@@ -58,7 +60,7 @@ store(R) ->
ok;
Err ->
?ERROR_MSG("failed to store oauth record ~p: ~p", [R, Err]),
{error, Err}
{error, db_failure}
end.
lookup(Token) ->
@@ -72,27 +74,27 @@ lookup(Token) ->
US = {JID#jid.luser, JID#jid.lserver},
Scope = proplists:get_value(<<"scope">>, Data, []),
Expire = proplists:get_value(<<"expire">>, Data, 0),
#oauth_token{token = Token,
us = US,
scope = Scope,
expire = Expire};
{ok, #oauth_token{token = Token,
us = US,
scope = Scope,
expire = Expire}};
{ok, 404, _Resp} ->
false;
error;
Other ->
?ERROR_MSG("Unexpected response for oauth lookup: ~p", [Other]),
{error, rest_failed}
error
end.
clean(_TS) ->
ok.
path(Path) ->
Base = ejabberd_config:get_option(ext_api_path_oauth,
fun(X) -> iolist_to_binary(X) end,
<<"/oauth">>),
Base = ejabberd_config:get_option(ext_api_path_oauth, <<"/oauth">>),
<<Base/binary, "/", Path/binary>>.
-spec opt_type(ext_api_path_oauth) -> fun((binary()) -> binary());
(atom()) -> [atom()].
opt_type(ext_api_path_oauth) ->
fun (X) -> iolist_to_binary(X) end;
opt_type(_) -> [ext_api_path_oauth].
+21 -13
View File
@@ -25,7 +25,7 @@
%%%-------------------------------------------------------------------
-module(ejabberd_oauth_sql).
-behaviour(ejabberd_oauth).
-compile([{parse_transform, ejabberd_sql_pt}]).
-export([init/0,
@@ -37,6 +37,7 @@
-include("ejabberd.hrl").
-include("ejabberd_sql_pt.hrl").
-include("jid.hrl").
-include("logger.hrl").
init() ->
ok.
@@ -47,13 +48,20 @@ store(R) ->
SJID = jid:encode({User, Server, <<"">>}),
Scope = str:join(R#oauth_token.scope, <<" ">>),
Expire = R#oauth_token.expire,
?SQL_UPSERT(
?MYNAME,
"oauth_token",
["!token=%(Token)s",
"jid=%(SJID)s",
"scope=%(Scope)s",
"expire=%(Expire)d"]).
case ?SQL_UPSERT(
?MYNAME,
"oauth_token",
["!token=%(Token)s",
"jid=%(SJID)s",
"scope=%(Scope)s",
"expire=%(Expire)d"]) of
ok ->
ok;
Err ->
?ERROR_MSG("Failed to write to SQL 'oauth_token' table: ~p",
[Err]),
{error, db_failure}
end.
lookup(Token) ->
case ejabberd_sql:sql_query(
@@ -63,12 +71,12 @@ lookup(Token) ->
{selected, [{SJID, Scope, Expire}]} ->
JID = jid:decode(SJID),
US = {JID#jid.luser, JID#jid.lserver},
#oauth_token{token = Token,
us = US,
scope = str:tokens(Scope, <<" ">>),
expire = Expire};
{ok, #oauth_token{token = Token,
us = US,
scope = str:tokens(Scope, <<" ">>),
expire = Expire}};
_ ->
false
error
end.
clean(TS) ->
+44
View File
@@ -0,0 +1,44 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% @doc
%%% This is a stub module which will be replaced during
%%% configuration load via p1_options:compile/1
%%% The only purpose of this file is to shut up xref/dialyzer
%%% @end
%%% Created : 16 Apr 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(ejabberd_options).
%% API
-export([is_known/1, get_scope/1]).
%%%===================================================================
%%% API
%%%===================================================================
is_known(_) ->
false.
get_scope(_) ->
[].
%%%===================================================================
%%% Internal functions
%%%===================================================================
+6 -13
View File
@@ -34,12 +34,9 @@
-module(ejabberd_piefxis).
-behaviour(ejabberd_config).
-protocol({xep, 227, '1.0'}).
-export([import_file/1, export_server/1, export_host/2,
opt_type/1]).
-export([import_file/1, export_server/1, export_host/2]).
-define(CHUNK_SIZE, 1024*20). %20k
@@ -138,7 +135,7 @@ export_host(Dir, FnH, Host) ->
{ok, Fd} ->
print(Fd, make_piefxis_xml_head()),
print(Fd, make_piefxis_host_head(Host)),
Users = ejabberd_auth:get_vh_registered_users(Host),
Users = ejabberd_auth:get_users(Host),
case export_users(Users, Host, Fd) of
ok ->
print(Fd, make_piefxis_host_tail()),
@@ -169,7 +166,7 @@ export_users([], _Server, _Fd) ->
export_user(User, Server, Fd) ->
Password = ejabberd_auth:get_password_s(User, Server),
LServer = jid:nameprep(Server),
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
PasswordFormat = ejabberd_auth:password_format(LServer),
Pass = case Password of
{_,_,_,_} ->
case PasswordFormat of
@@ -389,7 +386,7 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
#state{server = LServer} = State) ->
Name = fxml:get_attr_s(<<"name">>, Attrs),
Password = fxml:get_attr_s(<<"password">>, Attrs),
PasswordFormat = ejabberd_config:get_option({auth_password_format, LServer}, fun(X) -> X end, plain),
PasswordFormat = ejabberd_auth:password_format(LServer),
Pass = case PasswordFormat of
scram ->
case Password of
@@ -405,9 +402,9 @@ process_user(#xmlel{name = <<"user">>, attrs = Attrs, children = Els},
stop("Invalid 'user': ~s", [Name]);
LUser ->
case ejabberd_auth:try_register(LUser, LServer, Pass) of
{atomic, _} ->
ok ->
process_user_els(Els, State#state{user = LUser});
Err ->
{error, Err} ->
stop("Failed to create user '~s': ~p", [Name, Err])
end
end.
@@ -596,7 +593,3 @@ make_xinclude(Fn) ->
print(Fd, String) ->
file:write(Fd, String).
opt_type(auth_password_format) -> fun (X) -> X end;
opt_type(_) -> [auth_password_format].
+535
View File
@@ -0,0 +1,535 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 4 Mar 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(ejabberd_pkix).
-behaviour(gen_server).
-behaviour(ejabberd_config).
%% API
-export([start_link/0, add_certfile/1, format_error/1, opt_type/1,
get_certfile/1, try_certfile/1, route_registered/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include_lib("public_key/include/public_key.hrl").
-include("logger.hrl").
-include("jid.hrl").
-record(state, {validate = true :: boolean(),
certs = #{}}).
-record(cert_state, {domains = [] :: [binary()]}).
-type cert() :: #'OTPCertificate'{}.
-type priv_key() :: public_key:private_key().
-type pub_key() :: #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | #'ECPoint'{}.
-type bad_cert_reason() :: cert_expired | invalid_issuer | invalid_signature |
name_not_permitted | missing_basic_constraint |
invalid_key_usage | selfsigned_peer | unknown_sig_algo |
unknown_ca | missing_priv_key.
-type bad_cert() :: {bad_cert, bad_cert_reason()}.
-type cert_error() :: not_cert | not_der | not_pem | encrypted.
-export_type([cert_error/0]).
%%%===================================================================
%%% API
%%%===================================================================
-spec add_certfile(filename:filename())
-> ok | {error, cert_error() | file:posix()}.
add_certfile(Path) ->
gen_server:call(?MODULE, {add_certfile, prep_path(Path)}).
-spec try_certfile(filename:filename()) -> binary().
try_certfile(Path0) ->
Path = prep_path(Path0),
case mk_cert_state(Path, false) of
{ok, _} -> Path;
{error, _} -> erlang:error(badarg)
end.
route_registered(Route) ->
gen_server:call(?MODULE, {route_registered, Route}).
-spec format_error(cert_error() | file:posix()) -> string().
format_error(not_cert) ->
"no PEM encoded certificates found";
format_error(not_pem) ->
"failed to decode from PEM format";
format_error(not_der) ->
"failed to decode from DER format";
format_error(encrypted) ->
"encrypted certificate found in the chain";
format_error({bad_cert, cert_expired}) ->
"certificate is no longer valid as its expiration date has passed";
format_error({bad_cert, invalid_issuer}) ->
"certificate issuer name does not match the name of the "
"issuer certificate in the chain";
format_error({bad_cert, invalid_signature}) ->
"certificate was not signed by its issuer certificate in the chain";
format_error({bad_cert, name_not_permitted}) ->
"invalid Subject Alternative Name extension";
format_error({bad_cert, missing_basic_constraint}) ->
"certificate, required to have the basic constraints extension, "
"does not have a basic constraints extension";
format_error({bad_cert, invalid_key_usage}) ->
"certificate key is used in an invalid way according "
"to the key-usage extension";
format_error({bad_cert, selfsigned_peer}) ->
"self-signed certificate in the chain";
format_error({bad_cert, unknown_sig_algo}) ->
"certificate is signed using unknown algorithm";
format_error({bad_cert, unknown_ca}) ->
"certificate is signed by unknown CA";
format_error({bad_cert, missing_priv_key}) ->
"no matching private key found for certificate in the chain";
format_error({bad_cert, Unknown}) ->
lists:flatten(io_lib:format("~w", [Unknown]));
format_error(Why) ->
case file:format_error(Why) of
"unknown POSIX error" ->
atom_to_list(Why);
Reason ->
Reason
end.
-spec get_certfile(binary()) -> {ok, binary()} | error.
get_certfile(Domain) ->
case ejabberd_idna:domain_utf8_to_ascii(Domain) of
false ->
error;
ASCIIDomain ->
case ets:lookup(?MODULE, ASCIIDomain) of
[] ->
case binary:split(ASCIIDomain, <<".">>, [trim]) of
[_, Host] ->
case ets:lookup(?MODULE, <<"*.", Host/binary>>) of
[{_, Path}|_] ->
{ok, Path};
[] ->
error
end;
_ ->
error
end;
[{_, Path}|_] ->
{ok, Path}
end
end.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
opt_type(ca_path) ->
fun(Path) -> iolist_to_binary(Path) end;
opt_type(_) ->
[ca_path].
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
process_flag(trap_exit, true),
ets:new(?MODULE, [named_table, public, bag]),
ejabberd_hooks:add(route_registered, ?MODULE, route_registered, 50),
Validate = case os:type() of
{win32, _} -> false;
_ ->
code:ensure_loaded(public_key),
erlang:function_exported(
public_key, short_name_hash, 1)
end,
if Validate -> check_ca_dir();
true -> ok
end,
State = #state{validate = Validate},
{ok, add_certfiles(State)}.
handle_call({add_certfile, Path}, _, State) ->
{Result, NewState} = add_certfile(Path, State),
{reply, Result, NewState};
handle_call({route_registered, Host}, _, State) ->
NewState = add_certfiles(Host, State),
case get_certfile(Host) of
{ok, _} -> ok;
error ->
?WARNING_MSG("No certificate found matching '~s': strictly "
"configured clients or servers will reject "
"connections with this host", [Host])
end,
{reply, ok, NewState};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
?WARNING_MSG("unexpected info: ~p", [_Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ejabberd_hooks:delete(route_registered, ?MODULE, route_registered, 50).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
add_certfiles(State) ->
lists:foldl(
fun(Host, AccState) ->
add_certfiles(Host, AccState)
end, State, ejabberd_config:get_myhosts()).
add_certfiles(Host, State) ->
lists:foldl(
fun(Opt, AccState) ->
case ejabberd_config:get_option({Opt, Host}) of
undefined -> AccState;
Path ->
{_, NewAccState} = add_certfile(Path, AccState),
NewAccState
end
end, State, [c2s_certfile, s2s_certfile, domain_certfile]).
add_certfile(Path, State) ->
case maps:get(Path, State#state.certs, undefined) of
#cert_state{} ->
{ok, State};
undefined ->
case mk_cert_state(Path, State#state.validate) of
{error, Reason} ->
{{error, Reason}, State};
{ok, CertState} ->
NewCerts = maps:put(Path, CertState, State#state.certs),
lists:foreach(
fun(Domain) ->
ets:insert(?MODULE, {Domain, Path})
end, CertState#cert_state.domains),
{ok, State#state{certs = NewCerts}}
end
end.
mk_cert_state(Path, Validate) ->
case check_certfile(Path, Validate) of
{ok, Ds} ->
{ok, #cert_state{domains = Ds}};
{invalid, Ds, {bad_cert, _} = Why} ->
?WARNING_MSG("certificate from ~s is invalid: ~s",
[Path, format_error(Why)]),
{ok, #cert_state{domains = Ds}};
{error, Why} = Err ->
?ERROR_MSG("failed to read certificate from ~s: ~s",
[Path, format_error(Why)]),
Err
end.
-spec check_certfile(filename:filename(), boolean())
-> {ok, [binary()]} | {invalid, [binary()], bad_cert()} |
{error, cert_error() | file:posix()}.
check_certfile(Path, Validate) ->
try
{ok, Data} = file:read_file(Path),
{ok, Certs, PrivKeys} = pem_decode(Data),
CertPaths = get_cert_paths(Certs),
Domains = get_domains(CertPaths),
case match_cert_keys(CertPaths, PrivKeys) of
{ok, _} ->
case validate(CertPaths, Validate) of
ok -> {ok, Domains};
{error, Why} -> {invalid, Domains, Why}
end;
{error, Why} ->
{invalid, Domains, Why}
end
catch _:{badmatch, {error, _} = Err} ->
Err
end.
-spec pem_decode(binary()) -> {ok, [cert()], [priv_key()]} |
{error, cert_error()}.
pem_decode(Data) ->
try public_key:pem_decode(Data) of
PemEntries ->
case decode_certs(PemEntries) of
{error, _} = Err ->
Err;
Objects ->
case lists:partition(
fun(#'OTPCertificate'{}) -> true;
(_) -> false
end, Objects) of
{[], _} ->
{error, not_cert};
{Certs, PrivKeys} ->
{ok, Certs, PrivKeys}
end
end
catch _:_ ->
{error, not_pem}
end.
-spec decode_certs([public_key:pem_entry()]) -> {[cert()], [priv_key()]} |
{error, not_der | encrypted}.
decode_certs(PemEntries) ->
try lists:foldr(
fun(_, {error, _} = Err) ->
Err;
({_, _, Flag}, _) when Flag /= not_encrypted ->
{error, encrypted};
({'Certificate', Der, _}, Acc) ->
[public_key:pkix_decode_cert(Der, otp)|Acc];
({'PrivateKeyInfo', Der, not_encrypted}, Acc) ->
#'PrivateKeyInfo'{privateKeyAlgorithm =
#'PrivateKeyInfo_privateKeyAlgorithm'{
algorithm = Algo},
privateKey = Key} =
public_key:der_decode('PrivateKeyInfo', Der),
case Algo of
?'rsaEncryption' ->
[public_key:der_decode(
'RSAPrivateKey', iolist_to_binary(Key))|Acc];
?'id-dsa' ->
[public_key:der_decode(
'DSAPrivateKey', iolist_to_binary(Key))|Acc];
?'id-ecPublicKey' ->
[public_key:der_decode(
'ECPrivateKey', iolist_to_binary(Key))|Acc];
_ ->
Acc
end;
({Tag, Der, _}, Acc) when Tag == 'RSAPrivateKey';
Tag == 'DSAPrivateKey';
Tag == 'ECPrivateKey' ->
[public_key:der_decode(Tag, Der)|Acc];
(_, Acc) ->
Acc
end, [], PemEntries)
catch _:_ ->
{error, not_der}
end.
-spec validate([{path, [cert()]}], boolean()) -> ok | {error, bad_cert()}.
validate([{path, Path}|Paths], true) ->
case validate_path(Path) of
ok ->
validate(Paths, true);
Err ->
Err
end;
validate(_, _) ->
ok.
-spec validate_path([cert()]) -> ok | {error, bad_cert()}.
validate_path([Cert|_] = Certs) ->
case find_local_issuer(Cert) of
{ok, IssuerCert} ->
try public_key:pkix_path_validation(IssuerCert, Certs, []) of
{ok, _} ->
ok;
Err ->
Err
catch error:function_clause ->
case erlang:get_stacktrace() of
[{public_key, pkix_sign_types, _, _}|_] ->
{error, {bad_cert, unknown_sig_algo}};
ST ->
%% Bug in public_key application
erlang:raise(error, function_clause, ST)
end
end;
{error, _} = Err ->
case public_key:pkix_is_self_signed(Cert) of
true ->
{error, {bad_cert, selfsigned_peer}};
false ->
Err
end
end.
-spec ca_dir() -> string().
ca_dir() ->
ejabberd_config:get_option(ca_path, "/etc/ssl/certs").
-spec check_ca_dir() -> ok.
check_ca_dir() ->
case filelib:wildcard(filename:join(ca_dir(), "*.0")) of
[] ->
Hint = "configuring 'ca_path' option might help",
case file:list_dir(ca_dir()) of
{error, Why} ->
?WARNING_MSG("failed to read CA directory ~s: ~s; ~s",
[ca_dir(), file:format_error(Why), Hint]);
{ok, _} ->
?WARNING_MSG("CA directory ~s doesn't contain "
"hashed certificate files; ~s",
[ca_dir(), Hint])
end;
_ ->
ok
end.
-spec find_local_issuer(cert()) -> {ok, cert()} | {error, {bad_cert, unknown_ca}}.
find_local_issuer(Cert) ->
{ok, {_, IssuerID}} = public_key:pkix_issuer_id(Cert, self),
Hash = short_name_hash(IssuerID),
filelib:fold_files(
ca_dir(), Hash ++ "\\.[0-9]+", false,
fun(_, {ok, IssuerCert}) ->
{ok, IssuerCert};
(CertFile, Acc) ->
try
{ok, Data} = file:read_file(CertFile),
{ok, [IssuerCert|_], _} = pem_decode(Data),
case public_key:pkix_is_issuer(Cert, IssuerCert) of
true ->
{ok, IssuerCert};
false ->
Acc
end
catch _:{badmatch, {error, Why}} ->
?ERROR_MSG("failed to read CA certificate from \"~s\": ~s",
[CertFile, format_error(Why)]),
Acc
end
end, {error, {bad_cert, unknown_ca}}).
-spec match_cert_keys([{path, [cert()]}], [priv_key()])
-> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
match_cert_keys(CertPaths, PrivKeys) ->
KeyPairs = [{pubkey_from_privkey(PrivKey), PrivKey} || PrivKey <- PrivKeys],
match_cert_keys(CertPaths, KeyPairs, []).
-spec match_cert_keys([{path, [cert()]}], [{pub_key(), priv_key()}],
[{cert(), priv_key()}])
-> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
match_cert_keys([{path, Certs}|CertPaths], KeyPairs, Result) ->
[Cert|_] = RevCerts = lists:reverse(Certs),
PubKey = pubkey_from_cert(Cert),
case lists:keyfind(PubKey, 1, KeyPairs) of
false ->
{error, {bad_cert, missing_priv_key}};
{_, PrivKey} ->
match_cert_keys(CertPaths, KeyPairs, [{RevCerts, PrivKey}|Result])
end;
match_cert_keys([], _, Result) ->
{ok, Result}.
-spec pubkey_from_cert(cert()) -> pub_key().
pubkey_from_cert(Cert) ->
TBSCert = Cert#'OTPCertificate'.tbsCertificate,
PubKeyInfo = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
SubjPubKey = PubKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey,
case PubKeyInfo#'OTPSubjectPublicKeyInfo'.algorithm of
#'PublicKeyAlgorithm'{
algorithm = ?rsaEncryption} ->
SubjPubKey;
#'PublicKeyAlgorithm'{
algorithm = ?'id-dsa',
parameters = {params, DSSParams}} ->
{SubjPubKey, DSSParams};
#'PublicKeyAlgorithm'{
algorithm = ?'id-ecPublicKey'} ->
SubjPubKey
end.
-spec pubkey_from_privkey(priv_key()) -> pub_key().
pubkey_from_privkey(#'RSAPrivateKey'{modulus = Modulus,
publicExponent = Exp}) ->
#'RSAPublicKey'{modulus = Modulus,
publicExponent = Exp};
pubkey_from_privkey(#'DSAPrivateKey'{p = P, q = Q, g = G, y = Y}) ->
{Y, #'Dss-Parms'{p = P, q = Q, g = G}};
pubkey_from_privkey(#'ECPrivateKey'{publicKey = Key}) ->
#'ECPoint'{point = Key}.
-spec get_domains([{path, [cert()]}]) -> [binary()].
get_domains(CertPaths) ->
lists:usort(
lists:flatmap(
fun({path, Certs}) ->
Cert = lists:last(Certs),
xmpp_stream_pkix:get_cert_domains(Cert)
end, CertPaths)).
-spec get_cert_paths([cert()]) -> [{path, [cert()]}].
get_cert_paths(Certs) ->
G = digraph:new([acyclic]),
lists:foreach(
fun(Cert) ->
digraph:add_vertex(G, Cert)
end, Certs),
lists:foreach(
fun({Cert1, Cert2}) when Cert1 /= Cert2 ->
case public_key:pkix_is_issuer(Cert1, Cert2) of
true ->
digraph:add_edge(G, Cert1, Cert2);
false ->
ok
end;
(_) ->
ok
end, [{Cert1, Cert2} || Cert1 <- Certs, Cert2 <- Certs]),
Paths = lists:flatmap(
fun(Cert) ->
case digraph:in_degree(G, Cert) of
0 ->
get_cert_path(G, [Cert]);
_ ->
[]
end
end, Certs),
digraph:delete(G),
Paths.
get_cert_path(G, [Root|_] = Acc) ->
case digraph:out_edges(G, Root) of
[] ->
[{path, Acc}];
Es ->
lists:flatmap(
fun(E) ->
{_, _, V, _} = digraph:edge(G, E),
get_cert_path(G, [V|Acc])
end, Es)
end.
-spec prep_path(filename:filename()) -> binary().
prep_path(Path0) ->
case filename:pathtype(Path0) of
relative ->
{ok, CWD} = file:get_cwd(),
iolist_to_binary(filename:join(CWD, Path0));
_ ->
iolist_to_binary(Path0)
end.
-ifdef(SHORT_NAME_HASH).
short_name_hash(IssuerID) ->
public_key:short_name_hash(IssuerID).
-else.
short_name_hash(_) ->
"".
-endif.
+4 -7
View File
@@ -100,13 +100,7 @@ stop_host(Host) ->
%% Returns {true, App} if we have configured sql for the given host
needs_sql(Host) ->
LHost = jid:nameprep(Host),
case ejabberd_config:get_option({sql_type, LHost},
fun(mysql) -> mysql;
(pgsql) -> pgsql;
(sqlite) -> sqlite;
(mssql) -> mssql;
(odbc) -> odbc
end, undefined) of
case ejabberd_config:get_option({sql_type, LHost}, undefined) of
mysql -> {true, p1_mysql};
pgsql -> {true, p1_pgsql};
sqlite -> {true, sqlite3};
@@ -115,6 +109,9 @@ needs_sql(Host) ->
undefined -> false
end.
-type sql_type() :: mysql | pgsql | sqlite | mssql | odbc.
-spec opt_type(sql_type) -> fun((sql_type()) -> sql_type());
(atom()) -> [atom()].
opt_type(sql_type) ->
fun (mysql) -> mysql;
(pgsql) -> pgsql;
+40 -30
View File
@@ -31,6 +31,7 @@
-define(GEN_SERVER, gen_server).
-endif.
-behaviour(?GEN_SERVER).
-behaviour(ejabberd_config).
%% API
-export([start_link/4,
@@ -41,7 +42,8 @@
starttls/2,
compress/2,
become_controller/2,
close/1]).
close/1,
opt_type/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2,
@@ -59,9 +61,6 @@
xml_stream_state :: fxml_stream:xml_stream_state() | undefined,
timeout = infinity:: timeout()}).
-define(HIBERNATE_TIMEOUT, ejabberd_config:get_option(receiver_hibernate, fun(X) when is_integer(X); X == hibernate-> X end, 90000)).
-spec start_link(inet:socket(), atom(), shaper:shaper(),
non_neg_integer() | infinity) -> ignore |
{error, any()} |
@@ -137,7 +136,7 @@ handle_call({starttls, TLSSocket}, _From, State) ->
case fast_tls:recv_data(TLSSocket, <<"">>) of
{ok, TLSData} ->
{reply, ok,
process_data(TLSData, NewState), ?HIBERNATE_TIMEOUT};
process_data(TLSData, NewState), hibernate_timeout()};
{error, _} = Err ->
{stop, normal, Err, NewState}
end;
@@ -156,31 +155,31 @@ handle_call({compress, Data}, _From,
case ezlib:recv_data(ZlibSocket, <<"">>) of
{ok, ZlibData} ->
{reply, {ok, ZlibSocket},
process_data(ZlibData, NewState), ?HIBERNATE_TIMEOUT};
process_data(ZlibData, NewState), hibernate_timeout()};
{error, _} = Err ->
{stop, normal, Err, NewState}
end;
handle_call(reset_stream, _From, State) ->
NewState = reset_parser(State),
Reply = ok,
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
{reply, Reply, NewState, hibernate_timeout()};
handle_call({become_controller, C2SPid}, _From, State) ->
XMLStreamState = fxml_stream:new(C2SPid, State#state.max_stanza_size),
NewState = State#state{c2s_pid = C2SPid,
xml_stream_state = XMLStreamState},
activate_socket(NewState),
Reply = ok,
{reply, Reply, NewState, ?HIBERNATE_TIMEOUT};
{reply, Reply, NewState, hibernate_timeout()};
handle_call(_Request, _From, State) ->
Reply = ok, {reply, Reply, State, ?HIBERNATE_TIMEOUT}.
Reply = ok, {reply, Reply, State, hibernate_timeout()}.
handle_cast({change_shaper, Shaper}, State) ->
NewShaperState = shaper:new(Shaper),
{noreply, State#state{shaper_state = NewShaperState},
?HIBERNATE_TIMEOUT};
hibernate_timeout()};
handle_cast(close, State) -> {stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
{noreply, State, hibernate_timeout()}.
handle_info({Tag, _TCPSocket, Data},
#state{socket = Socket, sock_mod = SockMod} = State)
@@ -191,7 +190,7 @@ handle_info({Tag, _TCPSocket, Data},
case fast_tls:recv_data(Socket, Data) of
{ok, TLSData} ->
{noreply, process_data(TLSData, State),
?HIBERNATE_TIMEOUT};
hibernate_timeout()};
{error, Reason} ->
if is_binary(Reason) ->
?DEBUG("TLS error = ~s", [Reason]);
@@ -204,11 +203,11 @@ handle_info({Tag, _TCPSocket, Data},
case ezlib:recv_data(Socket, Data) of
{ok, ZlibData} ->
{noreply, process_data(ZlibData, State),
?HIBERNATE_TIMEOUT};
hibernate_timeout()};
{error, _Reason} -> {stop, normal, State}
end;
_ ->
{noreply, process_data(Data, State), ?HIBERNATE_TIMEOUT}
{noreply, process_data(Data, State), hibernate_timeout()}
end;
handle_info({Tag, _TCPSocket}, State)
when (Tag == tcp_closed) or (Tag == ssl_closed) ->
@@ -216,18 +215,18 @@ handle_info({Tag, _TCPSocket}, State)
handle_info({Tag, _TCPSocket, Reason}, State)
when (Tag == tcp_error) or (Tag == ssl_error) ->
case Reason of
timeout -> {noreply, State, ?HIBERNATE_TIMEOUT};
timeout -> {noreply, State, hibernate_timeout()};
_ -> {stop, normal, State}
end;
handle_info({timeout, _Ref, activate}, State) ->
activate_socket(State),
{noreply, State, ?HIBERNATE_TIMEOUT};
{noreply, State, hibernate_timeout()};
handle_info(timeout, State) ->
proc_lib:hibernate(?GEN_SERVER, enter_loop,
[?MODULE, [], State]),
{noreply, State, ?HIBERNATE_TIMEOUT};
{noreply, State, hibernate_timeout()};
handle_info(_Info, State) ->
{noreply, State, ?HIBERNATE_TIMEOUT}.
{noreply, State, hibernate_timeout()}.
terminate(_Reason,
#state{xml_stream_state = XMLStreamState,
@@ -235,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),
@@ -249,17 +248,15 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}.
activate_socket(#state{socket = Socket,
sock_mod = SockMod}) ->
PeerName = case SockMod of
gen_tcp ->
inet:setopts(Socket, [{active, once}]),
inet:peername(Socket);
_ ->
SockMod:setopts(Socket, [{active, once}]),
SockMod:peername(Socket)
end,
case PeerName of
Res = case SockMod of
gen_tcp ->
inet:setopts(Socket, [{active, once}]);
_ ->
SockMod:setopts(Socket, [{active, once}])
end,
case Res of
{error, _Reason} -> self() ! {tcp_closed, Socket};
{ok, _} -> ok
ok -> ok
end.
%% Data processing for connectors directly generating xmlelement in
@@ -275,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;
@@ -345,3 +342,16 @@ do_call(Pid, Msg) ->
_:_ ->
{error, einval}
end.
hibernate_timeout() ->
ejabberd_config:get_option(receiver_hibernate, timer:seconds(90)).
-spec opt_type(receiver_hibernate) -> fun((pos_integer() | hibernate) ->
pos_integer() | hibernate);
(atom()) -> [atom()].
opt_type(receiver_hibernate) ->
fun(I) when is_integer(I), I>0 -> I;
(hibernate) -> hibernate
end;
opt_type(_) ->
[receiver_hibernate].
+108 -47
View File
@@ -31,11 +31,12 @@
-compile({no_auto_import, [get/1, put/2]}).
%% API
-export([start_link/1, get_proc/1, q/1, qp/1, format_error/1]).
-export([start_link/1, get_proc/1, get_connection/1, q/1, qp/1, format_error/1]).
%% Commands
-export([multi/1, get/1, set/2, del/1,
sadd/2, srem/2, smembers/1, sismember/2, scard/1,
hget/2, hset/3, hdel/2, hlen/1, hgetall/1, hkeys/1]).
hget/2, hset/3, hdel/2, hlen/1, hgetall/1, hkeys/1,
subscribe/1, publish/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -53,14 +54,18 @@
-record(state, {connection :: pid() | undefined,
num :: pos_integer(),
subscriptions = #{} :: map(),
pending_q :: p1_queue:queue()}).
-type redis_error() :: {error, binary() | timeout | disconnected | overloaded}.
-type error_reason() :: binary() | timeout | disconnected | overloaded.
-type redis_error() :: {error, error_reason()}.
-type redis_reply() :: binary() | [binary()].
-type redis_command() :: [binary()].
-type redis_pipeline() :: [redis_command()].
-type state() :: #state{}.
-export_type([error_reason/0]).
%%%===================================================================
%%% API
%%%===================================================================
@@ -79,11 +84,11 @@ get_connection(I) ->
-spec q(redis_command()) -> {ok, redis_reply()} | redis_error().
q(Command) ->
call(get_worker(), {q, Command}, ?MAX_RETRIES).
call(get_rnd_id(), {q, Command}, ?MAX_RETRIES).
-spec qp(redis_pipeline()) -> {ok, [redis_reply()]} | redis_error().
qp(Pipeline) ->
call(get_worker(), {qp, Pipeline}, ?MAX_RETRIES).
call(get_rnd_id(), {qp, Pipeline}, ?MAX_RETRIES).
-spec multi(fun(() -> any())) -> {ok, [redis_reply()]} | redis_error().
multi(F) ->
@@ -288,6 +293,30 @@ hkeys(Key) ->
erlang:error(transaction_unsupported)
end.
-spec subscribe([binary()]) -> ok | redis_error().
subscribe(Channels) ->
try ?GEN_SERVER:call(get_proc(1), {subscribe, self(), Channels}, ?CALL_TIMEOUT)
catch exit:{Why, {?GEN_SERVER, call, _}} ->
Reason = case Why of
timeout -> timeout;
_ -> disconnected
end,
{error, Reason}
end.
-spec publish(iodata(), iodata()) -> {ok, non_neg_integer()} | redis_error() | queued.
publish(Channel, Data) ->
Cmd = [<<"PUBLISH">>, Channel, Data],
case erlang:get(?TR_STACK) of
undefined ->
case q(Cmd) of
{ok, N} -> {ok, binary_to_integer(N)};
{error, _} = Err -> Err
end;
Stack ->
tr_enq(Cmd, Stack)
end.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
@@ -315,6 +344,15 @@ handle_call(connect, From, #state{connection = Pid} = State) ->
self() ! connect,
handle_call(connect, From, State#state{connection = undefined})
end;
handle_call({subscribe, Caller, Channels}, _From,
#state{connection = Pid, subscriptions = Subs} = State) ->
Subs1 = lists:foldl(
fun(Channel, Acc) ->
Callers = maps:get(Channel, Acc, []) -- [Caller],
maps:put(Channel, [Caller|Callers], Acc)
end, Subs, Channels),
eredis_subscribe(Pid, Channels),
{reply, ok, State#state{subscriptions = Subs1}};
handle_call(Request, _From, State) ->
?WARNING_MSG("unexepected call: ~p", [Request]),
{noreply, State}.
@@ -326,6 +364,7 @@ handle_info(connect, #state{connection = undefined} = State) ->
NewState = case connect(State) of
{ok, Connection} ->
Q1 = flush_queue(State#state.pending_q),
re_subscribe(Connection, State#state.subscriptions),
State#state{connection = Connection, pending_q = Q1};
{error, _} ->
State
@@ -342,6 +381,31 @@ handle_info({'EXIT', Pid, _}, State) ->
_ ->
{noreply, State}
end;
handle_info({subscribed, Channel, Pid}, State) ->
case State#state.connection of
Pid ->
case maps:is_key(Channel, State#state.subscriptions) of
true -> eredis_sub:ack_message(Pid);
false ->
?WARNING_MSG("got subscription ack for unknown channel ~s",
[Channel])
end;
_ ->
ok
end,
{noreply, State};
handle_info({message, Channel, Data, Pid}, State) ->
case State#state.connection of
Pid ->
lists:foreach(
fun(Subscriber) ->
erlang:send(Subscriber, {redis_message, Channel, Data})
end, maps:get(Channel, State#state.subscriptions, [])),
eredis_sub:ack_message(Pid);
_ ->
ok
end,
{noreply, State};
handle_info(Info, State) ->
?WARNING_MSG("unexpected info = ~p", [Info]),
{noreply, State}.
@@ -357,28 +421,14 @@ code_change(_OldVsn, State, _Extra) ->
%%%===================================================================
-spec connect(state()) -> {ok, pid()} | {error, any()}.
connect(#state{num = Num}) ->
Server = ejabberd_config:get_option(redis_server,
fun iolist_to_list/1,
"localhost"),
Port = ejabberd_config:get_option(redis_port,
fun(P) when is_integer(P),
P>0, P<65536 ->
P
end, 6379),
DB = ejabberd_config:get_option(redis_db,
fun(I) when is_integer(I), I >= 0 ->
I
end, 0),
Pass = ejabberd_config:get_option(redis_password,
fun iolist_to_list/1,
""),
Server = ejabberd_config:get_option(redis_server, "localhost"),
Port = ejabberd_config:get_option(redis_port, 6379),
DB = ejabberd_config:get_option(redis_db, 0),
Pass = ejabberd_config:get_option(redis_password, ""),
ConnTimeout = timer:seconds(
ejabberd_config:get_option(
redis_connect_timeout,
fun(I) when is_integer(I), I>0 -> I end,
1)),
try case eredis:start_link(Server, Port, DB, Pass,
no_reconnect, ConnTimeout) of
redis_connect_timeout, 1)),
try case do_connect(Num, Server, Port, Pass, DB, ConnTimeout) of
{ok, Client} ->
?DEBUG("Connection #~p established to Redis at ~s:~p",
[Num, Server, Port]),
@@ -397,12 +447,24 @@ connect(#state{num = Num}) ->
{error, Reason}
end.
-spec call({atom(), atom()}, {q, redis_command()}, integer()) ->
do_connect(1, Server, Port, Pass, _DB, _ConnTimeout) ->
%% First connection in the pool is always a subscriber
Res = eredis_sub:start_link(Server, Port, Pass, no_reconnect, infinity, drop),
case Res of
{ok, Pid} -> eredis_sub:controlling_process(Pid);
_ -> ok
end,
Res;
do_connect(_, Server, Port, Pass, DB, ConnTimeout) ->
eredis:start_link(Server, Port, DB, Pass, no_reconnect, ConnTimeout).
-spec call(pos_integer(), {q, redis_command()}, integer()) ->
{ok, redis_reply()} | redis_error();
({atom(), atom()}, {qp, redis_pipeline()}, integer()) ->
(pos_integer(), {qp, redis_pipeline()}, integer()) ->
{ok, [redis_reply()]} | redis_error().
call({Conn, Parent}, {F, Cmd}, Retries) ->
call(I, {F, Cmd}, Retries) ->
?DEBUG("redis query: ~p", [Cmd]),
Conn = get_connection(I),
Res = try eredis:F(Conn, Cmd, ?CALL_TIMEOUT) of
{error, Reason} when is_atom(Reason) ->
try exit(whereis(Conn), kill) catch _:_ -> ok end,
@@ -414,8 +476,8 @@ call({Conn, Parent}, {F, Cmd}, Retries) ->
end,
case Res of
{error, disconnected} when Retries > 0 ->
try ?GEN_SERVER:call(Parent, connect, ?CALL_TIMEOUT) of
ok -> call({Conn, Parent}, {F, Cmd}, Retries-1);
try ?GEN_SERVER:call(get_proc(I), connect, ?CALL_TIMEOUT) of
ok -> call(I, {F, Cmd}, Retries-1);
{error, _} = Err -> Err
catch exit:{Why, {?GEN_SERVER, call, _}} ->
Reason1 = case Why of
@@ -439,11 +501,9 @@ log_error(Cmd, Reason) ->
"** response = ~s",
[Cmd, format_error(Reason)]).
-spec get_worker() -> {atom(), atom()}.
get_worker() ->
Time = p1_time_compat:system_time(),
I = erlang:phash2(Time, ejabberd_redis_sup:get_pool_size()) + 1,
{get_connection(I), get_proc(I)}.
-spec get_rnd_id() -> pos_integer().
get_rnd_id() ->
randoms:round_robin(ejabberd_redis_sup:get_pool_size() - 1) + 2.
-spec get_result([{error, atom() | binary()} | {ok, iodata()}]) ->
{ok, [redis_reply()]} | {error, binary()}.
@@ -479,10 +539,6 @@ reply(Val) ->
_ -> queued
end.
-spec iolist_to_list(iodata()) -> string().
iolist_to_list(IOList) ->
binary_to_list(iolist_to_binary(IOList)).
-spec max_fsm_queue() -> pos_integer().
max_fsm_queue() ->
proplists:get_value(max_queue, fsm_limit_opts(), ?DEFAULT_MAX_QUEUE).
@@ -491,14 +547,9 @@ fsm_limit_opts() ->
ejabberd_config:fsm_limit_opts([]).
get_queue_type() ->
case ejabberd_config:get_option(
redis_queue_type,
ejabberd_redis_sup:opt_type(redis_queue_type)) of
undefined ->
ejabberd_config:default_queue_type(global);
Type ->
Type
end.
ejabberd_config:get_option(
redis_queue_type,
ejabberd_config:default_queue_type(global)).
-spec flush_queue(p1_queue:queue()) -> p1_queue:queue().
flush_queue(Q) ->
@@ -531,3 +582,13 @@ clean_queue(Q, CurrTime) ->
true ->
Q1
end.
re_subscribe(Pid, Subs) ->
case maps:keys(Subs) of
[] -> ok;
Channels -> eredis_subscribe(Pid, Channels)
end.
eredis_subscribe(Pid, Channels) ->
?DEBUG("redis query: ~p", [[<<"SUBSCRIBE">>|Channels]]),
eredis_sub:subscribe(Pid, Channels).
+14 -21
View File
@@ -23,6 +23,7 @@
-module(ejabberd_redis_sup).
-behaviour(supervisor).
-behaviour(ejabberd_config).
%% API
-export([start_link/0, get_pool_size/0,
@@ -107,23 +108,11 @@ is_redis_configured(Host) ->
PoolSize = ejabberd_config:has_option({redis_pool_size, Host}),
ConnTimeoutConfigured = ejabberd_config:has_option(
{redis_connect_timeout, Host}),
Modules = ejabberd_config:get_option(
{modules, Host},
fun(L) when is_list(L) -> L end, []),
SMConfigured = ejabberd_config:get_option(
{sm_db_type, Host},
fun(V) -> V end) == redis,
RouterConfigured = ejabberd_config:get_option(
{router_db_type, Host},
fun(V) -> V end) == redis,
ModuleWithRedisDBConfigured =
lists:any(
fun({Module, Opts}) ->
gen_mod:db_type(Host, Opts, Module) == redis
end, Modules),
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == redis,
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == redis,
ServerConfigured or PortConfigured or DBConfigured or PassConfigured or
PoolSize or ConnTimeoutConfigured or
SMConfigured or RouterConfigured or ModuleWithRedisDBConfigured.
SMConfigured or RouterConfigured.
get_specs() ->
lists:map(
@@ -133,14 +122,19 @@ get_specs() ->
end, lists:seq(1, get_pool_size())).
get_pool_size() ->
ejabberd_config:get_option(
redis_pool_size,
fun(N) when is_integer(N), N >= 1 -> N end,
?DEFAULT_POOL_SIZE).
ejabberd_config:get_option(redis_pool_size, ?DEFAULT_POOL_SIZE) + 1.
iolist_to_list(IOList) ->
binary_to_list(iolist_to_binary(IOList)).
-spec opt_type(redis_connect_timeout) -> fun((pos_integer()) -> pos_integer());
(redis_db) -> fun((non_neg_integer()) -> non_neg_integer());
(redis_password) -> fun((binary()) -> binary());
(redis_port) -> fun((0..65535) -> 0..65535);
(redis_server) -> fun((binary()) -> binary());
(redis_pool_size) -> fun((pos_integer()) -> pos_integer());
(redis_queue_type) -> fun((ram | file) -> ram | file);
(atom()) -> [atom()].
opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_db) ->
@@ -155,5 +149,4 @@ opt_type(redis_queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(_) ->
[redis_connect_timeout, redis_db, redis_password,
redis_port, redis_pool_size, redis_server,
redis_pool_size, redis_queue_type].
redis_port, redis_pool_size, redis_server, redis_queue_type].
+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
+42 -59
View File
@@ -30,7 +30,7 @@
-author('alexey@process-one.net').
-export([start_link/0, init/1, get_pids/0,
transform_options/1, get_random_pid/0, get_random_pid/1,
transform_options/1, get_random_pid/0,
host_up/1, config_reloaded/0, opt_type/1]).
-include("ejabberd.hrl").
@@ -77,24 +77,23 @@ is_riak_configured() ->
lists:any(fun is_riak_configured/1, ?MYHOSTS).
is_riak_configured(Host) ->
ServerConfigured = ejabberd_config:get_option(
{riak_server, Host},
fun(_) -> true end, false),
PortConfigured = ejabberd_config:get_option(
{riak_port, Host},
fun(_) -> true end, false),
ServerConfigured = ejabberd_config:has_option({riak_server, Host}),
PortConfigured = ejabberd_config:has_option({riak_port, Host}),
StartIntervalConfigured = ejabberd_config:has_option({riak_start_interval, Host}),
PoolConfigured = ejabberd_config:has_option({riak_pool_size, Host}),
CacertConfigured = ejabberd_config:has_option({riak_cacertfile, Host}),
UserConfigured = ejabberd_config:has_option({riak_username, Host}),
PassConfigured = ejabberd_config:has_option({riak_password, Host}),
AuthConfigured = lists:member(
ejabberd_auth_riak,
ejabberd_auth:auth_modules(Host)),
Modules = ejabberd_config:get_option(
{modules, Host},
fun(L) when is_list(L) -> L end, []),
ModuleWithRiakDBConfigured = lists:any(
fun({Module, Opts}) ->
gen_mod:db_type(Host, Opts, Module) == riak
end, Modules),
ServerConfigured or PortConfigured
or AuthConfigured or ModuleWithRiakDBConfigured.
SMConfigured = ejabberd_config:get_option({sm_db_type, Host}) == riak,
RouterConfigured = ejabberd_config:get_option({router_db_type, Host}) == riak,
ServerConfigured or PortConfigured or StartIntervalConfigured
or PoolConfigured or CacertConfigured
or UserConfigured or PassConfigured
or SMConfigured or RouterConfigured
or AuthConfigured.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
@@ -140,59 +139,31 @@ get_specs() ->
end, lists:seq(1, PoolSize)).
get_start_interval() ->
ejabberd_config:get_option(
riak_start_interval,
fun(N) when is_integer(N), N >= 1 -> N end,
?DEFAULT_RIAK_START_INTERVAL).
ejabberd_config:get_option(riak_start_interval, ?DEFAULT_RIAK_START_INTERVAL).
get_pool_size() ->
ejabberd_config:get_option(
riak_pool_size,
fun(N) when is_integer(N), N >= 1 -> N end,
?DEFAULT_POOL_SIZE).
ejabberd_config:get_option(riak_pool_size, ?DEFAULT_POOL_SIZE).
get_riak_server() ->
ejabberd_config:get_option(
riak_server,
fun(S) ->
binary_to_list(iolist_to_binary(S))
end, ?DEFAULT_RIAK_HOST).
ejabberd_config:get_option(riak_server, ?DEFAULT_RIAK_HOST).
get_riak_cacertfile() ->
ejabberd_config:get_option(
riak_cacertfile,
fun(S) ->
binary_to_list(iolist_to_binary(S))
end, nil).
ejabberd_config:get_option(riak_cacertfile, nil).
get_riak_username() ->
ejabberd_config:get_option(
riak_username,
fun(S) ->
binary_to_list(iolist_to_binary(S))
end, nil).
ejabberd_config:get_option(riak_username, nil).
get_riak_password() ->
ejabberd_config:get_option(
riak_password,
fun(S) ->
binary_to_list(iolist_to_binary(S))
end, nil).
ejabberd_config:get_option(riak_password, nil).
get_riak_port() ->
ejabberd_config:get_option(
riak_port,
fun(P) when is_integer(P), P > 0, P < 65536 -> P end,
?DEFAULT_RIAK_PORT).
ejabberd_config:get_option(riak_port, ?DEFAULT_RIAK_PORT).
get_pids() ->
[ejabberd_riak:get_proc(I) || I <- lists:seq(1, get_pool_size())].
get_random_pid() ->
get_random_pid(p1_time_compat:system_time()).
get_random_pid(Term) ->
I = erlang:phash2(Term, get_pool_size()) + 1,
I = randoms:round_robin(get_pool_size()) + 1,
ejabberd_riak:get_proc(I).
transform_options(Opts) ->
@@ -203,16 +174,28 @@ transform_options({riak_server, {S, P}}, Opts) ->
transform_options(Opt, Opts) ->
[Opt|Opts].
opt_type(modules) -> fun (L) when is_list(L) -> L end;
-spec opt_type(riak_pool_size) -> fun((pos_integer()) -> pos_integer());
(riak_port) -> fun((0..65535) -> 0..65535);
(riak_server) -> fun((binary()) -> binary());
(riak_start_interval) -> fun((pos_integer()) -> pos_integer());
(riak_cacertfile) -> fun((binary()) -> binary());
(riak_username) -> fun((binary()) -> binary());
(riak_password) -> fun((binary()) -> binary());
(atom()) -> [atom()].
opt_type(riak_pool_size) ->
fun (N) when is_integer(N), N >= 1 -> N end;
opt_type(riak_port) -> fun (_) -> true end;
opt_type(riak_server) -> fun (_) -> true end;
opt_type(riak_port) ->
fun(P) when is_integer(P), P > 0, P < 65536 -> P end;
opt_type(riak_server) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(riak_start_interval) ->
fun (N) when is_integer(N), N >= 1 -> N end;
opt_type(riak_cacertfile) -> fun iolist_to_binary/1;
opt_type(riak_username) -> fun iolist_to_binary/1;
opt_type(riak_password) -> fun iolist_to_binary/1;
opt_type(riak_cacertfile) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(riak_username) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(riak_password) ->
fun(S) -> binary_to_list(iolist_to_binary(S)) end;
opt_type(_) ->
[modules, riak_pool_size, riak_port, riak_server,
[riak_pool_size, riak_port, riak_server,
riak_start_interval, riak_cacertfile, riak_username, riak_password].
+171 -41
View File
@@ -49,7 +49,8 @@
get_all_routes/0,
is_my_route/1,
is_my_host/1,
find_routes/0,
clean_cache/1,
config_reloaded/0,
get_backend/0]).
-export([start_link/0]).
@@ -70,12 +71,8 @@
-callback register_route(binary(), binary(), local_hint(),
undefined | pos_integer(), pid()) -> ok | {error, term()}.
-callback unregister_route(binary(), undefined | pos_integer(), pid()) -> ok | {error, term()}.
-callback find_routes(binary()) -> [#route{}].
-callback find_routes() -> [#route{}].
-callback host_of_route(binary()) -> {ok, binary()} | error.
-callback is_my_route(binary()) -> boolean().
-callback is_my_host(binary()) -> boolean().
-callback get_all_routes() -> [binary()].
-callback find_routes(binary()) -> {ok, [#route{}]} | {error, any()}.
-callback get_all_routes() -> {ok, [binary()]} | {error, any()}.
-record(state, {}).
@@ -159,7 +156,9 @@ register_route(Domain, ServerHost, LocalHint, Pid) ->
case Mod:register_route(LDomain, LServerHost, LocalHint,
get_component_number(LDomain), Pid) of
ok ->
?DEBUG("Route registered: ~s", [LDomain]);
?DEBUG("Route registered: ~s", [LDomain]),
ejabberd_hooks:run(route_registered, [LDomain]),
delete_cache(Mod, LDomain);
{error, Err} ->
?ERROR_MSG("Failed to register route ~s: ~p",
[LDomain, Err])
@@ -186,7 +185,9 @@ unregister_route(Domain, Pid) ->
case Mod:unregister_route(
LDomain, get_component_number(LDomain), Pid) of
ok ->
?DEBUG("Route unregistered: ~s", [LDomain]);
?DEBUG("Route unregistered: ~s", [LDomain]),
ejabberd_hooks:run(route_unregistered, [LDomain]),
delete_cache(Mod, LDomain);
{error, Err} ->
?ERROR_MSG("Failed to unregister route ~s: ~p",
[LDomain, Err])
@@ -199,15 +200,55 @@ unregister_routes(Domains) ->
end,
Domains).
-spec find_routes(binary()) -> [#route{}].
find_routes(Domain) ->
Mod = get_backend(),
case use_cache(Mod) of
true ->
case ets_cache:lookup(
?ROUTES_CACHE, {route, Domain},
fun() ->
case Mod:find_routes(Domain) of
{ok, Rs} when Rs /= [] ->
{ok, Rs};
_ ->
error
end
end) of
{ok, Rs} -> Rs;
error -> []
end;
false ->
case Mod:find_routes(Domain) of
{ok, Rs} -> Rs;
_ -> []
end
end.
-spec get_all_routes() -> [binary()].
get_all_routes() ->
Mod = get_backend(),
Mod:get_all_routes().
-spec find_routes() -> [#route{}].
find_routes() ->
Mod = get_backend(),
Mod:find_routes().
case use_cache(Mod) of
true ->
case ets_cache:lookup(
?ROUTES_CACHE, routes,
fun() ->
case Mod:get_all_routes() of
{ok, Rs} when Rs /= [] ->
{ok, Rs};
_ ->
error
end
end) of
{ok, Rs} -> Rs;
error -> []
end;
false ->
case Mod:get_all_routes() of
{ok, Rs} -> Rs;
_ -> []
end
end.
-spec host_of_route(binary()) -> binary().
host_of_route(Domain) ->
@@ -215,10 +256,11 @@ host_of_route(Domain) ->
error ->
erlang:error({invalid_domain, Domain});
LDomain ->
Mod = get_backend(),
case Mod:host_of_route(LDomain) of
{ok, ServerHost} -> ServerHost;
error -> erlang:error({unregistered_route, Domain})
case find_routes(LDomain) of
[#route{server_host = ServerHost}|_] ->
ServerHost;
_ ->
erlang:error({unregistered_route, Domain})
end
end.
@@ -228,8 +270,7 @@ is_my_route(Domain) ->
error ->
erlang:error({invalid_domain, Domain});
LDomain ->
Mod = get_backend(),
Mod:is_my_route(LDomain)
find_routes(LDomain) /= []
end.
-spec is_my_host(binary()) -> boolean().
@@ -238,8 +279,10 @@ is_my_host(Domain) ->
error ->
erlang:error({invalid_domain, Domain});
LDomain ->
Mod = get_backend(),
Mod:is_my_host(LDomain)
case find_routes(LDomain) of
[#route{server_host = LDomain}|_] -> true;
_ -> false
end
end.
-spec process_iq(iq()) -> any().
@@ -250,12 +293,20 @@ process_iq(#iq{to = To} = IQ) ->
ejabberd_sm:process_iq(IQ)
end.
-spec config_reloaded() -> ok.
config_reloaded() ->
Mod = get_backend(),
init_cache(Mod).
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
Mod = get_backend(),
init_cache(Mod),
Mod:init(),
clean_cache(),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
@@ -273,7 +324,7 @@ handle_info(Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
@@ -290,8 +341,7 @@ do_route(OrigPacket) ->
Packet ->
To = xmpp:get_to(Packet),
LDstDomain = To#jid.lserver,
Mod = get_backend(),
case Mod:find_routes(LDstDomain) of
case find_routes(LDstDomain) of
[] ->
ejabberd_s2s:route(Packet);
[Route] ->
@@ -337,15 +387,11 @@ balancing_route(From, To, Packet, Rs) ->
-spec get_component_number(binary()) -> pos_integer() | undefined.
get_component_number(LDomain) ->
ejabberd_config:get_option(
{domain_balancing_component_number, LDomain},
fun(N) when is_integer(N), N > 1 -> N end,
undefined).
ejabberd_config:get_option({domain_balancing_component_number, LDomain}).
-spec get_domain_balancing(jid(), jid(), binary()) -> any().
get_domain_balancing(From, To, LDomain) ->
case ejabberd_config:get_option(
{domain_balancing, LDomain}, fun(D) when is_atom(D) -> D end) of
case ejabberd_config:get_option({domain_balancing, LDomain}) of
undefined -> p1_time_compat:system_time();
random -> p1_time_compat:system_time();
source -> jid:tolower(From);
@@ -356,16 +402,92 @@ get_domain_balancing(From, To, LDomain) ->
-spec get_backend() -> module().
get_backend() ->
DBType = case ejabberd_config:get_option(
router_db_type,
fun(T) -> ejabberd_config:v_db(?MODULE, T) end) of
undefined ->
ejabberd_config:default_ram_db(?MODULE);
T ->
T
end,
DBType = ejabberd_config:get_option(
router_db_type,
ejabberd_config:default_ram_db(?MODULE)),
list_to_atom("ejabberd_router_" ++ atom_to_list(DBType)).
-spec cache_nodes(module()) -> [node()].
cache_nodes(Mod) ->
case erlang:function_exported(Mod, cache_nodes, 0) of
true -> Mod:cache_nodes();
false -> ejabberd_cluster:get_nodes()
end.
-spec use_cache(module()) -> boolean().
use_cache(Mod) ->
case erlang:function_exported(Mod, use_cache, 0) of
true -> Mod:use_cache();
false ->
ejabberd_config:get_option(
router_use_cache,
ejabberd_config:use_cache(global))
end.
-spec delete_cache(module(), binary()) -> ok.
delete_cache(Mod, Domain) ->
case use_cache(Mod) of
true ->
ets_cache:delete(?ROUTES_CACHE, {route, Domain}, cache_nodes(Mod)),
ets_cache:delete(?ROUTES_CACHE, routes, cache_nodes(Mod));
false ->
ok
end.
-spec init_cache(module()) -> ok.
init_cache(Mod) ->
case use_cache(Mod) of
true ->
ets_cache:new(?ROUTES_CACHE, cache_opts());
false ->
ets_cache:delete(?ROUTES_CACHE)
end.
-spec cache_opts() -> [proplists:property()].
cache_opts() ->
MaxSize = ejabberd_config:get_option(
router_cache_size,
ejabberd_config:cache_size(global)),
CacheMissed = ejabberd_config:get_option(
router_cache_missed,
ejabberd_config:cache_missed(global)),
LifeTime = case ejabberd_config:get_option(
router_cache_life_time,
ejabberd_config:cache_life_time(global)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec clean_cache(node()) -> ok.
clean_cache(Node) ->
ets_cache:filter(
?ROUTES_CACHE,
fun(_, error) ->
false;
(routes, _) ->
false;
({route, _}, {ok, Rs}) ->
not lists:any(
fun(#route{pid = Pid}) ->
node(Pid) == Node
end, Rs)
end).
-spec clean_cache() -> ok.
clean_cache() ->
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
-type domain_balancing() :: random | source | destination |
bare_source | bare_destination.
-spec opt_type(domain_balancing) -> fun((domain_balancing()) -> domain_balancing());
(domain_balancing_component_number) -> fun((pos_integer()) -> pos_integer());
(router_db_type) -> fun((atom()) -> atom());
(router_use_cache) -> fun((boolean()) -> boolean());
(router_cache_missed) -> fun((boolean()) -> boolean());
(router_cache_size) -> fun((timeout()) -> timeout());
(router_cache_life_time) -> fun((timeout()) -> timeout());
(atom()) -> [atom()].
opt_type(domain_balancing) ->
fun (random) -> random;
(source) -> source;
@@ -376,6 +498,14 @@ opt_type(domain_balancing) ->
opt_type(domain_balancing_component_number) ->
fun (N) when is_integer(N), N > 1 -> N end;
opt_type(router_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(O) when O == router_use_cache; O == router_cache_missed ->
fun(B) when is_boolean(B) -> B end;
opt_type(O) when O == router_cache_size; O == router_cache_life_time ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(_) ->
[domain_balancing, domain_balancing_component_number,
router_db_type].
router_db_type, router_use_cache, router_cache_size,
router_cache_missed, router_cache_life_time].
+13 -34
View File
@@ -25,8 +25,7 @@
%% API
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
host_of_route/1, is_my_route/1, is_my_host/1, get_all_routes/0,
find_routes/0]).
get_all_routes/0, use_cache/0]).
%% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, handle_info/2,
terminate/2, code_change/3, start_link/0]).
@@ -54,6 +53,9 @@ init() ->
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
use_cache() ->
false.
register_route(Domain, ServerHost, LocalHint, undefined, Pid) ->
F = fun () ->
mnesia:write(#route{domain = Domain,
@@ -124,37 +126,15 @@ unregister_route(Domain, _, Pid) ->
transaction(F).
find_routes(Domain) ->
mnesia:dirty_read(route, Domain).
host_of_route(Domain) ->
case mnesia:dirty_read(route, Domain) of
[#route{server_host = ServerHost}|_] ->
{ok, ServerHost};
[] ->
error
end.
is_my_route(Domain) ->
mnesia:dirty_read(route, Domain) /= [].
is_my_host(Domain) ->
case mnesia:dirty_read(route, Domain) of
[#route{server_host = Host}|_] ->
Host == Domain;
[] ->
false
end.
{ok, mnesia:dirty_read(route, Domain)}.
get_all_routes() ->
mnesia:dirty_select(
route,
ets:fun2ms(
fun(#route{domain = Domain, server_host = ServerHost})
when Domain /= ServerHost -> Domain
end)).
find_routes() ->
ets:tab2list(route).
{ok, mnesia:dirty_select(
route,
ets:fun2ms(
fun(#route{domain = Domain, server_host = ServerHost})
when Domain /= ServerHost -> Domain
end))}.
%%%===================================================================
%%% gen_server callbacks
@@ -165,12 +145,11 @@ init([]) ->
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, route)}]),
mnesia:add_table_copy(route, node(), ram_copies),
mnesia:subscribe({table, route, simple}),
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) ->
@@ -227,7 +206,7 @@ transaction(F) ->
ok;
{aborted, Reason} ->
?ERROR_MSG("Mnesia transaction failed: ~p", [Reason]),
{error, Reason}
{error, db_failure}
end.
-spec update_tables() -> ok.
-1
View File
@@ -120,7 +120,6 @@ init([]) ->
{type, bag},
{attributes,
record_info(fields, route_multicast)}]),
mnesia:add_table_copy(route_multicast, node(), ram_copies),
mnesia:subscribe({table, route_multicast, simple}),
lists:foreach(
fun(Pid) ->
+78 -34
View File
@@ -22,23 +22,38 @@
%%%-------------------------------------------------------------------
-module(ejabberd_router_redis).
-behaviour(ejabberd_router).
-behaviour(gen_server).
%% API
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
host_of_route/1, is_my_route/1, is_my_host/1, get_all_routes/0,
find_routes/0]).
get_all_routes/0]).
%% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, handle_info/2,
terminate/2, code_change/3, start_link/0]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_router.hrl").
-define(ROUTES_KEY, "ejabberd:routes").
-record(state, {}).
-define(ROUTES_KEY, <<"ejabberd:routes">>).
-define(DOMAINS_KEY, <<"ejabberd:domains">>).
%%%===================================================================
%%% API
%%%===================================================================
init() ->
clean_table().
Spec = {?MODULE, {?MODULE, start_link, []},
transient, 5000, worker, [?MODULE]},
case supervisor:start_child(ejabberd_backend_sup, Spec) of
{ok, _Pid} -> ok;
Err -> Err
end.
-spec start_link() -> {ok, pid()} | {error, any()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
register_route(Domain, ServerHost, LocalHint, _, Pid) ->
DomKey = domain_key(Domain),
@@ -47,7 +62,12 @@ register_route(Domain, ServerHost, LocalHint, _, Pid) ->
case ejabberd_redis:multi(
fun() ->
ejabberd_redis:hset(DomKey, PidKey, T),
ejabberd_redis:sadd(?ROUTES_KEY, [Domain])
ejabberd_redis:sadd(?DOMAINS_KEY, [Domain]),
if Domain /= ServerHost ->
ejabberd_redis:sadd(?ROUTES_KEY, [Domain]);
true ->
ok
end
end) of
{ok, _} ->
ok;
@@ -66,7 +86,8 @@ unregister_route(Domain, _, Pid) ->
{ok, _} = ejabberd_redis:multi(
fun() ->
ejabberd_redis:del([DomKey]),
ejabberd_redis:srem(?ROUTES_KEY, [Domain])
ejabberd_redis:srem(?ROUTES_KEY, [Domain]),
ejabberd_redis:srem(?DOMAINS_KEY, [Domain])
end),
ok;
true ->
@@ -83,47 +104,56 @@ find_routes(Domain) ->
DomKey = domain_key(Domain),
case ejabberd_redis:hgetall(DomKey) of
{ok, Vals} ->
decode_routes(Domain, Vals);
{error, _} ->
[]
end.
host_of_route(Domain) ->
DomKey = domain_key(Domain),
case ejabberd_redis:hgetall(DomKey) of
{ok, [{_Pid, Data}|_]} ->
{ServerHost, _} = binary_to_term(Data),
{ok, ServerHost};
{ok, decode_routes(Domain, Vals)};
_ ->
error
{error, db_failure}
end.
is_my_route(Domain) ->
case ejabberd_redis:sismember(?ROUTES_KEY, Domain) of
{ok, Bool} ->
Bool;
{error, _} ->
false
end.
is_my_host(Domain) ->
{ok, Domain} == host_of_route(Domain).
get_all_routes() ->
case ejabberd_redis:smembers(?ROUTES_KEY) of
{ok, Routes} ->
Routes;
{error, _} ->
[]
{ok, Routes};
_ ->
{error, db_failure}
end.
find_routes() ->
lists:flatmap(fun find_routes/1, get_all_routes()).
get_all_domains() ->
case ejabberd_redis:smembers(?DOMAINS_KEY) of
{ok, Domains} ->
{ok, Domains};
_ ->
{error, db_failure}
end.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
clean_table(),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(Info, State) ->
?ERROR_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
clean_table() ->
?DEBUG("Cleaning Redis route entries...", []),
lists:foreach(
fun(#route{domain = Domain, pid = Pid}) when node(Pid) == node() ->
unregister_route(Domain, undefined, Pid);
@@ -131,6 +161,20 @@ clean_table() ->
ok
end, find_routes()).
find_routes() ->
case get_all_domains() of
{ok, Domains} ->
lists:flatmap(
fun(Domain) ->
case find_routes(Domain) of
{ok, Routes} -> Routes;
{error, _} -> []
end
end, Domains);
{error, _} ->
[]
end.
domain_key(Domain) ->
<<"ejabberd:route:", Domain/binary>>.
+83
View File
@@ -0,0 +1,83 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 15 Apr 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(ejabberd_router_riak).
-behaviour(ejabberd_router).
%% API
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
get_all_routes/0]).
-include("logger.hrl").
-include("ejabberd_router.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init() ->
clean_table().
register_route(Domain, ServerHost, LocalHint, _, Pid) ->
ejabberd_riak:put(#route{domain = Domain,
server_host = ServerHost,
local_hint = LocalHint,
pid = Pid},
route_schema(),
[{i, {Domain, Pid}}, {'2i', [{<<"route">>, Domain}]}]).
unregister_route(Domain, _, Pid) ->
ejabberd_riak:delete(route, {Domain, Pid}).
find_routes(Domain) ->
ejabberd_riak:get_by_index(route, route_schema(), <<"route">>, Domain).
get_all_routes() ->
case ejabberd_riak:get(route, route_schema()) of
{ok, Routes} ->
{ok, lists:flatmap(
fun(#route{domain = D, server_host = S}) when D /= S ->
[D];
(_) ->
[]
end, Routes)};
Err ->
Err
end.
%%%===================================================================
%%% Internal functions
%%%===================================================================
route_schema() ->
{record_info(fields, route), #route{}}.
clean_table() ->
?DEBUG("Cleaning Riak 'route' table...", []),
case ejabberd_riak:get(route, route_schema()) of
{ok, Routes} ->
lists:foreach(
fun(#route{domain = Domain, pid = Pid}) ->
ejabberd_riak:delete(route, {Domain, Pid})
end, Routes);
{error, Err} ->
?ERROR_MSG("failed to clean Riak 'route' table: ~p", [Err]),
Err
end.
+20 -54
View File
@@ -27,8 +27,7 @@
%% API
-export([init/0, register_route/5, unregister_route/3, find_routes/1,
host_of_route/1, is_my_route/1, is_my_host/1, get_all_routes/0,
find_routes/0]).
get_all_routes/0]).
-include("ejabberd.hrl").
-include("logger.hrl").
@@ -40,7 +39,7 @@
%%%===================================================================
init() ->
Node = erlang:atom_to_binary(node(), latin1),
?INFO_MSG("Cleaning SQL 'route' table...", []),
?DEBUG("Cleaning SQL 'route' table...", []),
case ejabberd_sql:sql_query(
?MYNAME, ?SQL("delete from route where node=%(Node)s")) of
{updated, _} ->
@@ -64,18 +63,22 @@ register_route(Domain, ServerHost, LocalHint, _, Pid) ->
ok;
Err ->
?ERROR_MSG("failed to update 'route' table: ~p", [Err]),
{error, Err}
{error, db_failure}
end.
unregister_route(Domain, _, Pid) ->
PidS = misc:encode_pid(Pid),
Node = erlang:atom_to_binary(node(Pid), latin1),
ejabberd_sql:sql_query(
?MYNAME,
?SQL("delete from route where domain=%(Domain)s "
"and pid=%(PidS)s and node=%(Node)s")),
%% TODO: return meaningful error
ok.
case ejabberd_sql:sql_query(
?MYNAME,
?SQL("delete from route where domain=%(Domain)s "
"and pid=%(PidS)s and node=%(Node)s")) of
{updated, _} ->
ok;
Err ->
?ERROR_MSG("failed to delete from 'route' table: ~p", [Err]),
{error, db_failure}
end.
find_routes(Domain) ->
case ejabberd_sql:sql_query(
@@ -83,61 +86,24 @@ find_routes(Domain) ->
?SQL("select @(server_host)s, @(node)s, @(pid)s, @(local_hint)s "
"from route where domain=%(Domain)s")) of
{selected, Rows} ->
lists:flatmap(
fun(Row) ->
row_to_route(Domain, Row)
end, Rows);
{ok, lists:flatmap(
fun(Row) ->
row_to_route(Domain, Row)
end, Rows)};
Err ->
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
{error, Err}
{error, db_failure}
end.
host_of_route(Domain) ->
case ejabberd_sql:sql_query(
?MYNAME,
?SQL("select @(server_host)s from route where domain=%(Domain)s")) of
{selected, [{ServerHost}|_]} ->
{ok, ServerHost};
{selected, []} ->
error;
Err ->
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
error
end.
is_my_route(Domain) ->
case host_of_route(Domain) of
{ok, _} -> true;
_ -> false
end.
is_my_host(Domain) ->
{ok, Domain} == host_of_route(Domain).
get_all_routes() ->
case ejabberd_sql:sql_query(
?MYNAME,
?SQL("select @(domain)s from route where domain <> server_host")) of
{selected, Domains} ->
[Domain || {Domain} <- Domains];
{ok, [Domain || {Domain} <- Domains]};
Err ->
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
[]
end.
find_routes() ->
case ejabberd_sql:sql_query(
?MYNAME,
?SQL("select @(domain)s, @(server_host)s, @(node)s, @(pid)s, "
"@(local_hint)s from route")) of
{selected, Rows} ->
lists:flatmap(
fun({Domain, ServerHost, Node, Pid, LocalHint}) ->
row_to_route(Domain, {ServerHost, Node, Pid, LocalHint})
end, Rows);
Err ->
?ERROR_MSG("failed to select from 'route' table: ~p", [Err]),
[]
{error, db_failure}
end.
%%%===================================================================
+71 -77
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,46 +199,38 @@ dirty_get_connections() ->
-spec tls_options(binary(), [proplists:property()]) -> [proplists:property()].
tls_options(LServer, DefaultOpts) ->
TLSOpts1 = case ejabberd_config:get_option(
{s2s_certfile, LServer},
fun iolist_to_binary/1,
{domain_certfile, LServer},
ejabberd_config:get_option(
{domain_certfile, LServer},
fun iolist_to_binary/1)) of
{s2s_certfile, LServer})) of
undefined -> DefaultOpts;
CertFile -> lists:keystore(certfile, 1, DefaultOpts,
{certfile, CertFile})
end,
TLSOpts2 = case ejabberd_config:get_option(
{s2s_ciphers, LServer},
fun iolist_to_binary/1) of
{s2s_ciphers, LServer}) of
undefined -> TLSOpts1;
Ciphers -> lists:keystore(ciphers, 1, TLSOpts1,
{ciphers, Ciphers})
end,
TLSOpts3 = case ejabberd_config:get_option(
{s2s_protocol_options, LServer},
fun (Options) -> str:join(Options, <<$|>>) end) of
{s2s_protocol_options, LServer}) of
undefined -> TLSOpts2;
ProtoOpts -> lists:keystore(protocol_options, 1, TLSOpts2,
{protocol_options, ProtoOpts})
end,
TLSOpts4 = case ejabberd_config:get_option(
{s2s_dhfile, LServer},
fun iolist_to_binary/1) of
{s2s_dhfile, LServer}) of
undefined -> TLSOpts3;
DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
{dhfile, DHFile})
end,
TLSOpts5 = case ejabberd_config:get_option(
{s2s_cafile, LServer},
fun iolist_to_binary/1) of
{s2s_cafile, LServer}) of
undefined -> TLSOpts4;
CAFile -> lists:keystore(cafile, 1, TLSOpts4,
{cafile, CAFile})
end,
case ejabberd_config:get_option(
{s2s_tls_compression, LServer},
fun(B) when is_boolean(B) -> B end) of
case ejabberd_config:get_option({s2s_tls_compression, LServer}) of
undefined -> TLSOpts5;
false -> [compression_none | TLSOpts5];
true -> lists:delete(compression_none, TLSOpts5)
@@ -261,37 +253,21 @@ tls_enabled(LServer) ->
-spec zlib_enabled(binary()) -> boolean().
zlib_enabled(LServer) ->
ejabberd_config:get_option(
{s2s_zlib, LServer},
fun(B) when is_boolean(B) -> B end,
false).
ejabberd_config:get_option({s2s_zlib, LServer}, false).
-spec use_starttls(binary()) -> boolean() | optional | required | required_trusted.
use_starttls(LServer) ->
ejabberd_config:get_option(
{s2s_use_starttls, LServer},
fun(true) -> true;
(false) -> false;
(optional) -> optional;
(required) -> required;
(required_trusted) -> required_trusted
end, false).
ejabberd_config:get_option({s2s_use_starttls, LServer}, false).
-spec get_idle_timeout(binary()) -> non_neg_integer() | infinity.
get_idle_timeout(LServer) ->
ejabberd_config:get_option(
{s2s_timeout, LServer},
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
(infinity) -> infinity
end, timer:minutes(10)).
ejabberd_config:get_option({s2s_timeout, LServer}, timer:minutes(10)).
-spec queue_type(binary()) -> ram | file.
queue_type(LServer) ->
case ejabberd_config:get_option(
{s2s_queue_type, LServer}, opt_type(s2s_queue_type)) of
undefined -> ejabberd_config:default_queue_type(LServer);
Type -> Type
end.
ejabberd_config:get_option(
{s2s_queue_type, LServer},
ejabberd_config:default_queue_type(LServer)).
%%====================================================================
%% gen_server callbacks
@@ -303,7 +279,6 @@ init([]) ->
[{ram_copies, [node()]},
{type, bag},
{attributes, record_info(fields, s2s)}]),
mnesia:add_table_copy(s2s, node(), ram_copies),
mnesia:subscribe(system),
ejabberd_commands:register_commands(get_commands_spec()),
ejabberd_mnesia:create(?MODULE, temporarily_blocked,
@@ -394,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,
@@ -505,9 +480,13 @@ new_connection(MyServer, Server, From, FromTo,
end,
TRes = mnesia:transaction(F),
case TRes of
{atomic, Pid} ->
ejabberd_s2s_out:connect(Pid),
[Pid];
{atomic, Pid1} ->
if Pid1 == Pid ->
ejabberd_s2s_out:connect(Pid);
true ->
ejabberd_s2s_out:stop(Pid)
end,
[Pid1];
{aborted, Reason} ->
?ERROR_MSG("failed to register connection ~s -> ~s: ~p",
[MyServer, Server, Reason]),
@@ -544,9 +523,7 @@ needed_connections_number(Ls, MaxS2SConnectionsNumber,
-spec is_service(jid(), jid()) -> boolean().
is_service(From, To) ->
LFromDomain = From#jid.lserver,
case ejabberd_config:get_option(
{route_subdomains, LFromDomain},
fun(s2s) -> s2s; (local) -> local end, local) of
case ejabberd_config:get_option({route_subdomains, LFromDomain}, local) of
s2s -> % bypass RFC 3920 10.3
false;
local ->
@@ -569,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() ->
@@ -603,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)
@@ -646,10 +621,7 @@ allow_host(MyServer, S2SHost) ->
not is_temporarly_blocked(S2SHost).
allow_host1(MyHost, S2SHost) ->
Rule = ejabberd_config:get_option(
{s2s_access, MyHost},
fun acl:access_rules_validator/1,
all),
Rule = ejabberd_config:get_option({s2s_access, MyHost}, all),
JID = jid:make(S2SHost),
case acl:match_rule(MyHost, Rule, JID) of
deny -> false;
@@ -710,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];
@@ -719,17 +691,30 @@ get_s2s_state(S2sPid) ->
end,
[{s2s_pid, S2sPid} | Infos].
-type use_starttls() :: boolean() | optional | required | required_trusted.
-spec opt_type(route_subdomains) -> fun((s2s | local) -> s2s | local);
(s2s_access) -> fun((any()) -> any());
(s2s_certfile) -> fun((binary()) -> binary());
(s2s_ciphers) -> fun((binary()) -> binary());
(s2s_dhfile) -> fun((binary()) -> binary());
(s2s_cafile) -> fun((binary()) -> binary());
(s2s_protocol_options) -> fun(([binary()]) -> binary());
(s2s_tls_compression) -> fun((boolean()) -> boolean());
(s2s_use_starttls) -> fun((use_starttls()) -> use_starttls());
(s2s_zlib) -> fun((boolean()) -> boolean());
(s2s_timeout) -> fun((timeout()) -> timeout());
(s2s_queue_type) -> fun((ram | file) -> ram | file);
(atom()) -> [atom()].
opt_type(route_subdomains) ->
fun (s2s) -> s2s;
(local) -> local
end;
opt_type(s2s_access) ->
fun acl:access_rules_validator/1;
opt_type(domain_certfile) -> fun iolist_to_binary/1;
opt_type(s2s_certfile) -> fun iolist_to_binary/1;
opt_type(s2s_certfile) -> fun misc:try_read_file/1;
opt_type(s2s_ciphers) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun iolist_to_binary/1;
opt_type(s2s_cafile) -> fun iolist_to_binary/1;
opt_type(s2s_dhfile) -> fun misc:try_read_file/1;
opt_type(s2s_cafile) -> fun misc:try_read_file/1;
opt_type(s2s_protocol_options) ->
fun (Options) -> str:join(Options, <<"|">>) end;
opt_type(s2s_tls_compression) ->
@@ -741,15 +726,24 @@ 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;
opt_type(s2s_timeout) ->
fun(I) when is_integer(I), I>=0 -> I;
(infinity) -> infinity
fun(I) when is_integer(I), I >= 0 -> timer:seconds(I);
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(s2s_queue_type) ->
fun(ram) -> ram; (file) -> file end;
opt_type(_) ->
[route_subdomains, s2s_access, s2s_certfile,
[route_subdomains, s2s_access, s2s_certfile, s2s_zlib,
s2s_ciphers, s2s_dhfile, s2s_cafile, s2s_protocol_options,
s2s_tls_compression, s2s_use_starttls, s2s_timeout, s2s_queue_type].
+57 -24
View File
@@ -21,13 +21,12 @@
%%%-------------------------------------------------------------------
-module(ejabberd_s2s_in).
-behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
-behaviour(ejabberd_socket).
%% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0]).
%% ejabberd_config callbacks
-export([opt_type/1]).
%% ejabberd_listener callbacks
-export([listen_opt_type/1]).
%% xmpp_stream_in callbacks
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
@@ -42,7 +41,7 @@
-export([handle_unexpected_info/2, handle_unexpected_cast/2,
reject_unauthenticated_packet/2, process_closed/2]).
%% API
-export([stop/1, close/1, send/2, update_state/2, establish/1,
-export([stop/1, close/1, close/2, send/2, update_state/2, establish/1,
host_up/1, host_down/1]).
-include("ejabberd.hrl").
@@ -58,7 +57,10 @@
start(SockData, Opts) ->
case proplists:get_value(supervisor, Opts, true) of
true ->
supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]);
case supervisor:start_child(ejabberd_s2s_in_sup, [SockData, Opts]) of
{ok, undefined} -> ignore;
Res -> Res
end;
_ ->
xmpp_stream_in:start(?MODULE, [SockData, Opts],
ejabberd_config:fsm_limit_opts(Opts))
@@ -71,6 +73,9 @@ start_link(SockData, Opts) ->
close(Ref) ->
xmpp_stream_in:close(Ref).
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
stop(Ref) ->
xmpp_stream_in:stop(Ref).
@@ -162,7 +167,7 @@ handle_stream_start(_StreamStart, #{lserver := LServer} = State) ->
case check_to(jid:make(LServer), State) of
false ->
send(State, xmpp:serr_host_unknown());
true ->
true ->
ServerHost = ejabberd_router:host_of_route(LServer),
State#{server_host => ServerHost}
end.
@@ -184,7 +189,7 @@ handle_auth_success(RServer, Mech, _AuthModule,
[SockMod:pp(Socket), Mech, RServer, LServer,
ejabberd_config:may_hide_data(misc:ip_to_list(IP))]),
State1 = case ejabberd_s2s:allow_host(ServerHost, RServer) of
true ->
true ->
AuthDomains1 = sets:add_element(RServer, AuthDomains),
change_shaper(State, RServer),
State#{auth_domains => AuthDomains1};
@@ -242,25 +247,20 @@ handle_send(Pkt, Result, #{server_host := LServer} = State) ->
State, [Pkt, Result]).
init([State, Opts]) ->
Shaper = gen_mod:get_opt(shaper, Opts, fun acl:shaper_rules_validator/1, none),
Shaper = proplists:get_value(shaper, Opts, none),
TLSOpts1 = lists:filter(
fun({certfile, _}) -> true;
({ciphers, _}) -> true;
({dhfile, _}) -> true;
({cafile, _}) -> true;
({protocol_options, _}) -> true;
(_) -> false
end, Opts),
TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
false -> TLSOpts1;
{_, OptString} ->
ProtoOpts = str:join(OptString, <<$|>>),
[{protocol_options, ProtoOpts}|TLSOpts1]
end,
TLSOpts3 = case proplists:get_bool(tls_compression, Opts) of
false -> [compression_none | TLSOpts2];
true -> TLSOpts2
TLSOpts2 = case proplists:get_bool(tls_compression, Opts) of
false -> [compression_none | TLSOpts1];
true -> TLSOpts1
end,
State1 = State#{tls_options => TLSOpts3,
State1 = State#{tls_options => TLSOpts2,
auth_domains => sets:new(),
xmlns => ?NS_SERVER,
lang => ?MYLANG,
@@ -313,13 +313,13 @@ code_change(_OldVsn, State, _Extra) ->
-spec check_from_to(jid(), jid(), state()) -> ok | {error, stream_error()}.
check_from_to(From, To, State) ->
case check_from(From, State) of
true ->
true ->
case check_to(To, State) of
true ->
true ->
ok;
false ->
false ->
{error, xmpp:serr_host_unknown()}
end;
end;
false ->
{error, xmpp:serr_invalid_from()}
end.
@@ -346,5 +346,38 @@ change_shaper(#{shaper := ShaperName, server_host := ServerHost} = State,
Shaper = acl:match_rule(ServerHost, ShaperName, jid:make(RServer)),
xmpp_stream_in:change_shaper(State, Shaper).
opt_type(_) ->
[].
-spec listen_opt_type(shaper) -> fun((any()) -> any());
(certfile) -> fun((binary()) -> binary());
(ciphers) -> fun((binary()) -> binary());
(dhfile) -> fun((binary()) -> binary());
(cafile) -> fun((binary()) -> binary());
(protocol_options) -> fun(([binary()]) -> binary());
(tls_compression) -> fun((boolean()) -> boolean());
(tls) -> fun((boolean()) -> boolean());
(supervisor) -> fun((boolean()) -> boolean());
(max_stanza_type) -> fun((timeout()) -> timeout());
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
listen_opt_type(shaper) -> fun acl:shaper_rules_validator/1;
listen_opt_type(certfile) ->
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(ciphers) -> ejabberd_s2s:opt_type(s2s_ciphers);
listen_opt_type(dhfile) -> ejabberd_s2s:opt_type(s2s_dhfile);
listen_opt_type(cafile) -> ejabberd_s2s:opt_type(s2s_cafile);
listen_opt_type(protocol_options) -> ejabberd_s2s:opt_type(s2s_protocol_options);
listen_opt_type(tls_compression) -> ejabberd_s2s:opt_type(s2s_tls_compression);
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(supervisor) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(max_stanza_size) ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
listen_opt_type(max_fsm_queue) ->
fun(I) when is_integer(I), I>0 -> I end;
listen_opt_type(_) ->
[shaper, certfile, ciphers, dhfile, cafile, protocol_options,
tls_compression, tls, max_fsm_queue].
+42 -49
View File
@@ -39,7 +39,7 @@
-export([process_auth_result/2, process_closed/2, handle_unexpected_info/2,
handle_unexpected_cast/2, process_downgraded/2]).
%% API
-export([start/3, start_link/3, connect/1, close/1, stop/1, send/2,
-export([start/3, start_link/3, connect/1, close/1, close/2, stop/1, send/2,
route/2, establish/1, update_state/2, host_up/1, host_down/1]).
-include("ejabberd.hrl").
@@ -54,10 +54,13 @@
%%%===================================================================
start(From, To, Opts) ->
case proplists:get_value(supervisor, Opts, true) of
true ->
supervisor:start_child(ejabberd_s2s_out_sup,
[From, To, Opts]);
_ ->
true ->
case supervisor:start_child(ejabberd_s2s_out_sup,
[From, To, Opts]) of
{ok, undefined} -> ignore;
Res -> Res
end;
_ ->
xmpp_stream_out:start(?MODULE, [ejabberd_socket, From, To, Opts],
ejabberd_config:fsm_limit_opts([]))
end.
@@ -75,6 +78,11 @@ connect(Ref) ->
close(Ref) ->
xmpp_stream_out:close(Ref).
-spec close(pid(), atom()) -> ok;
(state(), atom()) -> state().
close(Ref, Reason) ->
xmpp_stream_out:close(Ref, Reason).
-spec stop(pid()) -> ok;
(state()) -> no_return().
stop(Ref) ->
@@ -186,42 +194,21 @@ tls_enabled(#{server := LServer}) ->
connect_timeout(#{server := LServer}) ->
ejabberd_config:get_option(
{outgoing_s2s_timeout, LServer},
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
timer:seconds(TimeOut);
(infinity) ->
infinity
end, timer:seconds(10)).
timer:seconds(10)).
default_port(#{server := LServer}) ->
ejabberd_config:get_option(
{outgoing_s2s_port, LServer},
fun(I) when is_integer(I), I > 0, I =< 65536 -> I end,
5269).
ejabberd_config:get_option({outgoing_s2s_port, LServer}, 5269).
address_families(#{server := LServer}) ->
ejabberd_config:get_option(
{outgoing_s2s_families, LServer},
fun(Families) ->
lists:map(
fun(ipv4) -> inet;
(ipv6) -> inet6
end, Families)
end, [inet, inet6]).
[inet, inet6]).
dns_retries(#{server := LServer}) ->
ejabberd_config:get_option(
{s2s_dns_retries, LServer},
fun(I) when is_integer(I), I>=0 -> I end,
2).
ejabberd_config:get_option({s2s_dns_retries, LServer}, 2).
dns_timeout(#{server := LServer}) ->
ejabberd_config:get_option(
{s2s_dns_timeout, LServer},
fun(I) when is_integer(I), I>=0 ->
timer:seconds(I);
(infinity) ->
infinity
end, timer:seconds(10)).
ejabberd_config:get_option({s2s_dns_timeout, LServer}, timer:seconds(10)).
handle_auth_success(Mech, #{sockmod := SockMod,
socket := Socket, ip := IP,
@@ -386,11 +373,8 @@ mk_bounce_error(_Lang, _State) ->
-spec get_delay() -> non_neg_integer().
get_delay() ->
MaxDelay = ejabberd_config:get_option(
s2s_max_retry_delay,
fun(I) when is_integer(I), I > 0 -> I end,
300),
crypto:rand_uniform(1, MaxDelay).
MaxDelay = ejabberd_config:get_option(s2s_max_retry_delay, 300),
randoms:uniform(MaxDelay).
-spec set_idle_timeout(state()) -> state().
set_idle_timeout(#{on_route := send, server := LServer} = State) ->
@@ -458,27 +442,36 @@ maybe_report_huge_timeout(Opt, T) when is_integer(T), T >= 1000 ->
maybe_report_huge_timeout(_, _) ->
ok.
-spec opt_type(outgoing_s2s_families) -> fun(([ipv4|ipv6]) -> [inet|inet6]);
(outgoing_s2s_port) -> fun((0..65535) -> 0..65535);
(outgoing_s2s_timeout) -> fun((timeout()) -> timeout());
(s2s_dns_retries) -> fun((non_neg_integer()) -> non_neg_integer());
(s2s_dns_timeout) -> fun((timeout()) -> timeout());
(s2s_max_retry_delay) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
opt_type(outgoing_s2s_families) ->
fun (Families) ->
true = lists:all(fun (ipv4) -> true;
(ipv6) -> true
end,
Families),
Families
fun(Families) ->
lists:map(
fun(ipv4) -> inet;
(ipv6) -> inet6
end, Families)
end;
opt_type(outgoing_s2s_port) ->
fun (I) when is_integer(I), I > 0, I =< 65536 -> I end;
fun (I) when is_integer(I), I > 0, I < 65536 -> I end;
opt_type(outgoing_s2s_timeout) ->
fun (TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
TimeOut;
(infinity) -> infinity
fun(TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
timer:seconds(TimeOut);
(unlimited) ->
infinity;
(infinity) ->
infinity
end;
opt_type(s2s_dns_retries) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(s2s_dns_timeout) ->
fun (TimeOut) when is_integer(TimeOut), TimeOut > 0 ->
TimeOut;
(infinity) -> infinity
fun(I) when is_integer(I), I>=0 -> timer:seconds(I);
(infinity) -> infinity;
(unlimited) -> infinity
end;
opt_type(s2s_max_retry_delay) ->
fun (I) when is_integer(I), I > 0 -> I end;
+84 -35
View File
@@ -21,15 +21,14 @@
%%%-------------------------------------------------------------------
-module(ejabberd_service).
-behaviour(xmpp_stream_in).
-behaviour(ejabberd_config).
-behaviour(ejabberd_socket).
-protocol({xep, 114, '1.6'}).
%% ejabberd_socket callbacks
-export([start/2, start_link/2, socket_type/0]).
%% ejabberd_config callbacks
-export([opt_type/1, transform_listen_option/2]).
-export([start/2, start_link/2, socket_type/0, close/1, close/2]).
%% ejabberd_listener callbacks
-export([listen_opt_type/1, transform_listen_option/2]).
%% xmpp_stream_in callbacks
-export([init/1, handle_info/2, terminate/2, code_change/3]).
-export([handle_stream_start/2, handle_auth_success/4, handle_auth_failure/4,
@@ -63,6 +62,16 @@ socket_type() ->
send(Stream, Pkt) ->
xmpp_stream_in:send(Stream, Pkt).
-spec close(pid()) -> ok;
(state()) -> state().
close(Ref) ->
xmpp_stream_in:close(Ref).
-spec close(pid(), atom()) -> ok;
(state(), atom()) -> state().
close(Ref, Reason) ->
xmpp_stream_in:close(Ref, Reason).
%%%===================================================================
%%% xmpp_stream_in callbacks
%%%===================================================================
@@ -70,49 +79,33 @@ tls_options(#{tls_options := TLSOptions}) ->
TLSOptions.
init([State, Opts]) ->
Access = gen_mod:get_opt(access, Opts, fun acl:access_rules_validator/1, all),
Shaper = gen_mod:get_opt(shaper_rule, Opts, fun acl:shaper_rules_validator/1, none),
HostOpts = case lists:keyfind(hosts, 1, Opts) of
{hosts, HOpts} ->
lists:foldl(
fun({H, Os}, D) ->
P = proplists:get_value(
password, Os,
str:sha(randoms:bytes(20))),
dict:store(H, P, D)
end, dict:new(), HOpts);
false ->
Pass = proplists:get_value(
password, Opts,
str:sha(randoms:bytes(20))),
dict:from_list([{global, Pass}])
end,
CheckFrom = gen_mod:get_opt(check_from, Opts,
fun(Flag) when is_boolean(Flag) -> Flag end,
true),
Access = proplists:get_value(access, Opts, all),
Shaper = proplists:get_value(shaper_rule, Opts, none),
GlobalPassword = proplists:get_value(password, Opts, random_password()),
HostOpts = proplists:get_value(hosts, Opts, [{global, GlobalPassword}]),
HostOpts1 = lists:map(
fun({Host, undefined}) -> {Host, GlobalPassword};
({Host, Password}) -> {Host, Password}
end, HostOpts),
CheckFrom = proplists:get_value(check_from, Opts, true),
TLSOpts1 = lists:filter(
fun({certfile, _}) -> true;
({ciphers, _}) -> true;
({dhfile, _}) -> true;
({cafile, _}) -> true;
({protocol_options, _}) -> true;
(_) -> false
end, Opts),
TLSOpts2 = case lists:keyfind(protocol_options, 1, Opts) of
false -> TLSOpts1;
{_, OptString} ->
ProtoOpts = str:join(OptString, <<$|>>),
[{protocol_options, ProtoOpts}|TLSOpts1]
end,
TLSOpts = case proplists:get_bool(tls_compression, Opts) of
false -> [compression_none | TLSOpts2];
true -> TLSOpts2
false -> [compression_none | TLSOpts1];
true -> TLSOpts1
end,
xmpp_stream_in:change_shaper(State, Shaper),
State1 = State#{access => Access,
xmlns => ?NS_COMPONENT,
lang => ?MYLANG,
server => ?MYNAME,
host_opts => HostOpts,
host_opts => dict:from_list(HostOpts1),
stream_version => undefined,
tls_options => TLSOpts,
check_from => CheckFrom},
@@ -206,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;
@@ -244,6 +237,9 @@ check_from(From, #{host_opts := HostOpts}) ->
Server = From#jid.lserver,
dict:is_key(Server, HostOpts).
random_password() ->
str:sha(randoms:bytes(20)).
transform_listen_option({hosts, Hosts, O}, Opts) ->
case lists:keyfind(hosts, 1, Opts) of
{_, PrevHostOpts} ->
@@ -262,4 +258,57 @@ transform_listen_option({host, Host, Os}, Opts) ->
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
opt_type(_) -> [].
-spec listen_opt_type(access) -> fun((any()) -> any());
(shaper_rule) -> fun((any()) -> any());
(certfile) -> fun((binary()) -> binary());
(ciphers) -> fun((binary()) -> binary());
(dhfile) -> fun((binary()) -> binary());
(cafile) -> fun((binary()) -> binary());
(protocol_options) -> fun(([binary()]) -> binary());
(tls_compression) -> fun((boolean()) -> boolean());
(tls) -> fun((boolean()) -> boolean());
(check_from) -> fun((boolean()) -> boolean());
(password) -> fun((boolean()) -> boolean());
(hosts) -> fun(([{binary(), [{password, binary()}]}]) ->
[{binary(), binary() | undefined}]);
(max_stanza_type) -> fun((timeout()) -> timeout());
(max_fsm_queue) -> fun((pos_integer()) -> pos_integer());
(atom()) -> [atom()].
listen_opt_type(access) -> fun acl:access_rules_validator/1;
listen_opt_type(shaper_rule) -> fun acl:shaper_rules_validator/1;
listen_opt_type(certfile) ->
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(ciphers) -> fun iolist_to_binary/1;
listen_opt_type(dhfile) -> fun misc:try_read_file/1;
listen_opt_type(cafile) -> fun misc:try_read_file/1;
listen_opt_type(protocol_options) ->
fun(Options) -> str:join(Options, <<"|">>) end;
listen_opt_type(tls_compression) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(tls) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(check_from) -> fun(B) when is_boolean(B) -> B end;
listen_opt_type(password) -> fun iolist_to_binary/1;
listen_opt_type(hosts) ->
fun(HostOpts) ->
lists:map(
fun({Host, Opts}) ->
Password = case proplists:get_value(password, Opts) of
undefined -> undefined;
P -> iolist_to_binary(P)
end,
{iolist_to_binary(Host), Password}
end, HostOpts)
end;
listen_opt_type(max_stanza_size) ->
fun(I) when is_integer(I) -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
listen_opt_type(max_fsm_queue) ->
fun(I) when is_integer(I), I>0 -> I end;
listen_opt_type(_) ->
[access, shaper_rule, certfile, ciphers, dhfile, cafile, tls,
protocol_options, tls_compression, password, hosts, check_from,
max_fsm_queue].
+77
View File
@@ -0,0 +1,77 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 30 Apr 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2013-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_sip).
-ifndef(SIP).
-include("logger.hrl").
-export([socket_type/0, start/2, listen_opt_type/1]).
log_error() ->
?CRITICAL_MSG("ejabberd is not compiled with SIP support", []).
socket_type() ->
log_error(),
raw.
listen_opt_type(_) ->
log_error(),
[].
start(_, _) ->
log_error(),
{error, sip_not_compiled}.
-else.
%% API
-export([tcp_init/2, udp_init/2, udp_recv/5, start/2,
socket_type/0, listen_opt_type/1]).
%%%===================================================================
%%% API
%%%===================================================================
tcp_init(Socket, Opts) ->
ejabberd:start_app(esip),
esip_socket:tcp_init(Socket, Opts).
udp_init(Socket, Opts) ->
ejabberd:start_app(esip),
esip_socket:udp_init(Socket, Opts).
udp_recv(Sock, Addr, Port, Data, Opts) ->
esip_socket:udp_recv(Sock, Addr, Port, Data, Opts).
start(Opaque, Opts) ->
esip_socket:start(Opaque, Opts).
socket_type() ->
raw.
listen_opt_type(certfile) ->
fun(S) ->
ejabberd_pkix:add_certfile(S),
iolist_to_binary(S)
end;
listen_opt_type(tls) ->
fun(B) when is_boolean(B) -> B end;
listen_opt_type(_) ->
[tls, certfile].
%%%===================================================================
%%% Internal functions
%%%===================================================================
-endif.
+247 -61
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,
@@ -76,7 +78,9 @@
c2s_handle_info/2,
host_up/1,
host_down/1,
make_sid/0
make_sid/0,
clean_cache/1,
config_reloaded/0
]).
-export([init/1, handle_call/3, handle_cast/2,
@@ -91,13 +95,15 @@
-include("ejabberd_sm.hrl").
-callback init() -> ok | {error, any()}.
-callback set_session(#session{}) -> ok.
-callback delete_session(binary(), binary(), binary(), sid()) ->
{ok, #session{}} | {error, notfound}.
-callback set_session(#session{}) -> ok | {error, any()}.
-callback delete_session(#session{}) -> ok | {error, any()}.
-callback get_sessions() -> [#session{}].
-callback get_sessions(binary()) -> [#session{}].
-callback get_sessions(binary(), binary()) -> [#session{}].
-callback get_sessions(binary(), binary(), binary()) -> [#session{}].
-callback get_sessions(binary(), binary()) -> {ok, [#session{}]} | {error, any()}.
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-optional_callbacks([use_cache/1, cache_nodes/1]).
-record(state, {}).
@@ -158,9 +164,13 @@ close_session(SID, User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
Info = case Mod:delete_session(LUser, LServer, LResource, SID) of
{ok, #session{info = I}} -> I;
{error, notfound} -> []
Sessions = get_sessions(Mod, LUser, LServer, LResource),
Info = case lists:keyfind(SID, #session.sid, Sessions) of
#session{info = I} = Session ->
delete_session(Mod, Session),
I;
_ ->
[]
end,
JID = jid:make(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook,
@@ -170,7 +180,7 @@ close_session(SID, User, Server, Resource) ->
subscribe | subscribed | unsubscribe | unsubscribed,
binary()) -> boolean() | {stop, false}.
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
case ejabberd_auth:user_exists(User, Server) of
true -> Acc;
false -> {stop, false}
end.
@@ -196,14 +206,14 @@ get_user_resources(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
Ss = online(get_sessions(Mod, LUser, LServer)),
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
get_user_present_resources(LUser, LServer) ->
Mod = get_sm_backend(LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
Ss = online(get_sessions(Mod, LUser, LServer)),
[{S#session.priority, element(3, S#session.usr)}
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
@@ -214,7 +224,7 @@ get_user_ip(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer, LResource)) of
case online(get_sessions(Mod, LUser, LServer, LResource)) of
[] ->
undefined;
Ss ->
@@ -227,7 +237,7 @@ get_user_info(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
Ss = online(get_sessions(Mod, LUser, LServer)),
[{LResource, [{node, node(Pid)}|Info]}
|| #session{usr = {_, _, LResource},
info = Info,
@@ -240,7 +250,7 @@ get_user_info(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer, LResource)) of
case online(get_sessions(Mod, LUser, LServer, LResource)) of
[] ->
offline;
Ss ->
@@ -284,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(Mod:get_sessions(LUser, LServer, LResource)) of
[#session{sid = {_, Pid}}] -> Pid;
case online(get_sessions(Mod, LUser, LServer, LResource)) of
[#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) ->
@@ -309,11 +336,11 @@ get_offline_info(Time, User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
case get_sessions(Mod, LUser, LServer, LResource) of
[#session{sid = {Time, _}, info = Info}] ->
case proplists:get_bool(offline, Info) of
true ->
Info;
Info;
false ->
none
end;
@@ -326,7 +353,7 @@ get_offline_info(Time, User, Server, Resource) ->
dirty_get_sessions_list() ->
lists:flatmap(
fun(Mod) ->
[S#session.usr || S <- online(Mod:get_sessions())]
[S#session.usr || S <- online(get_sessions(Mod))]
end, get_sm_backends()).
-spec dirty_get_my_sessions_list() -> [#session{}].
@@ -334,7 +361,7 @@ dirty_get_sessions_list() ->
dirty_get_my_sessions_list() ->
lists:flatmap(
fun(Mod) ->
[S || S <- online(Mod:get_sessions()),
[S || S <- online(get_sessions(Mod)),
node(element(2, S#session.sid)) == node()]
end, get_sm_backends()).
@@ -343,14 +370,14 @@ dirty_get_my_sessions_list() ->
get_vh_session_list(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
[S#session.usr || S <- online(Mod:get_sessions(LServer))].
[S#session.usr || S <- online(get_sessions(Mod, LServer))].
-spec get_all_pids() -> [pid()].
get_all_pids() ->
lists:flatmap(
fun(Mod) ->
[element(2, S#session.sid) || S <- online(Mod:get_sessions())]
[element(2, S#session.sid) || S <- online(get_sessions(Mod))]
end, get_sm_backends()).
-spec get_vh_session_number(binary()) -> non_neg_integer().
@@ -358,7 +385,7 @@ get_all_pids() ->
get_vh_session_number(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
length(online(Mod:get_sessions(LServer))).
length(online(get_sessions(Mod, LServer))).
-spec register_iq_handler(binary(), binary(), atom(), atom(), list()) -> ok.
@@ -387,16 +414,23 @@ c2s_handle_info(#{lang := Lang} = State, {exit, Reason}) ->
c2s_handle_info(State, _) ->
State.
-spec config_reloaded() -> ok.
config_reloaded() ->
init_cache().
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
process_flag(trap_exit, true),
init_cache(),
lists:foreach(fun(Mod) -> Mod:init() end, get_sm_backends()),
clean_cache(),
ets:new(sm_iqtable, [named_table, public, {read_concurrency, true}]),
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:add(host_down, ?MODULE, host_down, 60),
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
lists:foreach(fun host_up/1, ?MYHOSTS),
ejabberd_commands:register_commands(get_commands_spec()),
{ok, #state{}}.
@@ -432,6 +466,7 @@ terminate(_Reason, _State) ->
lists:foreach(fun host_down/1, ?MYHOSTS),
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50),
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 60),
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50),
ejabberd_commands:unregister_commands(get_commands_spec()),
ok.
@@ -455,12 +490,17 @@ 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
end, Mod:get_sessions(Host)),
end, get_sessions(Mod, Host)),
ejabberd_hooks:delete(c2s_handle_info, Host,
ejabberd_sm, c2s_handle_info, 50),
ejabberd_hooks:delete(roster_in_subscription, Host,
@@ -472,7 +512,7 @@ host_down(Host) ->
ejabberd_c2s:host_down(Host).
-spec set_session(sid(), binary(), binary(), binary(),
prio(), info()) -> ok.
prio(), info()) -> ok | {error, any()}.
set_session(SID, User, Server, Resource, Priority, Info) ->
LUser = jid:nodeprep(User),
@@ -481,8 +521,69 @@ set_session(SID, User, Server, Resource, Priority, Info) ->
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
Mod = get_sm_backend(LServer),
Mod:set_session(#session{sid = SID, usr = USR, us = US,
priority = Priority, info = Info}).
case Mod:set_session(#session{sid = SID, usr = USR, us = US,
priority = Priority, info = Info}) of
ok ->
case use_cache(Mod, LServer) of
true ->
ets_cache:delete(?SM_CACHE, {LUser, LServer},
cache_nodes(Mod, LServer));
false ->
ok
end;
{error, _} = Err ->
Err
end.
-spec get_sessions(module()) -> [#session{}].
get_sessions(Mod) ->
Mod:get_sessions().
-spec get_sessions(module(), binary()) -> [#session{}].
get_sessions(Mod, LServer) ->
Mod:get_sessions(LServer).
-spec get_sessions(module(), binary(), binary()) -> [#session{}].
get_sessions(Mod, LUser, LServer) ->
case use_cache(Mod, LServer) of
true ->
case ets_cache:lookup(
?SM_CACHE, {LUser, LServer},
fun() ->
case Mod:get_sessions(LUser, LServer) of
{ok, Ss} when Ss /= [] ->
{ok, Ss};
_ ->
error
end
end) of
{ok, Sessions} ->
Sessions;
error ->
[]
end;
false ->
case Mod:get_sessions(LUser, LServer) of
{ok, Ss} -> Ss;
_ -> []
end
end.
-spec get_sessions(module(), binary(), binary(), binary()) -> [#session{}].
get_sessions(Mod, LUser, LServer, LResource) ->
Sessions = get_sessions(Mod, LUser, LServer),
[S || S <- Sessions, element(3, S#session.usr) == LResource].
-spec delete_session(module(), #session{}) -> ok.
delete_session(Mod, #session{usr = {LUser, LServer, _}} = Session) ->
Mod:delete_session(Session),
case use_cache(Mod, LServer) of
true ->
ets_cache:delete(?SM_CACHE, {LUser, LServer},
cache_nodes(Mod, LServer));
false ->
ok
end.
-spec online([#session{}]) -> [#session{}].
@@ -505,7 +606,7 @@ do_route(To, Term) ->
?DEBUG("broadcasting ~p to ~s", [Term, jid:encode(To)]),
{U, S, R} = jid:tolower(To),
Mod = get_sm_backend(S),
case online(Mod:get_sessions(U, S, R)) of
case online(get_sessions(Mod, U, S, R)) of
[] ->
?DEBUG("dropping broadcast to unavailable resourse: ~p", [Term]);
Ss ->
@@ -541,7 +642,7 @@ do_route(#presence{from = From, to = To, type = T, status = Status} = Packet)
ejabberd_c2s:route(Pid, {route, Packet1});
(_) ->
ok
end, online(Mod:get_sessions(LUser, LServer)));
end, online(get_sessions(Mod, LUser, LServer)));
false ->
ok
end;
@@ -570,7 +671,7 @@ do_route(Packet) ->
To = xmpp:get_to(Packet),
{LUser, LServer, LResource} = jid:tolower(To),
Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer, LResource)) of
case online(get_sessions(Mod, LUser, LServer, LResource)) of
[] ->
case Packet of
#message{type = T} when T == chat; T == normal ->
@@ -618,7 +719,7 @@ route_message(#message{to = To, type = Type} = Packet) ->
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
Mod = get_sm_backend(LServer),
case online(Mod:get_sessions(LUser, LServer,
case online(get_sessions(Mod, LUser, LServer,
LResource)) of
[] ->
ok; % Race condition
@@ -638,7 +739,7 @@ route_message(#message{to = To, type = Type} = Packet) ->
end,
PrioRes);
_ ->
case ejabberd_auth:is_user_exists(LUser, LServer) andalso
case ejabberd_auth:user_exists(LUser, LServer) andalso
is_privacy_allow(Packet) of
true ->
ejabberd_hooks:run_fold(offline_message_hook,
@@ -689,10 +790,10 @@ check_for_sessions_to_replace(User, Server, Resource) ->
-spec check_existing_resources(binary(), binary(), binary()) -> ok.
check_existing_resources(LUser, LServer, LResource) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer, LResource),
Ss = get_sessions(Mod, LUser, LServer, LResource),
{OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
lists:foreach(fun(#session{sid = S}) ->
Mod:delete_session(LUser, LServer, LResource, S)
lists:foreach(fun(S) ->
delete_session(Mod, S)
end, OfflineSs),
if OnlineSs == [] -> ok;
true ->
@@ -716,12 +817,12 @@ get_resource_sessions(User, Server, Resource) ->
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
[S#session.sid || S <- online(Mod:get_sessions(LUser, LServer, LResource))].
[S#session.sid || S <- online(get_sessions(Mod, LUser, LServer, LResource))].
-spec check_max_sessions(binary(), binary()) -> ok | replaced.
check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
Ss = get_sessions(Mod, LUser, LServer),
{OnlineSs, OfflineSs} = lists:partition(fun is_online/1, Ss),
MaxSessions = get_max_user_sessions(LUser, LServer),
if length(OnlineSs) =< MaxSessions -> ok;
@@ -731,8 +832,7 @@ check_max_sessions(LUser, LServer) ->
end,
if length(OfflineSs) =< MaxSessions -> ok;
true ->
#session{sid = SID, usr = {_, _, R}} = lists:min(OfflineSs),
Mod:delete_session(LUser, LServer, R, SID)
delete_session(Mod, lists:min(OfflineSs))
end.
%% Get the user_max_session setting
@@ -779,23 +879,18 @@ process_iq(#iq{}) ->
force_update_presence({LUser, LServer}) ->
Mod = get_sm_backend(LServer),
Ss = online(Mod:get_sessions(LUser, LServer)),
Ss = online(get_sessions(Mod, LUser, LServer)),
lists:foreach(fun (#session{sid = {_, Pid}}) ->
ejabberd_c2s:route(Pid, force_update_presence)
ejabberd_c2s:resend_presence(Pid)
end,
Ss).
-spec get_sm_backend(binary()) -> module().
get_sm_backend(Host) ->
DBType = case ejabberd_config:get_option(
DBType = ejabberd_config:get_option(
{sm_db_type, Host},
fun(T) -> ejabberd_config:v_db(?MODULE, T) end) of
undefined ->
ejabberd_config:default_ram_db(Host, ?MODULE);
T ->
T
end,
ejabberd_config:default_ram_db(Host, ?MODULE)),
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
-spec get_sm_backends() -> [module()].
@@ -811,34 +906,110 @@ get_vh_by_backend(Mod) ->
get_sm_backend(Host) == Mod
end, ?MYHOSTS).
%%--------------------------------------------------------------------
%%% Cache stuff
%%--------------------------------------------------------------------
-spec init_cache() -> ok.
init_cache() ->
case use_cache() of
true ->
ets_cache:new(?SM_CACHE, cache_opts());
false ->
ets_cache:delete(?SM_CACHE)
end.
-spec cache_opts() -> [proplists:property()].
cache_opts() ->
MaxSize = ejabberd_config:get_option(
sm_cache_size,
ejabberd_config:cache_size(global)),
CacheMissed = ejabberd_config:get_option(
sm_cache_missed,
ejabberd_config:cache_missed(global)),
LifeTime = case ejabberd_config:get_option(
sm_cache_life_time,
ejabberd_config:cache_life_time(global)) of
infinity -> infinity;
I -> timer:seconds(I)
end,
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
-spec clean_cache(node()) -> ok.
clean_cache(Node) ->
ets_cache:filter(
?SM_CACHE,
fun(_, error) ->
false;
(_, {ok, Ss}) ->
not lists:any(
fun(#session{sid = {_, Pid}}) ->
node(Pid) == Node
end, Ss)
end).
-spec clean_cache() -> ok.
clean_cache() ->
ejabberd_cluster:eval_everywhere(?MODULE, clean_cache, [node()]).
-spec use_cache(module(), binary()) -> boolean().
use_cache(Mod, LServer) ->
case erlang:function_exported(Mod, use_cache, 1) of
true -> Mod:use_cache(LServer);
false ->
ejabberd_config:get_option(
{sm_use_cache, LServer},
ejabberd_config:use_cache(LServer))
end.
-spec use_cache() -> boolean().
use_cache() ->
lists:any(
fun(Host) ->
Mod = get_sm_backend(Host),
use_cache(Mod, Host)
end, ?MYHOSTS).
-spec cache_nodes(module(), binary()) -> [node()].
cache_nodes(Mod, LServer) ->
case erlang:function_exported(Mod, cache_nodes, 1) of
true -> Mod:cache_nodes(LServer);
false -> ejabberd_cluster:get_nodes()
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% 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()].
@@ -868,5 +1039,20 @@ kick_user(User, Server) ->
make_sid() ->
{p1_time_compat:unique_timestamp(), self()}.
-spec opt_type(sm_db_type) -> fun((atom()) -> atom());
(sm_use_cache) -> fun((boolean()) -> boolean());
(sm_cache_missed) -> fun((boolean()) -> boolean());
(sm_cache_size) -> fun((timeout()) -> timeout());
(sm_cache_life_time) -> fun((timeout()) -> timeout());
(atom()) -> [atom()].
opt_type(sm_db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
opt_type(_) -> [sm_db_type].
opt_type(O) when O == sm_use_cache; O == sm_cache_missed ->
fun(B) when is_boolean(B) -> B end;
opt_type(O) when O == sm_cache_size; O == sm_cache_life_time ->
fun(I) when is_integer(I), I>0 -> I;
(unlimited) -> infinity;
(infinity) -> infinity
end;
opt_type(_) ->
[sm_db_type, sm_use_cache, sm_cache_size, sm_cache_missed,
sm_cache_life_time].
+12 -19
View File
@@ -29,12 +29,12 @@
%% API
-export([init/0,
use_cache/1,
set_session/1,
delete_session/4,
delete_session/1,
get_sessions/0,
get_sessions/1,
get_sessions/2,
get_sessions/3]).
get_sessions/2]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
@@ -62,20 +62,17 @@ init() ->
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec use_cache(binary()) -> boolean().
use_cache(_LServer) ->
false.
-spec set_session(#session{}) -> ok.
set_session(Session) ->
mnesia:dirty_write(Session).
-spec delete_session(binary(), binary(), binary(), sid()) ->
{ok, #session{}} | {error, notfound}.
delete_session(_LUser, _LServer, _LResource, SID) ->
case mnesia:dirty_read(session, SID) of
[Session] ->
mnesia:dirty_delete(session, SID),
{ok, Session};
[] ->
{error, notfound}
end.
-spec delete_session(#session{}) -> ok.
delete_session(#session{sid = SID}) ->
mnesia:dirty_delete(session, SID).
-spec get_sessions() -> [#session{}].
get_sessions() ->
@@ -87,13 +84,9 @@ get_sessions(LServer) ->
[{#session{usr = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}], ['$_']}]).
-spec get_sessions(binary(), binary()) -> [#session{}].
-spec get_sessions(binary(), binary()) -> {ok, [#session{}]}.
get_sessions(LUser, LServer) ->
mnesia:dirty_index_read(session, {LUser, LServer}, #session.us).
-spec get_sessions(binary(), binary(), binary()) -> [#session{}].
get_sessions(LUser, LServer, LResource) ->
mnesia:dirty_index_read(session, {LUser, LServer, LResource}, #session.usr).
{ok, mnesia:dirty_index_read(session, {LUser, LServer}, #session.us)}.
%%%===================================================================
%%% gen_server callbacks
+95 -68
View File
@@ -23,63 +23,85 @@
%%%----------------------------------------------------------------------
-module(ejabberd_sm_redis).
-behaviour(ejabberd_config).
-ifndef(GEN_SERVER).
-define(GEN_SERVER, p1_server).
-endif.
-behaviour(?GEN_SERVER).
-behaviour(ejabberd_sm).
-export([init/0, set_session/1, delete_session/4,
-export([init/0, set_session/1, delete_session/1,
get_sessions/0, get_sessions/1, get_sessions/2,
get_sessions/3, opt_type/1]).
cache_nodes/1]).
%% gen_server callbacks
-export([init/1, handle_cast/2, handle_call/3, handle_info/2,
terminate/2, code_change/3, start_link/0]).
-include("ejabberd.hrl").
-include("ejabberd_sm.hrl").
-include("logger.hrl").
-define(SM_KEY, <<"ejabberd:sm">>).
-record(state, {}).
%%%===================================================================
%%% API
%%%===================================================================
-spec init() -> ok | {error, any()}.
init() ->
clean_table().
Spec = {?MODULE, {?MODULE, start_link, []},
transient, 5000, worker, [?MODULE]},
case supervisor:start_child(ejabberd_backend_sup, Spec) of
{ok, _Pid} -> ok;
Err -> Err
end.
-spec set_session(#session{}) -> ok.
-spec start_link() -> {ok, pid()} | {error, any()}.
start_link() ->
?GEN_SERVER:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec cache_nodes(binary()) -> [node()].
cache_nodes(_LServer) ->
[node()].
-spec set_session(#session{}) -> ok | {error, ejabberd_redis:error_reason()}.
set_session(Session) ->
T = term_to_binary(Session),
USKey = us_to_key(Session#session.us),
SIDKey = sid_to_key(Session#session.sid),
ServKey = server_to_key(element(2, Session#session.us)),
USSIDKey = us_sid_to_key(Session#session.us, Session#session.sid),
ejabberd_redis:multi(
fun() ->
ejabberd_redis:hset(USKey, SIDKey, T),
ejabberd_redis:hset(ServKey, USSIDKey, T)
end),
ok.
case ejabberd_redis:multi(
fun() ->
ejabberd_redis:hset(USKey, SIDKey, T),
ejabberd_redis:hset(ServKey, USSIDKey, T),
ejabberd_redis:publish(
?SM_KEY, term_to_binary({delete, Session#session.us}))
end) of
{ok, _} ->
ok;
Err ->
Err
end.
-spec delete_session(binary(), binary(), binary(), sid()) ->
{ok, #session{}} | {error, notfound}.
delete_session(LUser, LServer, _LResource, SID) ->
USKey = us_to_key({LUser, LServer}),
case ejabberd_redis:hgetall(USKey) of
{ok, Vals} ->
Ss = decode_session_list(Vals),
case lists:keyfind(SID, #session.sid, Ss) of
false ->
{error, notfound};
Session ->
SIDKey = sid_to_key(SID),
ServKey = server_to_key(element(2, Session#session.us)),
USSIDKey = us_sid_to_key(Session#session.us, SID),
ejabberd_redis:multi(
fun() ->
ejabberd_redis:hdel(USKey, [SIDKey]),
ejabberd_redis:hdel(ServKey, [USSIDKey])
end),
{ok, Session}
end;
{error, _} ->
{error, notfound}
-spec delete_session(#session{}) -> ok | {error, ejabberd_redis:error_reason()}.
delete_session(#session{sid = SID} = Session) ->
USKey = us_to_key(Session#session.us),
SIDKey = sid_to_key(SID),
ServKey = server_to_key(element(2, Session#session.us)),
USSIDKey = us_sid_to_key(Session#session.us, SID),
case ejabberd_redis:multi(
fun() ->
ejabberd_redis:hdel(USKey, [SIDKey]),
ejabberd_redis:hdel(ServKey, [USSIDKey]),
ejabberd_redis:publish(
?SM_KEY,
term_to_binary({delete, Session#session.us}))
end) of
{ok, _} ->
ok;
Err ->
Err
end.
-spec get_sessions() -> [#session{}].
@@ -99,34 +121,53 @@ get_sessions(LServer) ->
[]
end.
-spec get_sessions(binary(), binary()) -> [#session{}].
-spec get_sessions(binary(), binary()) -> {ok, [#session{}]} |
{error, ejabberd_redis:error_reason()}.
get_sessions(LUser, LServer) ->
USKey = us_to_key({LUser, LServer}),
case ejabberd_redis:hgetall(USKey) of
{ok, Vals} ->
decode_session_list(Vals);
{error, _} ->
[]
{ok, decode_session_list(Vals)};
Err ->
Err
end.
-spec get_sessions(binary(), binary(), binary()) ->
[#session{}].
get_sessions(LUser, LServer, LResource) ->
USKey = us_to_key({LUser, LServer}),
case ejabberd_redis:hgetall(USKey) of
{ok, Vals} ->
[S || S <- decode_session_list(Vals),
element(3, S#session.usr) == LResource];
{error, _} ->
[]
end.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
ejabberd_redis:subscribe([?SM_KEY]),
clean_table(),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({redis_message, ?SM_KEY, Data}, State) ->
case binary_to_term(Data) of
{delete, Key} ->
ets_cache:delete(?SM_CACHE, Key);
Msg ->
?WARNING_MSG("unexpected redis message: ~p", [Msg])
end,
{noreply, State};
handle_info(Info, State) ->
?ERROR_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
iolist_to_list(IOList) ->
binary_to_list(iolist_to_binary(IOList)).
us_to_key({LUser, LServer}) ->
<<"ejabberd:sm:", LUser/binary, "@", LServer/binary>>.
@@ -143,7 +184,7 @@ decode_session_list(Vals) ->
[binary_to_term(Val) || {_, Val} <- Vals].
clean_table() ->
?INFO_MSG("Cleaning Redis SM table...", []),
?DEBUG("Cleaning Redis SM table...", []),
try
lists:foreach(
fun(LServer) ->
@@ -169,17 +210,3 @@ clean_table() ->
catch _:{badmatch, {error, _}} ->
?ERROR_MSG("failed to clean redis c2s sessions", [])
end.
opt_type(redis_connect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_db) ->
fun (I) when is_integer(I), I >= 0 -> I end;
opt_type(redis_password) -> fun iolist_to_list/1;
opt_type(redis_port) ->
fun (P) when is_integer(P), P > 0, P < 65536 -> P end;
opt_type(redis_reconnect_timeout) ->
fun (I) when is_integer(I), I > 0 -> I end;
opt_type(redis_server) -> fun iolist_to_list/1;
opt_type(_) ->
[redis_connect_timeout, redis_db, redis_password,
redis_port, redis_reconnect_timeout, redis_server].
+72
View File
@@ -0,0 +1,72 @@
%%%-------------------------------------------------------------------
%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
%%% Created : 15 Apr 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(ejabberd_sm_riak).
-behaviour(ejabberd_sm).
%% API
-export([init/0, set_session/1, delete_session/1, get_sessions/0,
get_sessions/1, get_sessions/2]).
-include("ejabberd_sm.hrl").
-include("logger.hrl").
%%%===================================================================
%%% API
%%%===================================================================
init() ->
clean_table().
set_session(Session) ->
ejabberd_riak:put(Session, session_schema(),
[{'2i', [{<<"us">>, Session#session.us}]}]).
delete_session(Session) ->
ejabberd_riak:delete(session, Session#session.sid).
get_sessions() ->
case ejabberd_riak:get(session, session_schema()) of
{ok, Ss} -> Ss;
{error, _} -> []
end.
get_sessions(LServer) ->
[S || S <- get_sessions(), element(2, S#session.us) == LServer].
get_sessions(U, S) ->
ejabberd_riak:get_by_index(session, session_schema(), <<"us">>, {U, S}).
%%%===================================================================
%%% Internal functions
%%%===================================================================
session_schema() ->
{record_info(fields, session), #session{}}.
clean_table() ->
%% TODO: not very efficient, rewrite using map-reduce or something
?DEBUG("Cleaning Riak 'sm' table...", []),
lists:foreach(
fun(#session{sid = {_, Pid} = SID}) when node(Pid) == node() ->
ejabberd_riak:delete(session, SID);
(_) ->
ok
end, get_sessions()).

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